Skip to main content

Documentation Index

Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Use the Java SDK to sign EIP-712 typed data with your server wallet. Typed data signing is used for permits, order signing, and other structured data that produces a recoverable signature. DynamicEvmWalletClient::signTypedData takes the typed data as a JSON string (the standard EIP-712 envelope: domain, types, primaryType, message) and returns the 0x-prefixed r || s || v signature.

Prerequisites

Sign Typed Data

import xyz.dynamic.waas.evm.opts.SignTypedDataOpts;

String typedDataJson = """
    {
      "domain": {
        "name": "My App",
        "version": "1",
        "chainId": 1
      },
      "types": {
        "EIP712Domain": [
          {"name": "name",    "type": "string"},
          {"name": "version", "type": "string"},
          {"name": "chainId", "type": "uint256"}
        ],
        "Message": [
          {"name": "content", "type": "string"},
          {"name": "from",    "type": "address"}
        ]
      },
      "primaryType": "Message",
      "message": {
        "content": "Hello!",
        "from": "0xYourWalletAddress"
      }
    }""";

String signature = client.signTypedData(SignTypedDataOpts.builder()
    .typedDataJson(typedDataJson)
    .walletProperties(walletProperties)
    .externalServerKeyShares(externalServerKeyShares)
    .build()
).join();

System.out.println("Signature: " + signature);
The method returns the serialized ECDSA signature as a 0x-prefixed hex string (130 hex chars).

ERC-2612 Permit Example

A common use case is signing ERC-2612 token permits, allowing gasless approvals:
String permitJson = """
    {
      "domain": {
        "name": "USD Coin",
        "version": "2",
        "chainId": 1,
        "verifyingContract": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
      },
      "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",
      "message": {
        "owner":    "0xYourWalletAddress",
        "spender":  "0xSpenderAddress",
        "value":    "1000000",
        "nonce":    "0",
        "deadline": "1735689600"
      }
    }""";

String signature = client.signTypedData(SignTypedDataOpts.builder()
    .typedDataJson(permitJson)
    .walletProperties(walletProperties)
    .externalServerKeyShares(externalServerKeyShares)
    .build()
).join();
Large integers (uint256) should be serialized as JSON strings to avoid precision loss — Java’s JSON parsers and the underlying web3j EIP-712 encoder both accept the string form.

Verifying a typed-data signature

EIP-712 signatures recover the same way as EIP-191 — hash the typed-data digest, then run ecrecover against the signature. Use web3j’s StructuredDataEncoder:
import org.web3j.crypto.StructuredDataEncoder;
import org.web3j.crypto.Sign;
import org.web3j.crypto.Sign.SignatureData;
import org.web3j.crypto.Keys;
import org.web3j.utils.Numeric;

byte[] digest = new StructuredDataEncoder(typedDataJson).hashStructuredData();
byte[] sig    = Numeric.hexStringToByteArray(signature);

SignatureData sd = new SignatureData(
    sig[64],
    java.util.Arrays.copyOfRange(sig, 0, 32),
    java.util.Arrays.copyOfRange(sig, 32, 64)
);
String recovered = "0x" + Keys.getAddress(Sign.signedMessageHashToKey(digest, sd));

assert recovered.equalsIgnoreCase(walletProperties.accountAddress());

Next Steps