Support Web3 wallets in StarkNet

Support Web3 wallets in StarkNet

Goal

Support Web3 wallets in StarkNet (SN) without applying any changes to the wallets themselves. The wallet will be configured with a custom RPC endpoint and work as usual without being aware of SN.

What are the gaps today?

  1. Signature - Today SN only supports a native signature while Web3 wallets are signing using an Ethereum signature (SECP256k1 curve).

  2. EOA address - Ethereum uses an EOA address while SN uses account abstraction and the address is calculated as follows:
    pedersen(PREFIX, caller_address, salt, contract_hash, ctor_arguments)

  3. API - Even though SN API tries to follow Ethereum’s API, the transaction structure is not exactly the same.

Proposed Solution

High-Level Overview

The signature gap will be solved internally by StarkWare; the ability to verify Ethereum signatures is already in the work and should be available soon. This will enable the account contract to verify the Ethereum signature (however, it will be less efficient than the native signature).

For the address and API gaps, we propose to add an adapter between the wallet and SN’s sequencer. This adapter will have two roles:

  1. Translate the Ethereum transaction format to a format corresponding to SN API
  2. Calculate the SN account contract address from the available data in the transaction - the Ethereum signature.

In addition, a web3 wallet user will have a new type of an account contract that can verify Ethereum signatures and parse the messages that are constructed by the adapter (the beauty of account abstraction :star_struck:).

Based on those building blocks, we can describe the following flow (see the figure below):

  1. The Application sends the transaction to be signed to the wallet.
  2. The wallet signs the transaction with the user’s Ethereum key and sends the signed transaction to the SN custom RPC node.
  3. The RPC node contains the adapter, which reformats the transaction fields and calculates the SN account address and then sends it to the SN’s sequencer to be executed.
  4. The account contract gets the transaction, verifies that the Ethereum signature is valid against the user’s Ethereum address, which is stored in the contract. Eventually, it activates the execution of the transaction in the target contract.

How to Adapt the Transaction?

A transaction submitted to a web3 wallet will be encoded as follows:

  1. From - contains the user’s Ethereum address
  2. To - is not used since its size is only 20 bytes and SN addresses are 31 bytes
  3. Data - contains the target contract address, the target contract selector and the call data of the transaction
  4. Gas, nonce, and value fields are not used

For example, here is a web3 transaction:

{
    from: "0x112233...4455",//20 bytes - the ethereum address of the user
    to: "", //the target account address is provided in the call data
    data : "0x54321 98765 12345 13579 100" ,
    gasLimit: "0",
    maxFeePerGas: "0",
    maxPriorityFeePerGas: "0",
    nonce: "0",
    value: "0",
    R: ”r”,
    S: “s”,
    V: “v”,
}

This Web3 transaction is adapted to the following SN transaction format:

{
    contract_address: “0x12345” , // The SN account address
    entry_point_selecter: “0x67891”,
    call_data: "0x54321 98765 12345 13579 100" ,
    signature: [r, s, v]
}

How to Calculate the SN Address from the Ethereum Address?

The contract_address field is computed as follows:
contract_address:= pedersen(PREFIX, caller_address, salt,contract_hash, ctor_arguments)
where:

PREFIX="SN_CONTRACT_ADDRESS"
caller_address=0
salt=pre define constant
contract_hash= hash over a known contract byte code, configured once in the proxy
ctor_arguments = Ethereum address = keccak(ecrecover(msg_hash, v, r,s))[12:32]

More Wallet Functionalities to Support

Get Balance

  • Problem: When you configure a new ERC-20 in your wallet you need to provide a 20-byte address
  • Solution: Store a mapping of ERC-20 Ethereum addresses to SN addresses
    • Con: Users won’t be able to add custom ERC-20 tokens, only “whitelisted” tokens are mapped

Send (ETH/ERC-20)

  • Problem: To address field is only 20 bytes
  • Solutions: The To field will provide the Ethereum address of the receiver. The adapter will translate the To field from an Ethereum address to a SN address as described above.
    • Con: It is a bit confusing for the user to send the funds to an Ethereum address, which is not the real address where the funds will be received.

The diagram below summarizes both the Send Transaction and Get Balance flows in the adapter:

33 Likes

Nice post. Web3 wallet support, which optimistic rollups have a natural advantage in, would definitely help with SN adoption.

Such a contract would need to be deployed first though. We probably need some “account-contract-deployment-as-a-service” thingy here.

7 Likes

Yes, I agree, users should be able to ask for a web3 account creation.
Also, if a user sends a transaction before his account contract was deployed, the proxy can deploy it for him.
Note that anyway, the contract code must be known to the proxy because it uses it to calculate the SN address from the Ethereum address.

4 Likes

The proxy can’t just “deploy it for him” after StarkNet introduces fees (very soon?) though. The user would have to pay, one way or another. This can be implemented as an L1 (not necessarily Ethereum!) service that can be used with a Web3 wallet.

I imagine SN dApps would check for account contract deployment first for users using these wallets, and if not found, instruct them to use such a service.

5 Likes

This is looking good! If I understand correctly, this design requires almost no changes on our current Account contract implementation aside from using Ethereum’s ecdsa, right?

Since this is definitely a hard UX problem for many parties: users, wallet developers, and frontend/dapp developers; I’d like to address a few UX issues we might face:

Account addresses

This solution definitely works. But a big problem for users is going to be the split between “their address” (what they see in metamask, the EOA) and their account contract address. This could open the door for user mistakes such as sending/receiving tokens to EOA addresses instead of account addresses).

Another common error is to send tokens to the right address on the wrong network (e.g. send to polygon instead of eth mainnet). With the EOA/account contract address split, funds sent to the right user on the wrong network could result in loss of funds.

It would also be very confusing for users to understand the difference between these addresses (which they will definitely encounter in different places such as web dapps, Metamask, tx metadata, etc) and this confusion could be also exploited by attackers. This is also highlighted on the getBalance/send ERC20 section of the OP.

To minimize risk, reduce cognitive overhead for users, and simplify overall adapters, I’d like to suggest adding a special CREATE opcode to deploy accounts to an EOA-derived address. Without taking into account contract code, salts, etc. Only proving EOA ownership once. This way, if you own an ethereum private key that controls a given EOA, you can deploy an account contract to the same address. This should be feasible since ethereum addresses are 20bytes long, smaller thus representable in Starknet’s >31bytes.

Metadata display

There’s also the issue of how tx metadata is shown to the user, since every tx is in a way a meta-transaction (all txs are redundantly calling execute on the same account contract, always). Solving this issue will probably require wallet-side development.

Nonces

I think we should leverage wallet-side nonce management. The RPC endpoint could expose a method for getting the nonce of a given account, then use that to build the transaction. Did you have anything in mind regarding this?

Account creation

When should account be created? Who’s going to pay for it? Maybe this is something the dapps could cover, but it still bears the UX issue of requiring users to sign an account creation transaction, which will not always be obvious/easy to explain. I think we should think about this flow too.

13 Likes

I love the idea and would vote for it. Notice that we could also have this for StarkKeys as it would enable stateless wallets like Ledger to be aware of their own address.

The main concern is what happens if a contract has already been deployed at the said address.
Should we overwrite the contract or not? We have an idea to solve it using a proxy that can then changes its instantiation without needing to overwrite the said contract

8 Likes

Yes I was also thinking that deploying proxies could be an option, but I’d leave the CREATE opcode as a one-off thing, meaning it would only work once. I wouldn’t impose “account addresses” to be proxies either, although it will probably be the most common thing.

5 Likes

There is a problem if the RPC node sends you a crappy contract to deploy. How would you replace it?

2 Likes

Hmm although I don’t think user onboarding should be handled by the RPC, it’s true that someone, whoever is onboarding the user, has the power to influence what account contract will be associated to the user’s keys.

So it makes sense to have an upgradeable proxy associated to a pair of ethereum keys for the reasons stated above, but there’s the problem of some party (e.g. uniswap, coinbase, infura) choosing your account contract for life. I’m buying into forcing that this CREATE method instantiates a proxy, to protect users from tying themselves to a given contract unknowingly.

4 Likes

what do you mean by implemented as a L1?

2 Likes

Yes, and storing the Ethereum address in the contract.

We are considering this approach, which I agree provides better UX, with the proxy as was discussed above. However, this requires changes to the SN OS, which should be considered carefully and will add development time and delay the web3 wallet’s support. We can start with the described approach at first in order to do this faster and consider adding this as second phase.

Again, I agree with you, this is an important issue. We thought of using EIP-712, just trying to figure out if this is a standard supported by most wallets? but even using this, the user will only be able to see the destination address and selector of the meta transaction and not the full parsing of the calldata.

5 Likes

Presumably if there was a Starknet token the fee could be paid for on any L1 it exists on.

1 Like

I’m sure @juniset might have good insight on this

3 Likes

I meant it can be implemented as an L1 service, where it can take 2 forms:

  • trust-less: implemented as a smart contract on Ethereum, which sends an L1-L2 message to an L2 contract that deploys (CREATE2?) the account contract to an address specified in the message

  • centralized: pay the service provider on whatever L1 with whatever token that’s accepted, and hope that the provider will deploy the contract correctly

The trust-less option would be the best, semantically, but would also incur a non-negligible gas cost.

4 Likes

IMO EIP-712 is good enough for now. After all, it’s impossible to display extra metadata without making changes to wallets themselves anyways.

3 Likes

One thing that needs to be implemented if we use EIP712 is that we still need to mimic an RPC change if we want to keep the same “changing network approach” that all other chains use.
Not a blocking thing just something to have in mind

4 Likes

Got it, makes sense. I assume that most people wouldn’t want to pay L1 gas, even though it’s better trust-wise. So probably the second way would be more popular. A third option is that applications would subsidize the deployment in order to onboard users and potentially profit later. Anyway this service is still required.

6 Likes

Agree, it’s usually apps who onboard users, hence my comments above.

6 Likes

In that case it would really help if there’s a de facto (maybe even official/semi-official) account contract factory on SN that dApps can just invoke to deploy a certain type of account contract, instead of having dApps deploy directly.

I mean you can’t really stop dApps from going with raw deploy, but at least there should be a widely accepted option for those who want to do things right.

7 Likes

This is still not possible unfortunately, since we don’t yet support contract factories. I will be supported in future versions.

6 Likes