A minimal, end-to-end example that demonstrates TIP-712 (TRON’s EIP-712 variant) by:
- deploying a small on-chain verifier contract with TronBox,
- signing a typed message off-chain with TronWeb, and
- verifying the signature locally and on-chain.
contracts/Tip712Verifier.sol– a tiny on-chain signature checker. It builds a fixed TIP-712 domain separator at deploy time (name, version, this contract’s address, TRON’s 32-bitchainId) and exposesverify(...)which recomputes the TIP-712 digest for aMailmessage and usesecrecoverto confirm the signer.demo.js– Node.js script that constructs domain / types / value, signs the typed data viatronWeb.trx._signTypedData, normalizes thevbyte (00/01 → 1b/1c), verifies locally, and then calls the contract’sverifyto confirm on-chain.migrations/2_deploy_tip712.js– TronBox migration that deploys the verifier withNAMEandVERSIONfrom your environment.tronbox.js– networks & compiler settings (Nile by default)..env.sample– template for your local.env.
- Domain = the stamp:
{ name, version, chainId(32-bit), verifyingContract }
Binds a signature to one app/contract/chain → prevents replay elsewhere. - Types = the form template: schema for your structs.
- Value = the filled form: the actual data you approve.
TRON specifics:
- Domain uses low 32 bits of
block.chainid.- Mainnet:
0x2b6653dc - Nile:
0xcd8690dc
- Mainnet:
- Addresses are handled as 20-byte values under the hood.
- Some tools return signature
vas0/1; many verifiers expect27/28→ normalize for compatibility.
TRON-TIP712-DEMO
├─ build/contracts/ # created by tronbox compile/migrate
├─ contracts/
│ ├─ Migrations.sol
│ └─ Tip712Verifier.sol
├─ migrations/
│ └─ 2_deploy_tip712.js
├─ demo.js
├─ tronbox.js
├─ .env # your real secrets (not committed)
├─ .env.sample # template
└─ package.json
- Node.js 20+
- TronBox CLI:
npm i -g tronbox - A TRON account private key with some Nile test TRX
- RPC endpoint (default in this repo):
https://nile.trongrid.io
npm install
# (If you don't have TronBox globally)
npm i -g tronboxCopy .env.sample → .env, then edit:
FULL_HOST=https://nile.trongrid.io
PRIVATE_KEY=YOUR_PRIVATE_KEY_64HEX
NETWORK=nile
# TIP-712 domain values
NAME=TRON TIP-712 Demo
VERSION=1
# Set this after deployment (see step 3):
VERIFYING_CONTRACT=TP....................Note:
VERIFYING_CONTRACTis the deployed contract address (base58 T-address) you’ll paste after running migrations.
tronbox compile
tronbox migrate --reset --network nileGrab the printed contract address (T-address) from migration output and put it in .env as VERIFYING_CONTRACT=.
If you see
Could not find artifacts for ./Migrations, ensurecontracts/Migrations.solexists (and optionally add amigrations/1_initial_migration.js) then re-runtronbox compile.
node demo.jsExpected output (example):
Signature: 0x<r...s...v>
Local verify: true
On-chain verify: true
Local verify: true→ TronWeb recovered your signer address from the signature.On-chain verify: true→ the contract computed the same TIP-712 digest andecrecovermatched the expected signer.
- Contract deploy creates a domain separator from
(NAME, VERSION, uint32(chainId), address(this))and stores it immutably. - demo.js builds the same domain, defines types and value, and calls
_signTypedDatato sign. - demo.js normalizes the signature
vbyte to1b/1c(27/28) for broad compatibility. - demo.js checks locally with
verifyTypedData(...)(no RPC required). - demo.js calls the contract’s
verify(...)with:Mailas tuples:[[from.name, from.wallet], [to.name, to.wallet], contents, nonce]expectedSigner(your T-address)signature
- The contract reconstructs the same TIP-712 digest and runs
ecrecover(...)to confirm the signer.
-
Artifacts not found (
Tip712Verifier.json)
Make sure you runnode demo.jsfrom the project root wherebuild/contracts/Tip712Verifier.jsonexists (created by TronBox). The script usesprocess.cwd()to load the artifact. -
Could not find artifacts for ./Migrations
Addcontracts/Migrations.sol(and optionallymigrations/1_initial_migration.js), thentronbox compile. -
Local verify: false
Domain or message mismatch. Ensurename,version,verifyingContract, and 32-bitchainIdmatch between JS and Solidity; also checknonceand string content. -
On-chain verify: false
Same as above plus make sure theMailstruct is passed as tuples in the declared order. -
Rate limits / RPC errors
Nile endpoint is public; mainnet often needs a TronGrid API key (TRON_PRO_API_KEY).
- To port this to mainnet, set
FULL_HOST=https://api.trongrid.ioand usechainId=0x2b6653dcin your domain. - You can wire
verify(...)into state-changing functions (e.g., “if valid signature, then mint/transfer/execute”). - TIP-712 also defines an atomic
trcTokentype (not used in this demo).
MIT