I'm working a crowding contract in solidity. The contract has been deployed and I also generated a Go ABI. I've managed to generate the transaction hash and sign it using web3.js by hooking into bind.TransactOpts.Signer (I compiled go-ethereum to wasm/js).

The only issue is that I don't know how to hash the transaction for the "buyTokens" function in a EIP712Domain format so that the UI can display a more friendly message like on uniswap[0]

If I hash the transaction using github.com/ethereum/go-ethereum/core/types.LatestSignerForChainID(chainID).Hash(*types.Transaction).Bytes() I get an ugly/confusing UI[1] displayed on most wallets.

I assume that I need to use a custom signer

signerFn := func(address common.Address, tx *types.Transaction) (rtx *types.Transaction, err error) {
        // how do I hash the transaction here with EIP712Domain/Typed data
        // so that the approval can be can be presented in a 
        // user friendly manner to the user. ???

        txBytes := signer.Hash(tx).Bytes()
        signature, err := metamaskgo.RequestSign(txBytes)
        if err != nil {
            return nil, err
        return tx.WithSignature(signer, signature)

    txOpts := &bind.TransactOpts{
        From:     buyerAddrCom,
        Nonce:    nil,
        Context:  cx,
        Signer:   signerFn,
        Value:    big.NewInt(1),
        GasPrice: client.Auth.GasPrice,
        GasLimit: 0,
    _, err = service.BuyTokens(txOpts, buyerAddrCom)
    if err != nil {

I have found some examples using TypedData from the signer/core package but:

  • it's not clear to me where to integrate this kind of meta data with the transaction generated by the service.BuyTokens/bind.TransactOpts
  • it's not clear to me at all where are the types documented (e.g. PrimaryType: "Challenge" seems to be a "well-known" value but I can't find it anywhere documented).

Any help would be greatly appreciated.

Example of TypedData

  import signerPkg "github.com/ethereum/go-ethereum/signer/core"
  signerData = signerPkg.TypedData{
            Types: signerPkg.Types{
                "Challenge": []signerPkg.Type{
                    {Name: "address", Type: "address"},
                    {Name: "nonce", Type: "string"},
                    {Name: "timestamp", Type: "string"},
                "EIP712Domain": []signerPkg.Type{
                    {Name: "name", Type: "string"},
                    {Name: "chainId", Type: "uint256"},
                    {Name: "version", Type: "string"},
                    {Name: "salt", Type: "string"},
            PrimaryType: "Challenge",
            Domain: signerPkg.TypedDataDomain{
                Name:    "MyCrowding Transaction",
                Version: "1",
                Salt:    salt,
                ChainId: math.NewHexOrDecimal256(56),
            Message: signerPkg.TypedDataMessage{
                "timestamp": timestamp,
                "address":   walletAddress,
                "nonce":     nonce,

Solidity sale contract (partial)

function buyTokens(address beneficiary) public nonReentrant payable {
        uint256 weiAmount = msg.value;
        _preValidatePurchase(beneficiary, weiAmount);

        // calculate token amount to be created
        uint256 tokens = _getTokenAmount(weiAmount);

        // update state
        _weiRaised = _weiRaised.add(weiAmount);

        _processPurchase(beneficiary, tokens);
        emit TokensPurchased(_msgSender(), beneficiary, weiAmount, tokens);

        _updatePurchasingState(beneficiary, weiAmount);

        _postValidatePurchase(beneficiary, weiAmount);

[0] https://miro.medium.com/max/4800/0*75ZGYTio_xraRlHc

[1] https://i.stack.imgur.com/B53xC.png

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.