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

Sending EIP-712 Transactions #171

Open
fabiooshiro opened this issue Mar 6, 2025 · 48 comments
Open

Sending EIP-712 Transactions #171

fabiooshiro opened this issue Mar 6, 2025 · 48 comments
Labels

Comments

@fabiooshiro
Copy link

fabiooshiro commented Mar 6, 2025

Hey everyone,

We’re considering how to structure a command for sending EIP-712 transactions and would love some input.

The process includes:

  1. Retrieving the nonce (/nonce endpoint).
  2. Building the EIP-712 message.
  3. Signing the transaction.
  4. Submitting the transaction (/submit endpoint).

This applies to both Espresso and Paio + Avail, which follow the same API structure.

Possible Approaches:

  • cartesi send A general send command with an option to specify EIP-712 format.
  • cartesi <context> send A subcommand that provides additional context before sending, such as cartesi l2 send.
  • Other suggestions?
@sandhilt
Copy link

sandhilt commented Mar 6, 2025

I like this flexibility; EIP-712 could be a great suggestion.

@tuler
Copy link
Member

tuler commented Mar 6, 2025

Signing a EIP-712 structure, and sending a transaction are two independent actions.
It doesn't mean a send subcommand can exist for sending a different transaction, but the destination is more relevant for the subcommand context than the EIP-712 building and signing process.

@tuler
Copy link
Member

tuler commented Mar 6, 2025

Just as an analogy, take a look at cast wallet sign.
It's under the wallet command. Not a send operation.

@fabiooshiro
Copy link
Author

In our case, we are considering executing the full process:

  1. Retrieve the nonce.
  2. Build the EIP-712 message.
  3. Sign the transaction.
  4. Send the transaction to the /submit endpoint.

The nonce is important for us...

@tuler
Copy link
Member

tuler commented Mar 6, 2025

Are you talking about espresso sequencer? Please provide context in the original post.
What endpoint?

@fabiooshiro
Copy link
Author

/nonce and /submit

Yes, for Espresso and Paio + Avail

@tuler
Copy link
Member

tuler commented Mar 6, 2025

/nonce and /submit

Yes, for Espresso and Paio + Avail

Does espresso and paio + avail follow the exact same /nonce and /submit API?

@fabiooshiro
Copy link
Author

Yes the same API

@fabiooshiro
Copy link
Author

Probably something like cartesi l2 send is the best option

@fabiooshiro
Copy link
Author

fabiooshiro commented Mar 11, 2025

Something like aws cli
aws <service> <commands>
aws s3 ls

@fabiooshiro
Copy link
Author

Today we have:

cartesi send
? Select send sub-command
  Send DApp address input to the application.
  Send ERC-20 deposit to the application.
  Send ERC-721 deposit to the application.
  Send ether deposit to the application.
❯ Send generic input to the application.
Sends generics inputs to the application, optionally in interactive mode.

Could be:

cartesi send
? Select send sub-command
  Send DApp address input to the application.
  Send ERC-20 deposit to the application.
  Send ERC-721 deposit to the application.
  Send ether deposit to the application.
  Send generic input to the application.
❯ Send L2 input to the application.

@tuler
Copy link
Member

tuler commented Mar 11, 2025

Currently every type of input is a subcommand of cartesi send.
The cartesi send command itself is another command that interactively allows the selection of one of the subcommands.

Consider also the future adoption of cartesi coprocessor, and a possible impact, with two possibilities:

  • move all rollups related inputs to cartesi rollups send [subcommand].
  • or simply the addition of a new send subcommand for coprocessor.

Even though coprocessor might be a different case, because the user develops a custom smart contract, which then sends an input to the coprocessor task issuer. We don't want to replicate what cast already provides, and we probably better off just driving the user to use cast.

Coming back to "L2" inputs, it's not very clear to me if sending a transaction to espresso or paio is equivalent to the current structure of the send subcommands. Because it looks to me more like a destination, not an input type or structure.

It looks more to me like sending a generic input, and then choosing the destination, which is either the InputBox L1 contract, or a sequencer.

@fabiooshiro
Copy link
Author

It looks more to me like sending a generic input, and then choosing the destination, which is either the InputBox L1 contract, or a sequencer.

I agree, how about:

cartesi send
? Select send sub-command
  Send DApp address input to the application.
  Send ERC-20 deposit to the application.
  Send ERC-721 deposit to the application.
  Send ether deposit to the application.
  Send generic L1 input to the application.
❯ Send generic L2 input to the application.

@tuler
Copy link
Member

tuler commented Mar 11, 2025

How about:

cartesi send generic [--sequencer evm|espresso]

@fabiooshiro
Copy link
Author

Nice!

@fabiooshiro
Copy link
Author

For the client side, it does not matter whether it is Espresso or Paio, as the /submit endpoint receives the same payload.

How about:
cartesi send generic [--sequencer evm|l2]

@fabiooshiro
Copy link
Author

I wrote a draft following the --sequencer evm|paio|espresso.
I have a question: how can I get the http://<host>:<port>?
https://github.com/cartesi/cli/pull/176/files#diff-b11d41edf88ae12622645be8879cc024f02289065340bbd8b9db1cbf83b568c3R10

@tuler
Copy link
Member

tuler commented Mar 13, 2025

You would have to ask for the address to send

@fabiooshiro
Copy link
Author

fabiooshiro commented Mar 13, 2025

The command line already asks for the address...
Im reusing the same code structure

@tuler
Copy link
Member

tuler commented Mar 13, 2025

Well, in case of sequencer !== evm, the address should not default to anvil, as you are not sending to anvil, but to the sequencer. Not there is also a chain-id parameter.

@miltonjonat
Copy link

miltonjonat commented Mar 13, 2025

Hi folks. I think there is some confusion here.

The user is not sending the input to a specific sequencer. He doesn't need to care if this will go to Espresso or Paio, only that the input will be submitted to an L2 endpoint that may act as a gateway to forward it to wherever is appropriate (usually some sequencer like Espresso or Paio).
So it's more like the old send is "tx submitted to the base chain (L1)" (most rollups call this "forced inclusion"), while the new send option is "tx submitted to the L2 network" (this is so common/trivial for most rollups that I don't know if they have a name for this, I call it "L2 tx").
Moreover, in our particular implementation, the L1 tx uses Ethereum JSON-RPC, while the L2 tx's currently use an EIP-712 signed message.

I'd use one of these options:

  1. cartesi send generic [--type L1|L2]
  2. cartesi send generic [--format evm|eip712] (or [--format jsonrpc|eip712])

For (1), I admit I'm not very fond of "type", which is too abstract. Maybe something like "layer", "network", "destination" or something like that could work better. But atm I think I still prefer "type" over those :P

@miltonjonat
Copy link

miltonjonat commented Mar 13, 2025

As a curiosity, which may be related: is there a cartesi send command that uses account abstraction? Does that make sense? (sorry for my ignorance)
It just crossed my mind that maybe that would be a 3rd format? i.e., if we use the --format option suggested above, maybe we could have [--format evm|eip712|erc4337]?

@miltonjonat
Copy link

miltonjonat commented Mar 13, 2025

Thinking more about this, I'm leaning more towards --format. It is in theory possible to implement a full Ethereum-compatible JSON-RPC layer on the Node, so that people could use regular Ethereum clients to send tx's directly to the L2 rollup. If that ever happens, then you would be able to send an L2 tx using --format evm and pointing to the Node's RPC endpoint, instead of the base layer's RPC endpoint.

@tuler
Copy link
Member

tuler commented Mar 13, 2025

There is no send for ERC-4337 operations. From the application backend perspective there is no difference of a generic input that comes from ERC-4337 (other than the fact that the sender is a smart account).

I think the utility of the cartesi send commands are overrated. They are just quick shortcuts that could otherwise be executed directly through cast, or cast + curl. And in general developers should move ASAP to their application frontends.

@tuler
Copy link
Member

tuler commented Mar 13, 2025

I haven't seen the term "format" in this context, but I have seen the term "type", as in transaction types (for ethereum).

But ERC-4337 and these paio thing are not transactions. Sometimes they are called meta-transactions, and ERC-4337 call them UserOps.

@miltonjonat
Copy link

miltonjonat commented Mar 14, 2025

Interesting info!

  1. I agree that cartesi send is just a shortcut, and for generic JSON-RPC tx's it's certainly not that much better than using cast. But for EIP-712 and ERC-4337 I think it's significantly more burdensome to do it from the command line
  2. I agree there is no difference from the back-end's pov: all inputs arrive the same! But if your frontend is not working for EIP-712 or ERC-4337, you won't have a good way of knowing if the issue is your code (more likely) or some issue with your environment. The CLI would allow you to test the environment.
  3. A doubt: in ERC-4337, how do these "UserOps" differ from regular tx's from the blockchain's point of view? Is there one L1 tx per user operation?
  4. From an L2's pov (think Arbitrum or Base), user inputs sent to the L2 Nodes are batched by a sequencer, and AFAIK each one is simply called "transaction" (on that L2, that's why I call them "L2 tx's"). They are not called "meta-transactions" or something else. Do you agree?

@fabiooshiro
Copy link
Author

fabiooshiro commented Mar 14, 2025

in case of sequencer !== evm, the address should not default to anvil, as you are not sending to anvil, but to the sequencer

The app address will be the same for EVM and L2 (Paio/Espresso)

Not there is also a chain-id parameter.

It's the base layer chain ID. It's useful to prevent the transaction from being replayed on another chain.

I still don't get how to determine the http://<host>:<port> of the local node.

@tuler
Copy link
Member

tuler commented Mar 14, 2025

I still don't get how to determine the http://<host>:<port> of the local node.

You currently can't. The cartesi rollups start has a --port parameter with a 8080 default.

@miltonjonat
Copy link

in case of sequencer !== evm, the address should not default to anvil, as you are not sending to anvil, but to the sequencer

The app address will be the same for EVM and L2 (Paio/Espresso)

I still don't get how to determine the http://<host>:<port> of the local node.

I think by "address" here @tuler means the URL endpoint for the send command, which for L1 JSON-RPC tx's defaults to http://localhost:8545 (Anvil). In the case of EIP-712 L2 tx's, it should default to wherever the CLI will be mapping the Node's /submit endpoint (e.g., http://localhost:<port>/tx/submit, where port follows the --port CLI param that defaults to 8080; there is a discussion about which path to use going on in #150)

@fabiooshiro
Copy link
Author

fabiooshiro commented Mar 17, 2025

I think by "address" here @tuler means the URL endpoint for the send command, which for L1 JSON-RPC tx's defaults to http://localhost:8545 (Anvil). In the case of EIP-712 L2 tx's, it should default to wherever the CLI will be mapping the Node's /submit endpoint (e.g., http://localhost:<port>/tx/submit, where port follows the --port CLI param that defaults to 8080; there is a discussion about which path to use going on in #150)

IMO, it should be http://<host>:<port>/submit and http://<host>:<port>/nonce since there is no difference between Paio, Espresso, or Anvil.

Node v2 abstracts which L2 it is using (Paio, Espresso, or Anvil) from the end user's point of view.

Image

@miltonjonat
Copy link

miltonjonat commented Mar 17, 2025

Ok, but IMO the default path in the local environment is something to be discussed in #150.
In this issue, I think the discussion is about the command format, right?

From everything we discussed above, I think the best option we currently have is:

cartesi send generic [--type evm|eip712]

where:

  • evm is the default type, and means sending a normal L1 tx to the InputBox via JSON-RPC
  • a third type option erc4337 may be added in the future if we think it's useful

But I'm still a bit unsure if this is the best solution. The current options for cartesi send generic are the following AFAIK:

  --chain-id <id>                        Chain ID (default: 31337)
  --rpc-url <url>                        RPC URL
  --mnemonic <phrase>                    Mnemonic passphrase
  --mnemonic-index <index>               Mnemonic account index (default: 0)
  --dapp <address>                       Application address
  --input <input>                        input payload
  --input-encoding <input-encoding>      input encoding (choices: "hex", "string", "abi")
  --input-abi-params <input-abi-params>  input abi params

I think they all have the same meaning for both evm and eip712, except for --rpc-url, which for evm means a JSON-RPC endpoint. For eip712, it could be interpreted as the "base eip712 url" (e.g., http://localhost:8080 or http://localhost:8080/tx, which should have endpoints /submit and /nonce). But I'm not sure if that's the best we can do.

What do you think?

@tuler
Copy link
Member

tuler commented Mar 17, 2025

I think they all have the same meaning for both evm and eip712, except for --rpc-url
What do you think?

I think the params will be conditioned to the transaction type, rpc-url in case of evm and a different url in case of eip712, probably using advanced param parsing provided by commander's conflicts and implies.

@miltonjonat
Copy link

miltonjonat commented Mar 17, 2025

I think the params will be conditioned to the transaction type, rpc-url in case of evm and a different url in case of eip712, probably using advanced param parsing provided by commander's conflicts and implies.

Ohh, that's nice, I didn't know that was an option.

So, do you like that proposal of doing cartesi send generic [--type evm|eip712]?

The URL options could then be something like --submit-url and --nonce-url?

@tuler
Copy link
Member

tuler commented Mar 17, 2025

So, do you like that proposal of doing cartesi send generic [--type evm|eip712]?

More or less. I still think json-rpc was the way to go. With a cartesi_sendTransaction or something like that. Especially now that the node has a RPC, which will be available at http://127.0.0.1:8080/rpc

The URL options could then be something like --submit-url and --nonce-url?

I guess just 1 url.

@fabiooshiro
Copy link
Author

What about --node-base-url default http://localhost:8080

<node-base-url>/nonce
<node-base-url>/submit

@tuler
Copy link
Member

tuler commented Mar 17, 2025

<node-base-url>/nonce <node-base-url>/submit

The CLI has several services that can be enabled.

http://127.0.0.1:8080/rpc
http://127.0.0.1:8080/inspect
http://127.0.0.1:8080/graphql
http://127.0.0.1:8080/explorer
http://127.0.0.1:8080/paymaster
http://127.0.0.1:8080/bundler/rpc

I don't know if /submit and /nonce classify as services to deserve a root path.

If we have http://127.0.0.1:8080/<service>/nonce and http://127.0.0.1:8080/<service>/submit what would you call <service>?

@fabiooshiro
Copy link
Author

fabiooshiro commented Mar 17, 2025

/nonce is associated with an account, while /submit relates to the generic input.

/account/nonce

/input/submit

account+input = transaction

@tuler
Copy link
Member

tuler commented Mar 17, 2025

I meant the component name.

@miltonjonat
Copy link

miltonjonat commented Mar 17, 2025

I guess it would be an eip712 service then right? With endpoints /submit and /nonce

i.e., http://127.0.0.1:8080/eip712

@miltonjonat
Copy link

More or less. I still think json-rpc was the way to go. With a cartesi_sendTransaction or something like that. Especially now that the node has a RPC, which will be available at http://127.0.0.1:8080/rpc

This is interesting. Have you already thought about details of this?

@fabiooshiro
Copy link
Author

Someone needs to decide this so we can move forward.
My vote is for http://localhost:8080/transaction/<nonce/submit>.

I'm okay with whatever you guys decide ;-)

@tuler
Copy link
Member

tuler commented Mar 18, 2025

Have you already thought about details of this?

Haven't thought about details.
For comparison here is the ERC-4337 eth_sendUserOperation.

@miltonjonat
Copy link

Someone needs to decide this so we can move forward. My vote is for http://localhost:8080/transaction/<nonce/submit>.

I'm okay with whatever you guys decide ;-)

I'm ok with /transaction, or with /tx. It would represent a transaction submission service, with a /submit endpoint (using EIP-712 format) and a /nonce endpoint.

@tuler
Copy link
Member

tuler commented Mar 19, 2025

We can do that.
But I think we should have a long-term discussion about having a JSON-RPC method for sending transactions.

@miltonjonat
Copy link

We can do that.
But I think we should have a long-term discussion about having a JSON-RPC method for sending transactions.

Agree 100%

@fabiooshiro
Copy link
Author

We can do that. But I think we should have a long-term discussion about having a JSON-RPC method for sending transactions.

Yeah, sure.

@fabiooshiro
Copy link
Author

fabiooshiro commented Mar 20, 2025

I updated the PR #176 with --node-base-url, http://localhost:8080/transaction/<nonce/submit> and --type evm|eip712

For comparison here is the ERC-4337 eth_sendUserOperation.

I'm studying this format.

@miltonjonat
Copy link

For comparison here is the ERC-4337 eth_sendUserOperation.

I'm studying this format.

Yeah, this really looks like what we'd like to do. When submitting, it returns a userOpHash, which can then be used to retrieve the UserOp itself, or its receipt with logs. Pretty much what we are doing: when submitting an EIP-712 tx, we return an identifier that can be used to retrieve that input and its related outputs. It would be great if we could identify InputBox inputs by a hash as well, so that we could use hashes to identify any input, regardless of origin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants