Problem

Currently, "Upload code" button on Vara.ETH IDEA redirects to documentation and forces user to use terminal and paste their private key into Vara.ETH CLI simply to upload WASM file with Gear program's code to Vara.ETH IDEA. This is big problem from UX perspective, since builders can't just create smart contract and drag-and-drop WASM file into our front-end. Instead, they will have to use terminal, download binary of Vara.ETH CLI, and figure out how to do it using special command in Vara.ETH CLI. image00

Another issue is that we use EIP-4844/EIP-7594 blob transactions to upload WASM files to Vara.ETH. On the one hand, this is convenient, since we use Ethereum as temporary storage for blob data and essentially don't need our own centralized service for this. User sends EIP-4844/EIP-7594 blob transactions and simultaneously interacts with our smart contract, huge WASM file of up to 512 KiB is uploaded to Ethereum and currently it is quite cheap (even less than dollar, only $0.06 fee in ETH as of April 25, 2026): example of such transaction with EIP-4844/EIP-7594 on Ethereum Mainnet. However, blob transactions were originally designed to allow Ethereum L2 to publish its compressed data, which is stored for about 2-3 weeks and then deleted. This means that these blob transactions were originally intended to be used not for browser wallets like MetaMask, but for Ethereum L2 sequencers accounts that operate under private key, and we simply cannot send such transaction on client because we need to depends on KZG library to frontend, it takes long time to initialize on frontend (up to 30 seconds), and MetaMask wallet itself does not support sending such transactions: MetaMask/core issue #8331. Other wallets likely have this problem too. We could probably vibe-code support for it into MetaMask/TrustWallet, but we don't manage their release cycles, so we might have to wait three months to integrate this new feature, which would significantly improve our user experience. We want to fix UX in 1-2 weeks, not wait that long.

Our solution: Charge base fee in WVARA ERC20 token and uploading WASM file in one transaction

We've also introduced a new approach to managing user balances in WVARA ERC20 tokens through ERC20Permit extension, called ERC-2612. Instead of sending two on-chain transactions to Ethereum, we simply obtain an off-chain signature and then use this signature in next transaction to charge fee for calling Router.requestCodeValidation(...). This allows us to save money for users on fees when using Vara.ETH via Ethereum. This is how it appears in wallet like MetaMask:

image01

We won't go into detail about ERC-2612 standard. In short, user signs domain separator (contract they interact with): EIP712Domain(string name,string version,uint256 chainId,address verifyingContract) and Permit type (Who, in what quantity, and for how long can manage ERC20 tokens on behalf of connected wallet): Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline). Signature is returned in format (uint8 v, bytes32 r, bytes32 s) and allows to increase _allowances[owner][spender] in ERC20 smart contract. This allows for avoiding sending an extra transaction and simply inserting ready-made v, r, s signature into method, thereby saving transaction gas. This method is similar to what ERC20.approve(address _spender, uint256 _value) does, but using an off-chain signature.

Here's an example of how to open browser window to sign such request to manage ERC20 tokens on behalf of Router:

javascript
await window.ethereum.request({ "method": "eth_signTypedData_v4", "params": [ // connected wallet address "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", { types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" } ], Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" } ], }, primaryType: "Permit", domain: { // WrappedVara.eip712Domain().name name: "Wrapped Vara", // WrappedVara.eip712Domain().version version: "1", // WrappedVara.eip712Domain().chainId chainId: 1, // WrappedVara.eip712Domain().verifyingContract verifyingContract: "0xB67010F2246814e5c39593ac23A925D9e9d7E5aD" }, message: { // connected wallet address (msg.sender) owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Router address spender: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", // 1_000 WVARA ERC20 tokens = 1_000 * (10 ** WrappedVara.decimals()) value: 1000000000000000, // WrappedVara.nonces(owner) nonce: 0, // block.timestamp + 5 minutes deadline: 1777200000 } } ] })

Vara.ETH Router smart contract can then spend tokens using signature:

solidity
function requestCodeValidation( bytes32 _codeId, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s ) external whenNotPaused { require(blobhash(0) != 0, BlobNotFound()); Storage storage router = _router(); require( router.genesisBlock.hash != bytes32(0), RouterGenesisHashNotInitialized() ); require( router.protocolData.codes[_codeId] == Gear.CodeState.Unknown, CodeAlreadyOnValidationOrValidated() ); IWrappedVara _wrappedVara = IWrappedVara(router.implAddresses.wrappedVara); uint256 baseFee = router.protocolData.requestCodeValidationBaseFee; try _wrappedVara.permit( msg.sender, /* owner */ address(this), /* spender */ baseFee, /* value */ _deadline, _v, _r, _s ) {} catch {} bool success = _wrappedVara.transferFrom( msg.sender, /* from */ address(this), /* to */ baseFee /* value */ ); require(success, TransferFromFailed()); router.protocolData.codes[_codeId] = Gear.CodeState.ValidationRequested; emit CodeValidationRequested(_codeId); }

We've just covered how we save users money on transactions when interacting with Vara.ETH, but it's worth noting that Router.requestCodeValidation(...) still can't be called from wallet like MetaMask because it requires sending EIP-4844/EIP-7594 blob transactions. We use require(blobhash(0) != 0, BlobNotFound()) to reject non-blob transactions. We simply check that transaction has at least one blob hash.

Relaying of WASM file upload using EIP-712 off-network signatures

As you just saw, we obtained an off-chain signature to transfer control of certain amount of WVARA ERC20 tokens to Router. What if we asked user to sign their address, WASM file hash (CodeId), and list of blob hashes expected in transaction? In this case, we will charge user an additional fee of 500 WVARA and transfer it to Router address. Someone else will pay for transaction and send only following data in it: blob object with WASM file, address of requester that initiated this, CodeId, bytes32[] blobHashes, deadline when signature on both off-chain messages expires, two pairs of ECDSA signatures in format (uint8 v, bytes32 r, bytes32 s). We will call this EIP-712 type RequestCodeValidationOnBehalf(address requester,bytes32 codeId,bytes32[] blobHashes,uint256 nonce,uint256 deadline). Here's an example of what it will look like in your wallet:

image02

How this is implemented in Router smart contract. One interesting solution here is that we simply iterate over blobhash(i) until it returns a zero hash. We use this approach to check number of blob hashes.

solidity
function requestCodeValidationOnBehalf( address _requester, bytes32 _codeId, bytes32[] calldata _blobHashes, uint256 _deadline, uint8 _v1, bytes32 _r1, bytes32 _s1, uint8 _v2, bytes32 _r2, bytes32 _s2 ) external whenNotPaused { require(blobhash(0) != 0, BlobNotFound()); Storage storage router = _router(); require( router.genesisBlock.hash != bytes32(0), RouterGenesisHashNotInitialized() ); require( router.protocolData.codes[_codeId] == Gear.CodeState.Unknown, CodeAlreadyOnValidationOrValidated() ); uint256 _blobHashesLength = 0; while (true) { if (blobhash(_blobHashesLength) == bytes32(0)) { break; } _blobHashesLength++; } require( _blobHashes.length == _blobHashesLength, InvalidBlobHashesLength(_blobHashes.length, _blobHashesLength) ); for (uint256 i = 0; i < _blobHashes.length; i++) { bytes32 expectedBlobHash = blobhash(i); require( _blobHashes[i] == expectedBlobHash, InvalidBlobHash(i, _blobHashes[i], expectedBlobHash) ); } require(block.timestamp <= _deadline, ExpiredSignature(_deadline)); bytes32 structHash = keccak256( abi.encode( REQUEST_CODE_VALIDATION_ON_BEHALF_TYPEHASH, _requester, _codeId, keccak256(abi.encodePacked(_blobHashes)), _useNonce(_requester), _deadline ) ); bytes32 hash = _hashTypedDataV4(structHash); address signer = ECDSA.recover(hash, _v1, _r1, _s1); require(signer == _requester, InvalidSigner(signer, _requester)); IWrappedVara _wrappedVara = IWrappedVara(router.implAddresses.wrappedVara); uint256 fee = router.protocolData.requestCodeValidationBaseFee + router.protocolData.requestCodeValidationExtraFee; try _wrappedVara.permit( _requester, /* owner */ address(this), /* spender */ fee, /* value */ _deadline, _v2, _r2, _s2 ) {} catch {} bool success = _wrappedVara.transferFrom( _requester, /* from */ address(this), /* to */ fee /* value */ ); require(success, TransferFromFailed()); router.protocolData.codes[_codeId] = Gear.CodeState.ValidationRequested; emit CodeValidationRequested(_codeId); }

As result, we've improved Vara.ETH IDEA UX and made it similar to using regular Gear IDEA. While this does require some backend support, it's consistent with Web3 concepts. This feature has already been added to Vara.ETH IDEA frontend.

Difference between Vara.ETH CLI and new WASM file upload form:

  • Before:

    .ethexe.toml:

    toml
    [ethereum] rpc = "wss://hoodi-reth-rpc.gear-tech.io/ws" beacon-rpc = "https://hoodi-lighthouse-rpc.gear-tech.io" router = "0xE549b0AfEdA978271FF7E712232B9F7f39A0b060"
    bash
    ./ethexe tx --sender "$SENDER" upload --watch \ ./target/wasm32-gear/release/counter.opt.wasm
  • After:

    Vara.ETH Testnet Faucet image03

    Vara.ETH IDEA image04 image05 image06 image07

Vara

Website | X | Discord | Telegram | Wiki | GitHub

Gear Protocol

Website | X | Discord | Telegram | GitHub | Gear IDEA | Whitepaper