From 86e4377797875390a1eff267baff4fe36d46800d Mon Sep 17 00:00:00 2001 From: Kostadin Madzherski Date: Wed, 27 May 2026 19:16:54 +0300 Subject: [PATCH 01/20] Add TRON support to Contracts Wizard Introduces a TRON variant of the Solidity Wizard, targeting the TRON Virtual Machine via @openzeppelin/hardhat-tron and the @openzeppelin/tron-contracts library. What's included: - New top-level "TRON" tab in the UI nav (wizard URL: /tron) - New core utility `rewriteForTron` that maps ERC20/721/1155/4626 to TRC*, rewrites @openzeppelin/contracts paths to @openzeppelin/tron-contracts, and caps pragma at the tron-solc maximum (^0.8.26). - New `zip-hardhat-tron` generator emitting an @openzeppelin/hardhat-tron project (TRE network, tron-solc 0.8.26 + cancun + viaIR, plain ethers deploy script). - New `zip-tronbox` generator emitting a TronBox project with migrations and a Mocha test, in place of the Foundry download for TRON. - "Open in Remix" enabled for TRON with an @openzeppelin/tron-contracts remappings override (TVM is EVM-compatible enough for Remix). - Account tab and superchain cross-chain bridging omitted on TRON (no EntryPoint deployment / OP-stack pattern). - Per-tab labels overridable via `tabLabels` (TRC20/TRC721/TRC1155 on TRON; ERC* unchanged elsewhere). Tests: 15 new ava tests (rewriteForTron unit + hardhat-tron snapshot + tronbox snapshot). svelte-check passes on the UI package. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../environments/hardhat/tron/package.json | 19 + packages/core/solidity/src/index.ts | 1 + .../solidity/src/utils/transform-tron.test.ts | 123 +++ .../core/solidity/src/utils/transform-tron.ts | 46 + .../solidity/src/zip-hardhat-tron.test.ts | 115 +++ .../solidity/src/zip-hardhat-tron.test.ts.md | 693 +++++++++++++++ .../src/zip-hardhat-tron.test.ts.snap | Bin 0 -> 3291 bytes .../core/solidity/src/zip-hardhat-tron.ts | 165 ++++ .../core/solidity/src/zip-tronbox.test.ts | 113 +++ .../core/solidity/src/zip-tronbox.test.ts.md | 835 ++++++++++++++++++ .../solidity/src/zip-tronbox.test.ts.snap | Bin 0 -> 2984 bytes packages/core/solidity/src/zip-tronbox.ts | 284 ++++++ .../core/solidity/zip-env-hardhat-tron.js | 1 + .../core/solidity/zip-env-hardhat-tron.ts | 1 + packages/core/solidity/zip-env-tronbox.js | 1 + packages/core/solidity/zip-env-tronbox.ts | 1 + .../ai-assistant/function-definitions/tron.ts | 20 + .../ui/api/ai-assistant/types/languages.ts | 1 + packages/ui/api/ai.ts | 2 + packages/ui/public/cairo.html | 1 + packages/ui/public/confidential.html | 1 + packages/ui/public/icons/tron.svg | 5 + packages/ui/public/icons/tron_active.svg | 5 + packages/ui/public/index.html | 1 + packages/ui/public/polkadot.html | 1 + packages/ui/public/stellar.html | 1 + packages/ui/public/stylus.html | 1 + packages/ui/public/tron.html | 103 +++ packages/ui/public/uniswap-hooks.html | 1 + packages/ui/src/common/languages-types.ts | 1 + packages/ui/src/common/post-config.ts | 1 + packages/ui/src/main.ts | 23 +- packages/ui/src/solidity/App.svelte | 41 +- packages/ui/src/solidity/overrides.ts | 61 ++ packages/ui/src/standalone.css | 4 + packages/ui/src/tron/App.svelte | 72 ++ .../src/tron/handle-unsupported-features.ts | 26 + 37 files changed, 2759 insertions(+), 11 deletions(-) create mode 100644 packages/core/solidity/src/environments/hardhat/tron/package.json create mode 100644 packages/core/solidity/src/utils/transform-tron.test.ts create mode 100644 packages/core/solidity/src/utils/transform-tron.ts create mode 100644 packages/core/solidity/src/zip-hardhat-tron.test.ts create mode 100644 packages/core/solidity/src/zip-hardhat-tron.test.ts.md create mode 100644 packages/core/solidity/src/zip-hardhat-tron.test.ts.snap create mode 100644 packages/core/solidity/src/zip-hardhat-tron.ts create mode 100644 packages/core/solidity/src/zip-tronbox.test.ts create mode 100644 packages/core/solidity/src/zip-tronbox.test.ts.md create mode 100644 packages/core/solidity/src/zip-tronbox.test.ts.snap create mode 100644 packages/core/solidity/src/zip-tronbox.ts create mode 100644 packages/core/solidity/zip-env-hardhat-tron.js create mode 100644 packages/core/solidity/zip-env-hardhat-tron.ts create mode 100644 packages/core/solidity/zip-env-tronbox.js create mode 100644 packages/core/solidity/zip-env-tronbox.ts create mode 100644 packages/ui/api/ai-assistant/function-definitions/tron.ts create mode 100644 packages/ui/public/icons/tron.svg create mode 100644 packages/ui/public/icons/tron_active.svg create mode 100644 packages/ui/public/tron.html create mode 100644 packages/ui/src/tron/App.svelte create mode 100644 packages/ui/src/tron/handle-unsupported-features.ts diff --git a/packages/core/solidity/src/environments/hardhat/tron/package.json b/packages/core/solidity/src/environments/hardhat/tron/package.json new file mode 100644 index 000000000..9378374a3 --- /dev/null +++ b/packages/core/solidity/src/environments/hardhat/tron/package.json @@ -0,0 +1,19 @@ +{ + "name": "hardhat-tron-sample", + "version": "0.0.1", + "description": "", + "main": "index.js", + "scripts": { + "test": "hardhat test" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", + "@nomicfoundation/hardhat-ethers": "^3.0.9", + "@openzeppelin/hardhat-tron": "^1.0.0", + "@openzeppelin/tron-contracts": "^0.1.0", + "ethers": "^6.14.0", + "hardhat": "^2.26.0" + } +} diff --git a/packages/core/solidity/src/index.ts b/packages/core/solidity/src/index.ts index 9d3231380..3c9c3c4fc 100644 --- a/packages/core/solidity/src/index.ts +++ b/packages/core/solidity/src/index.ts @@ -54,3 +54,4 @@ export { formatLinesWithSpaces, spaceBetween } from './utils/format-lines'; export { findCover } from './utils/find-cover'; export type { PremintCalculation } from './erc20'; export { calculatePremint as calculateERC20Premint, scaleByPowerOfTen } from './erc20'; +export { rewriteForTron } from './utils/transform-tron'; diff --git a/packages/core/solidity/src/utils/transform-tron.test.ts b/packages/core/solidity/src/utils/transform-tron.test.ts new file mode 100644 index 000000000..6f1c14945 --- /dev/null +++ b/packages/core/solidity/src/utils/transform-tron.test.ts @@ -0,0 +1,123 @@ +import test from 'ava'; +import { rewriteForTron } from './transform-tron'; + +test('rewrites @openzeppelin/contracts path root', t => { + t.is( + rewriteForTron('import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";'), + 'import {Ownable} from "@openzeppelin/tron-contracts/access/Ownable.sol";', + ); +}); + +test('rewrites token directory names', t => { + t.is( + rewriteForTron('import "@openzeppelin/contracts/token/ERC20/ERC20.sol";'), + 'import "@openzeppelin/tron-contracts/token/TRC20/TRC20.sol";', + ); + t.is( + rewriteForTron('import "@openzeppelin/contracts/token/ERC721/ERC721.sol";'), + 'import "@openzeppelin/tron-contracts/token/TRC721/TRC721.sol";', + ); + t.is( + rewriteForTron('import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";'), + 'import "@openzeppelin/tron-contracts/token/TRC1155/TRC1155.sol";', + ); +}); + +test('rewrites token symbols and interfaces', t => { + t.is(rewriteForTron('contract Foo is ERC20, ERC20Burnable {}'), 'contract Foo is TRC20, TRC20Burnable {}'); + t.is(rewriteForTron('contract Foo is ERC721Pausable {}'), 'contract Foo is TRC721Pausable {}'); + t.is(rewriteForTron('contract Foo is ERC1155Supply {}'), 'contract Foo is TRC1155Supply {}'); + t.is(rewriteForTron('contract Foo is ERC4626 {}'), 'contract Foo is TRC4626 {}'); + t.is(rewriteForTron('IERC20 token; IERC721 nft;'), 'ITRC20 token; ITRC721 nft;'); +}); + +test('does NOT rewrite digit-adjacent unrelated standards', t => { + // These are real OZ filenames that must pass through untouched. + const samples = [ + 'import "@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol";', + 'import "@openzeppelin/contracts/token/common/ERC2981.sol";', + 'import "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol";', + 'import "@openzeppelin/contracts/token/ERC6909/ERC6909.sol";', + ]; + const rewritten = samples.map(rewriteForTron); + // Path root still rewrites; the symbols stay as-is. + t.is( + rewritten[0], + 'import "@openzeppelin/tron-contracts/token/TRC20/extensions/ERC1363.sol";', + 'ERC1363 must stay verbatim', + ); + t.is(rewritten[1], 'import "@openzeppelin/tron-contracts/token/common/ERC2981.sol";', 'ERC2981 must stay verbatim'); + t.is( + rewritten[2], + 'import "@openzeppelin/tron-contracts/account/utils/draft-ERC4337Utils.sol";', + 'ERC4337Utils must stay verbatim', + ); + // ERC6909 is NOT renamed in tron-contracts (only ERC20/721/1155/4626 are). + t.is(rewritten[3], 'import "@openzeppelin/tron-contracts/token/ERC6909/ERC6909.sol";', 'ERC6909 must stay verbatim'); +}); + +test('preserves the entire program when no TRON-relevant tokens present', t => { + // 0.8.20 is below the tron-solc cap, so it stays as-is. + const source = 'pragma solidity ^0.8.20;\ncontract Plain { uint256 x; }\n'; + t.is(rewriteForTron(source), source); +}); + +test('caps pragma at 0.8.26 (the current tron-solc maximum)', t => { + // Above the cap — downgraded. + t.is( + rewriteForTron('pragma solidity ^0.8.27;\ncontract Foo {}\n'), + 'pragma solidity ^0.8.26;\ncontract Foo {}\n', + ); + t.is( + rewriteForTron('pragma solidity ^0.8.30;\ncontract Foo {}\n'), + 'pragma solidity ^0.8.26;\ncontract Foo {}\n', + ); + // At the cap — unchanged. + t.is( + rewriteForTron('pragma solidity ^0.8.26;\ncontract Foo {}\n'), + 'pragma solidity ^0.8.26;\ncontract Foo {}\n', + ); + // Below the cap — unchanged. + t.is( + rewriteForTron('pragma solidity ^0.8.20;\ncontract Foo {}\n'), + 'pragma solidity ^0.8.20;\ncontract Foo {}\n', + ); +}); + +test('handles a realistic ERC20 wizard output', t => { + const input = `// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.0.0 +pragma solidity ^0.8.27; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyToken is ERC20, ERC20Burnable, Ownable, ERC20Permit { + constructor(address initialOwner) + ERC20("MyToken", "MTK") + Ownable(initialOwner) + ERC20Permit("MyToken") + {} +} +`; + const expected = `// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.0.0 +pragma solidity ^0.8.26; + +import {TRC20} from "@openzeppelin/tron-contracts/token/TRC20/TRC20.sol"; +import {TRC20Burnable} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Burnable.sol"; +import {TRC20Permit} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Permit.sol"; +import {Ownable} from "@openzeppelin/tron-contracts/access/Ownable.sol"; + +contract MyToken is TRC20, TRC20Burnable, Ownable, TRC20Permit { + constructor(address initialOwner) + TRC20("MyToken", "MTK") + Ownable(initialOwner) + TRC20Permit("MyToken") + {} +} +`; + t.is(rewriteForTron(input), expected); +}); diff --git a/packages/core/solidity/src/utils/transform-tron.ts b/packages/core/solidity/src/utils/transform-tron.ts new file mode 100644 index 000000000..fece5ce94 --- /dev/null +++ b/packages/core/solidity/src/utils/transform-tron.ts @@ -0,0 +1,46 @@ +// Post-process rewriter that adapts generated Solidity output for the +// TRON ecosystem. +// +// `@openzeppelin/tron-contracts` mirrors `@openzeppelin/contracts` with two +// systematic differences: +// +// 1. Path root: `@openzeppelin/contracts/...` -> `@openzeppelin/tron-contracts/...` +// 2. Token standards are renamed to the TRC family: +// ERC20 -> TRC20 (dir `token/ERC20/` -> `token/TRC20/`) +// ERC721 -> TRC721 (dir `token/ERC721/` -> `token/TRC721/`) +// ERC1155 -> TRC1155 (dir `token/ERC1155/` -> `token/TRC1155/`) +// ERC4626 -> TRC4626 (lives under `token/TRC20/extensions/`) +// +// Every other identifier (ERC1363, ERC2981, ERC4337, ERC6909, EIP712, +// Ownable, AccessControl, etc.) is kept verbatim, matching how +// `@openzeppelin/tron-contracts` ships those files. +// +// We also cap the `pragma solidity` version at the maximum that `tron-solc` +// supports. The mainline Wizard currently emits ^0.8.27, which tron-solc +// does not yet ship; the TVM Democritus hardfork targets 0.8.26 + cancun. + +// The maximum 0.8.x minor that tron-solc currently supports. +// Bump this when tron-solc catches up to the mainline Wizard's Solidity version. +const TRON_SOLC_MAX_MINOR = 26; + +const PATH_ROOT_PATTERN = /@openzeppelin\/contracts\//g; +const TOKEN_ERC20_DIR_PATTERN = /\/token\/ERC20\//g; +const TOKEN_ERC721_DIR_PATTERN = /\/token\/ERC721\//g; +const TOKEN_ERC1155_DIR_PATTERN = /\/token\/ERC1155\//g; +const INTERFACE_PATTERN = /\bIERC(20|721|1155|4626)/g; +const SYMBOL_PATTERN = /\bERC(20|721|1155|4626)/g; +const PRAGMA_PATTERN = /(pragma\s+solidity\s+\^0\.8\.)(\d+)(\s*;)/g; + +export function rewriteForTron(source: string): string { + return source + .replace(PATH_ROOT_PATTERN, '@openzeppelin/tron-contracts/') + .replace(TOKEN_ERC20_DIR_PATTERN, '/token/TRC20/') + .replace(TOKEN_ERC721_DIR_PATTERN, '/token/TRC721/') + .replace(TOKEN_ERC1155_DIR_PATTERN, '/token/TRC1155/') + .replace(INTERFACE_PATTERN, 'ITRC$1') + .replace(SYMBOL_PATTERN, 'TRC$1') + .replace(PRAGMA_PATTERN, (_match, prefix, minor, suffix) => { + const capped = Math.min(parseInt(minor, 10), TRON_SOLC_MAX_MINOR); + return `${prefix}${capped}${suffix}`; + }); +} diff --git a/packages/core/solidity/src/zip-hardhat-tron.test.ts b/packages/core/solidity/src/zip-hardhat-tron.test.ts new file mode 100644 index 000000000..7c340b4f1 --- /dev/null +++ b/packages/core/solidity/src/zip-hardhat-tron.test.ts @@ -0,0 +1,115 @@ +import type { ExecutionContext } from 'ava'; +import test from 'ava'; + +import { zipHardhatTron } from './zip-hardhat-tron'; + +import { buildERC20 } from './erc20'; +import { buildERC721 } from './erc721'; +import { buildERC1155 } from './erc1155'; +import type { Contract } from './contract'; +import type { JSZipObject } from 'jszip'; +import type JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; + +// The TRON download cannot run `npm install` end-to-end yet because +// @openzeppelin/hardhat-tron and @openzeppelin/tron-contracts are not +// published to npm at the time of this test. These tests therefore only +// verify the file layout and snapshot the contents. + +test.serial('erc20 basic - layout & contents', async t => { + const opts: GenericOptions = { + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + }; + const c = buildERC20(opts); + await runSnapshotTest(c, t, opts); +}); + +test.serial('erc20 full (mintable, pausable, permit, votes, flashmint)', async t => { + const opts: GenericOptions = { + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + premint: '2000', + access: 'roles', + burnable: true, + mintable: true, + pausable: true, + permit: true, + votes: true, + flashmint: true, + }; + const c = buildERC20(opts); + await runSnapshotTest(c, t, opts); +}); + +test.serial('erc721 basic', async t => { + const opts: GenericOptions = { + kind: 'ERC721', + name: 'My NFT', + symbol: 'MNFT', + }; + const c = buildERC721(opts); + await runSnapshotTest(c, t, opts); +}); + +test.serial('erc1155 basic', async t => { + const opts: GenericOptions = { + kind: 'ERC1155', + name: 'My Multi', + uri: 'ipfs://example/{id}', + }; + const c = buildERC1155(opts); + await runSnapshotTest(c, t, opts); +}); + +async function runSnapshotTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { + const zip = await zipHardhatTron(c, opts); + + assertLayout(zip, c, t); + await assertContents(zip, c, t); +} + +function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { + const sorted = Object.keys(zip.files).sort(); + t.deepEqual(sorted, [ + '.gitignore', + 'README.md', + 'contracts/', + `contracts/${c.name}.sol`, + 'hardhat.config.ts', + 'package.json', + 'scripts/', + 'scripts/deploy.ts', + 'test/', + 'test/test.ts', + 'tsconfig.json', + ]); +} + +async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { + const contentComparison = [ + await getItemString(zip, `contracts/${c.name}.sol`), + await getItemString(zip, 'hardhat.config.ts'), + await getItemString(zip, 'package.json'), + await getItemString(zip, 'scripts/deploy.ts'), + await getItemString(zip, 'test/test.ts'), + await getItemString(zip, 'README.md'), + await getItemString(zip, '.gitignore'), + ]; + + t.snapshot(contentComparison); +} + +async function getItemString(zip: JSZip, key: string) { + const obj = zip.files[key]; + if (obj === undefined) { + throw Error(`Item ${key} not found in zip`); + } + return `${key}:\n${await asString(obj)}`; +} + +async function asString(item: JSZipObject) { + return Buffer.from(await item.async('arraybuffer')).toString(); +} diff --git a/packages/core/solidity/src/zip-hardhat-tron.test.ts.md b/packages/core/solidity/src/zip-hardhat-tron.test.ts.md new file mode 100644 index 000000000..72711925f --- /dev/null +++ b/packages/core/solidity/src/zip-hardhat-tron.test.ts.md @@ -0,0 +1,693 @@ +# Snapshot report for `src/zip-hardhat-tron.test.ts` + +The actual snapshot is saved in `zip-hardhat-tron.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## erc20 basic - layout & contents + +> Snapshot 1 + + [ + `contracts/MyToken.sol:␊ + // SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.6.0␊ + pragma solidity ^0.8.26;␊ + ␊ + import {TRC20} from "@openzeppelin/tron-contracts/token/TRC20/TRC20.sol";␊ + import {TRC20Permit} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Permit.sol";␊ + ␊ + contract MyToken is TRC20, TRC20Permit {␊ + constructor() TRC20("My Token", "MTK") TRC20Permit("My Token") {}␊ + }␊ + `, + `hardhat.config.ts:␊ + import { HardhatUserConfig } from "hardhat/config";␊ + import "@nomicfoundation/hardhat-ethers";␊ + import "@nomicfoundation/hardhat-chai-matchers";␊ + import "@openzeppelin/hardhat-tron";␊ + ␊ + const config: HardhatUserConfig = {␊ + solidity: {␊ + version: "0.8.26",␊ + settings: {␊ + optimizer: { enabled: true, runs: 200 },␊ + evmVersion: 'cancun',␊ + viaIR: true,␊ + // Embed source as literal text in metadata so verification␊ + // services (Sourcify, etc.) can reconstruct it deterministically.␊ + metadata: { bytecodeHash: 'ipfs', useLiteralContent: true },␊ + },␊ + },␊ + tre: {␊ + autoStart: true,␊ + image: 'tronbox/tre:dev',␊ + compiler: { target: 'tron' },␊ + },␊ + defaultNetwork: 'tre',␊ + networks: {␊ + tre: {␊ + url: process.env.TRE_URL || 'http://127.0.0.1:9090/jsonrpc',␊ + tron: true,␊ + // Default well-known TRE dev key — fine for local tests, NEVER use on a real network.␊ + accounts: [process.env.TRE_PRIVATE_KEY || '0xdd23ca549a97cb330b011aebb674730df8b14acaee42d211ab45692699ab8ba5'],␊ + },␊ + },␊ + };␊ + ␊ + export default config;␊ + `, + `package.json:␊ + {␊ + "name": "hardhat-tron-sample",␊ + "version": "0.0.1",␊ + "description": "",␊ + "main": "index.js",␊ + "scripts": {␊ + "test": "hardhat test"␊ + },␊ + "author": "",␊ + "license": "MIT",␊ + "devDependencies": {␊ + "@nomicfoundation/hardhat-chai-matchers": "^2.0.6",␊ + "@nomicfoundation/hardhat-ethers": "^3.0.9",␊ + "@openzeppelin/hardhat-tron": "^1.0.0",␊ + "@openzeppelin/tron-contracts": "^0.1.0",␊ + "ethers": "^6.14.0",␊ + "hardhat": "^2.26.0"␊ + }␊ + }`, + `scripts/deploy.ts:␊ + import { ethers } from "hardhat";␊ + ␊ + async function main() {␊ + const ContractFactory = await ethers.getContractFactory("MyToken");␊ + ␊ + ␊ + const instance = await ContractFactory.deploy();␊ + await instance.waitForDeployment();␊ + ␊ + console.log(\`Contract deployed to ${await instance.getAddress()}\`);␊ + }␊ + ␊ + // We recommend this pattern to be able to use async/await everywhere␊ + // and properly handle errors.␊ + main().catch((error) => {␊ + console.error(error);␊ + process.exitCode = 1;␊ + });␊ + `, + `test/test.ts:␊ + import { expect } from "chai";␊ + import { ethers } from "hardhat";␊ + ␊ + describe("MyToken", function () {␊ + it("Test contract", async function () {␊ + const ContractFactory = await ethers.getContractFactory("MyToken");␊ + ␊ + const instance = await ContractFactory.deploy();␊ + await instance.waitForDeployment();␊ + ␊ + expect(await instance.name()).to.equal("My Token");␊ + });␊ + });␊ + `, + `README.md:␊ + # Sample TRON Hardhat Project (MyToken)␊ + ␊ + This project demonstrates a TRON-targeted Hardhat use case using \`@openzeppelin/hardhat-tron\`. It comes with the \`MyToken\` contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, and a deploy script.␊ + ␊ + ## Prerequisites␊ + ␊ + Ensure you have the following installed:␊ + - [Node.js 20+](https://nodejs.org/en/download/)␊ + - [Docker](https://docs.docker.com/get-docker/) — the TRON Runtime Environment (\`tronbox/tre:dev\`) is spawned as a Docker container by \`@openzeppelin/hardhat-tron\`.␊ + ␊ + ## Installing dependencies␊ + ␊ + \`\`\`␊ + npm install␊ + \`\`\`␊ + ␊ + ## TRON Runtime Environment␊ + ␊ + \`@openzeppelin/hardhat-tron\` spawns a local \`java-tron\` node (TRE) in Docker the first time you run \`npx hardhat test\` and tears it down on exit. No manual setup is required.␊ + ␊ + ## Testing the contract␊ + ␊ + \`\`\`␊ + npm test␊ + \`\`\`␊ + ␊ + ## Deploying the contract␊ + ␊ + \`\`\`␊ + npx hardhat run --network tre scripts/deploy.ts␊ + \`\`\`␊ + ␊ + The default \`tre\` network in \`hardhat.config.ts\` points at a local TRON Runtime Environment (Docker container, spawned automatically by \`@openzeppelin/hardhat-tron\`). For Shasta, Nile, or mainnet, add a network entry and pass \`--network \`.␊ + `, + `.gitignore:␊ + node_modules␊ + .env␊ + coverage␊ + coverage.json␊ + typechain␊ + typechain-types␊ + ␊ + # Hardhat files␊ + cache␊ + artifacts␊ + `, + ] + +## erc20 full (mintable, pausable, permit, votes, flashmint) + +> Snapshot 1 + + [ + `contracts/MyToken.sol:␊ + // SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.6.0␊ + pragma solidity ^0.8.26;␊ + ␊ + import {AccessControl} from "@openzeppelin/tron-contracts/access/AccessControl.sol";␊ + import {TRC20} from "@openzeppelin/tron-contracts/token/TRC20/TRC20.sol";␊ + import {TRC20Burnable} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Burnable.sol";␊ + import {TRC20FlashMint} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20FlashMint.sol";␊ + import {TRC20Pausable} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Pausable.sol";␊ + import {TRC20Permit} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Permit.sol";␊ + import {TRC20Votes} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Votes.sol";␊ + import {Nonces} from "@openzeppelin/tron-contracts/utils/Nonces.sol";␊ + ␊ + contract MyToken is TRC20, TRC20Burnable, TRC20Pausable, AccessControl, TRC20Permit, TRC20Votes, TRC20FlashMint {␊ + bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊ + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");␊ + ␊ + constructor(address recipient, address defaultAdmin, address pauser, address minter)␊ + TRC20("My Token", "MTK")␊ + TRC20Permit("My Token")␊ + {␊ + _mint(recipient, 2000 * 10 ** decimals());␊ + _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);␊ + _grantRole(PAUSER_ROLE, pauser);␊ + _grantRole(MINTER_ROLE, minter);␊ + }␊ + ␊ + function pause() public onlyRole(PAUSER_ROLE) {␊ + _pause();␊ + }␊ + ␊ + function unpause() public onlyRole(PAUSER_ROLE) {␊ + _unpause();␊ + }␊ + ␊ + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {␊ + _mint(to, amount);␊ + }␊ + ␊ + // The following functions are overrides required by Solidity.␊ + ␊ + function _update(address from, address to, uint256 value)␊ + internal␊ + override(TRC20, TRC20Pausable, TRC20Votes)␊ + {␊ + super._update(from, to, value);␊ + }␊ + ␊ + function nonces(address owner)␊ + public␊ + view␊ + override(TRC20Permit, Nonces)␊ + returns (uint256)␊ + {␊ + return super.nonces(owner);␊ + }␊ + }␊ + `, + `hardhat.config.ts:␊ + import { HardhatUserConfig } from "hardhat/config";␊ + import "@nomicfoundation/hardhat-ethers";␊ + import "@nomicfoundation/hardhat-chai-matchers";␊ + import "@openzeppelin/hardhat-tron";␊ + ␊ + const config: HardhatUserConfig = {␊ + solidity: {␊ + version: "0.8.26",␊ + settings: {␊ + optimizer: { enabled: true, runs: 200 },␊ + evmVersion: 'cancun',␊ + viaIR: true,␊ + // Embed source as literal text in metadata so verification␊ + // services (Sourcify, etc.) can reconstruct it deterministically.␊ + metadata: { bytecodeHash: 'ipfs', useLiteralContent: true },␊ + },␊ + },␊ + tre: {␊ + autoStart: true,␊ + image: 'tronbox/tre:dev',␊ + compiler: { target: 'tron' },␊ + },␊ + defaultNetwork: 'tre',␊ + networks: {␊ + tre: {␊ + url: process.env.TRE_URL || 'http://127.0.0.1:9090/jsonrpc',␊ + tron: true,␊ + // Default well-known TRE dev key — fine for local tests, NEVER use on a real network.␊ + accounts: [process.env.TRE_PRIVATE_KEY || '0xdd23ca549a97cb330b011aebb674730df8b14acaee42d211ab45692699ab8ba5'],␊ + },␊ + },␊ + };␊ + ␊ + export default config;␊ + `, + `package.json:␊ + {␊ + "name": "hardhat-tron-sample",␊ + "version": "0.0.1",␊ + "description": "",␊ + "main": "index.js",␊ + "scripts": {␊ + "test": "hardhat test"␊ + },␊ + "author": "",␊ + "license": "MIT",␊ + "devDependencies": {␊ + "@nomicfoundation/hardhat-chai-matchers": "^2.0.6",␊ + "@nomicfoundation/hardhat-ethers": "^3.0.9",␊ + "@openzeppelin/hardhat-tron": "^1.0.0",␊ + "@openzeppelin/tron-contracts": "^0.1.0",␊ + "ethers": "^6.14.0",␊ + "hardhat": "^2.26.0"␊ + }␊ + }`, + `scripts/deploy.ts:␊ + import { ethers } from "hardhat";␊ + ␊ + async function main() {␊ + const ContractFactory = await ethers.getContractFactory("MyToken");␊ + ␊ + // TODO: Set values for the constructor arguments below␊ + const instance = await ContractFactory.deploy(recipient, defaultAdmin, pauser, minter);␊ + await instance.waitForDeployment();␊ + ␊ + console.log(\`Contract deployed to ${await instance.getAddress()}\`);␊ + }␊ + ␊ + // We recommend this pattern to be able to use async/await everywhere␊ + // and properly handle errors.␊ + main().catch((error) => {␊ + console.error(error);␊ + process.exitCode = 1;␊ + });␊ + `, + `test/test.ts:␊ + import { expect } from "chai";␊ + import { ethers } from "hardhat";␊ + ␊ + describe("MyToken", function () {␊ + it("Test contract", async function () {␊ + const ContractFactory = await ethers.getContractFactory("MyToken");␊ + ␊ + const recipient = (await ethers.getSigners())[0].address;␊ + const defaultAdmin = (await ethers.getSigners())[1].address;␊ + const pauser = (await ethers.getSigners())[2].address;␊ + const minter = (await ethers.getSigners())[3].address;␊ + ␊ + const instance = await ContractFactory.deploy(recipient, defaultAdmin, pauser, minter);␊ + await instance.waitForDeployment();␊ + ␊ + expect(await instance.name()).to.equal("My Token");␊ + });␊ + });␊ + `, + `README.md:␊ + # Sample TRON Hardhat Project (MyToken)␊ + ␊ + This project demonstrates a TRON-targeted Hardhat use case using \`@openzeppelin/hardhat-tron\`. It comes with the \`MyToken\` contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, and a deploy script.␊ + ␊ + ## Prerequisites␊ + ␊ + Ensure you have the following installed:␊ + - [Node.js 20+](https://nodejs.org/en/download/)␊ + - [Docker](https://docs.docker.com/get-docker/) — the TRON Runtime Environment (\`tronbox/tre:dev\`) is spawned as a Docker container by \`@openzeppelin/hardhat-tron\`.␊ + ␊ + ## Installing dependencies␊ + ␊ + \`\`\`␊ + npm install␊ + \`\`\`␊ + ␊ + ## TRON Runtime Environment␊ + ␊ + \`@openzeppelin/hardhat-tron\` spawns a local \`java-tron\` node (TRE) in Docker the first time you run \`npx hardhat test\` and tears it down on exit. No manual setup is required.␊ + ␊ + ## Testing the contract␊ + ␊ + \`\`\`␊ + npm test␊ + \`\`\`␊ + ␊ + ## Deploying the contract␊ + ␊ + \`\`\`␊ + npx hardhat run --network tre scripts/deploy.ts␊ + \`\`\`␊ + ␊ + The default \`tre\` network in \`hardhat.config.ts\` points at a local TRON Runtime Environment (Docker container, spawned automatically by \`@openzeppelin/hardhat-tron\`). For Shasta, Nile, or mainnet, add a network entry and pass \`--network \`.␊ + `, + `.gitignore:␊ + node_modules␊ + .env␊ + coverage␊ + coverage.json␊ + typechain␊ + typechain-types␊ + ␊ + # Hardhat files␊ + cache␊ + artifacts␊ + `, + ] + +## erc721 basic + +> Snapshot 1 + + [ + `contracts/MyNFT.sol:␊ + // SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.6.0␊ + pragma solidity ^0.8.26;␊ + ␊ + import {TRC721} from "@openzeppelin/tron-contracts/token/TRC721/TRC721.sol";␊ + ␊ + contract MyNFT is TRC721 {␊ + constructor() TRC721("My NFT", "MNFT") {}␊ + }␊ + `, + `hardhat.config.ts:␊ + import { HardhatUserConfig } from "hardhat/config";␊ + import "@nomicfoundation/hardhat-ethers";␊ + import "@nomicfoundation/hardhat-chai-matchers";␊ + import "@openzeppelin/hardhat-tron";␊ + ␊ + const config: HardhatUserConfig = {␊ + solidity: {␊ + version: "0.8.26",␊ + settings: {␊ + optimizer: { enabled: true, runs: 200 },␊ + evmVersion: 'cancun',␊ + viaIR: true,␊ + // Embed source as literal text in metadata so verification␊ + // services (Sourcify, etc.) can reconstruct it deterministically.␊ + metadata: { bytecodeHash: 'ipfs', useLiteralContent: true },␊ + },␊ + },␊ + tre: {␊ + autoStart: true,␊ + image: 'tronbox/tre:dev',␊ + compiler: { target: 'tron' },␊ + },␊ + defaultNetwork: 'tre',␊ + networks: {␊ + tre: {␊ + url: process.env.TRE_URL || 'http://127.0.0.1:9090/jsonrpc',␊ + tron: true,␊ + // Default well-known TRE dev key — fine for local tests, NEVER use on a real network.␊ + accounts: [process.env.TRE_PRIVATE_KEY || '0xdd23ca549a97cb330b011aebb674730df8b14acaee42d211ab45692699ab8ba5'],␊ + },␊ + },␊ + };␊ + ␊ + export default config;␊ + `, + `package.json:␊ + {␊ + "name": "hardhat-tron-sample",␊ + "version": "0.0.1",␊ + "description": "",␊ + "main": "index.js",␊ + "scripts": {␊ + "test": "hardhat test"␊ + },␊ + "author": "",␊ + "license": "MIT",␊ + "devDependencies": {␊ + "@nomicfoundation/hardhat-chai-matchers": "^2.0.6",␊ + "@nomicfoundation/hardhat-ethers": "^3.0.9",␊ + "@openzeppelin/hardhat-tron": "^1.0.0",␊ + "@openzeppelin/tron-contracts": "^0.1.0",␊ + "ethers": "^6.14.0",␊ + "hardhat": "^2.26.0"␊ + }␊ + }`, + `scripts/deploy.ts:␊ + import { ethers } from "hardhat";␊ + ␊ + async function main() {␊ + const ContractFactory = await ethers.getContractFactory("MyNFT");␊ + ␊ + ␊ + const instance = await ContractFactory.deploy();␊ + await instance.waitForDeployment();␊ + ␊ + console.log(\`Contract deployed to ${await instance.getAddress()}\`);␊ + }␊ + ␊ + // We recommend this pattern to be able to use async/await everywhere␊ + // and properly handle errors.␊ + main().catch((error) => {␊ + console.error(error);␊ + process.exitCode = 1;␊ + });␊ + `, + `test/test.ts:␊ + import { expect } from "chai";␊ + import { ethers } from "hardhat";␊ + ␊ + describe("MyNFT", function () {␊ + it("Test contract", async function () {␊ + const ContractFactory = await ethers.getContractFactory("MyNFT");␊ + ␊ + const instance = await ContractFactory.deploy();␊ + await instance.waitForDeployment();␊ + ␊ + expect(await instance.name()).to.equal("My NFT");␊ + });␊ + });␊ + `, + `README.md:␊ + # Sample TRON Hardhat Project (MyNFT)␊ + ␊ + This project demonstrates a TRON-targeted Hardhat use case using \`@openzeppelin/hardhat-tron\`. It comes with the \`MyNFT\` contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, and a deploy script.␊ + ␊ + ## Prerequisites␊ + ␊ + Ensure you have the following installed:␊ + - [Node.js 20+](https://nodejs.org/en/download/)␊ + - [Docker](https://docs.docker.com/get-docker/) — the TRON Runtime Environment (\`tronbox/tre:dev\`) is spawned as a Docker container by \`@openzeppelin/hardhat-tron\`.␊ + ␊ + ## Installing dependencies␊ + ␊ + \`\`\`␊ + npm install␊ + \`\`\`␊ + ␊ + ## TRON Runtime Environment␊ + ␊ + \`@openzeppelin/hardhat-tron\` spawns a local \`java-tron\` node (TRE) in Docker the first time you run \`npx hardhat test\` and tears it down on exit. No manual setup is required.␊ + ␊ + ## Testing the contract␊ + ␊ + \`\`\`␊ + npm test␊ + \`\`\`␊ + ␊ + ## Deploying the contract␊ + ␊ + \`\`\`␊ + npx hardhat run --network tre scripts/deploy.ts␊ + \`\`\`␊ + ␊ + The default \`tre\` network in \`hardhat.config.ts\` points at a local TRON Runtime Environment (Docker container, spawned automatically by \`@openzeppelin/hardhat-tron\`). For Shasta, Nile, or mainnet, add a network entry and pass \`--network \`.␊ + `, + `.gitignore:␊ + node_modules␊ + .env␊ + coverage␊ + coverage.json␊ + typechain␊ + typechain-types␊ + ␊ + # Hardhat files␊ + cache␊ + artifacts␊ + `, + ] + +## erc1155 basic + +> Snapshot 1 + + [ + `contracts/MyMulti.sol:␊ + // SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.6.0␊ + pragma solidity ^0.8.26;␊ + ␊ + import {Ownable} from "@openzeppelin/tron-contracts/access/Ownable.sol";␊ + import {TRC1155} from "@openzeppelin/tron-contracts/token/TRC1155/TRC1155.sol";␊ + ␊ + contract MyMulti is TRC1155, Ownable {␊ + constructor(address initialOwner)␊ + TRC1155("ipfs://example/{id}")␊ + Ownable(initialOwner)␊ + {}␊ + ␊ + function setURI(string memory newuri) public onlyOwner {␊ + _setURI(newuri);␊ + }␊ + }␊ + `, + `hardhat.config.ts:␊ + import { HardhatUserConfig } from "hardhat/config";␊ + import "@nomicfoundation/hardhat-ethers";␊ + import "@nomicfoundation/hardhat-chai-matchers";␊ + import "@openzeppelin/hardhat-tron";␊ + ␊ + const config: HardhatUserConfig = {␊ + solidity: {␊ + version: "0.8.26",␊ + settings: {␊ + optimizer: { enabled: true, runs: 200 },␊ + evmVersion: 'cancun',␊ + viaIR: true,␊ + // Embed source as literal text in metadata so verification␊ + // services (Sourcify, etc.) can reconstruct it deterministically.␊ + metadata: { bytecodeHash: 'ipfs', useLiteralContent: true },␊ + },␊ + },␊ + tre: {␊ + autoStart: true,␊ + image: 'tronbox/tre:dev',␊ + compiler: { target: 'tron' },␊ + },␊ + defaultNetwork: 'tre',␊ + networks: {␊ + tre: {␊ + url: process.env.TRE_URL || 'http://127.0.0.1:9090/jsonrpc',␊ + tron: true,␊ + // Default well-known TRE dev key — fine for local tests, NEVER use on a real network.␊ + accounts: [process.env.TRE_PRIVATE_KEY || '0xdd23ca549a97cb330b011aebb674730df8b14acaee42d211ab45692699ab8ba5'],␊ + },␊ + },␊ + };␊ + ␊ + export default config;␊ + `, + `package.json:␊ + {␊ + "name": "hardhat-tron-sample",␊ + "version": "0.0.1",␊ + "description": "",␊ + "main": "index.js",␊ + "scripts": {␊ + "test": "hardhat test"␊ + },␊ + "author": "",␊ + "license": "MIT",␊ + "devDependencies": {␊ + "@nomicfoundation/hardhat-chai-matchers": "^2.0.6",␊ + "@nomicfoundation/hardhat-ethers": "^3.0.9",␊ + "@openzeppelin/hardhat-tron": "^1.0.0",␊ + "@openzeppelin/tron-contracts": "^0.1.0",␊ + "ethers": "^6.14.0",␊ + "hardhat": "^2.26.0"␊ + }␊ + }`, + `scripts/deploy.ts:␊ + import { ethers } from "hardhat";␊ + ␊ + async function main() {␊ + const ContractFactory = await ethers.getContractFactory("MyMulti");␊ + ␊ + // TODO: Set values for the constructor arguments below␊ + const instance = await ContractFactory.deploy(initialOwner);␊ + await instance.waitForDeployment();␊ + ␊ + console.log(\`Contract deployed to ${await instance.getAddress()}\`);␊ + }␊ + ␊ + // We recommend this pattern to be able to use async/await everywhere␊ + // and properly handle errors.␊ + main().catch((error) => {␊ + console.error(error);␊ + process.exitCode = 1;␊ + });␊ + `, + `test/test.ts:␊ + import { expect } from "chai";␊ + import { ethers } from "hardhat";␊ + ␊ + describe("MyMulti", function () {␊ + it("Test contract", async function () {␊ + const ContractFactory = await ethers.getContractFactory("MyMulti");␊ + ␊ + const initialOwner = (await ethers.getSigners())[0].address;␊ + ␊ + const instance = await ContractFactory.deploy(initialOwner);␊ + await instance.waitForDeployment();␊ + ␊ + expect(await instance.uri(0)).to.equal("ipfs://example/{id}");␊ + });␊ + });␊ + `, + `README.md:␊ + # Sample TRON Hardhat Project (MyMulti)␊ + ␊ + This project demonstrates a TRON-targeted Hardhat use case using \`@openzeppelin/hardhat-tron\`. It comes with the \`MyMulti\` contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, and a deploy script.␊ + ␊ + ## Prerequisites␊ + ␊ + Ensure you have the following installed:␊ + - [Node.js 20+](https://nodejs.org/en/download/)␊ + - [Docker](https://docs.docker.com/get-docker/) — the TRON Runtime Environment (\`tronbox/tre:dev\`) is spawned as a Docker container by \`@openzeppelin/hardhat-tron\`.␊ + ␊ + ## Installing dependencies␊ + ␊ + \`\`\`␊ + npm install␊ + \`\`\`␊ + ␊ + ## TRON Runtime Environment␊ + ␊ + \`@openzeppelin/hardhat-tron\` spawns a local \`java-tron\` node (TRE) in Docker the first time you run \`npx hardhat test\` and tears it down on exit. No manual setup is required.␊ + ␊ + ## Testing the contract␊ + ␊ + \`\`\`␊ + npm test␊ + \`\`\`␊ + ␊ + ## Deploying the contract␊ + ␊ + \`\`\`␊ + npx hardhat run --network tre scripts/deploy.ts␊ + \`\`\`␊ + ␊ + The default \`tre\` network in \`hardhat.config.ts\` points at a local TRON Runtime Environment (Docker container, spawned automatically by \`@openzeppelin/hardhat-tron\`). For Shasta, Nile, or mainnet, add a network entry and pass \`--network \`.␊ + `, + `.gitignore:␊ + node_modules␊ + .env␊ + coverage␊ + coverage.json␊ + typechain␊ + typechain-types␊ + ␊ + # Hardhat files␊ + cache␊ + artifacts␊ + `, + ] diff --git a/packages/core/solidity/src/zip-hardhat-tron.test.ts.snap b/packages/core/solidity/src/zip-hardhat-tron.test.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..9c15d84457b6e580ad14dc4e54b2efd2aee46f7a GIT binary patch literal 3291 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 ZDdjz-yr-1+l+M1V^j{>(y#O9g007hvH>Usq literal 0 HcmV?d00001 diff --git a/packages/core/solidity/src/zip-hardhat-tron.ts b/packages/core/solidity/src/zip-hardhat-tron.ts new file mode 100644 index 000000000..464120c6f --- /dev/null +++ b/packages/core/solidity/src/zip-hardhat-tron.ts @@ -0,0 +1,165 @@ +import JSZip from 'jszip'; +import type { Contract } from './contract'; +import { HardhatZipGenerator } from './zip-hardhat'; +import type { GenericOptions } from './build-generic'; +import { printContract } from './print'; +import { rewriteForTron } from './utils/transform-tron'; + +// Solidity version used by @openzeppelin/hardhat-tron and openzeppelin/tron-contracts. +// `tron-solc` 0.8.26 + cancun + viaIR is what the TRON Democritus hardfork +// (post-GreatVoyage 4.7) targets, and matches the README of OpenZeppelin/hardhat-tron. +const TRON_SOLIDITY_VERSION = '0.8.26'; + +class HardhatTronZipGenerator extends HardhatZipGenerator { + protected getAdditionalHardhatImports(): string[] { + // hardhat-tron does NOT use @nomicfoundation/hardhat-toolbox; the plugin + // composes the smaller ethers + chai-matchers plugins that it actually needs. + return ['@nomicfoundation/hardhat-ethers', '@nomicfoundation/hardhat-chai-matchers', '@openzeppelin/hardhat-tron']; + } + + protected getHardhatConfigJsonString(): string { + return `\ +{ + solidity: { + version: "${TRON_SOLIDITY_VERSION}", + settings: { + optimizer: { enabled: true, runs: 200 }, + evmVersion: 'cancun', + viaIR: true, + // Embed source as literal text in metadata so verification + // services (Sourcify, etc.) can reconstruct it deterministically. + metadata: { bytecodeHash: 'ipfs', useLiteralContent: true }, + }, + }, + tre: { + autoStart: true, + image: 'tronbox/tre:dev', + compiler: { target: 'tron' }, + }, + defaultNetwork: 'tre', + networks: { + tre: { + url: process.env.TRE_URL || 'http://127.0.0.1:9090/jsonrpc', + tron: true, + // Default well-known TRE dev key — fine for local tests, NEVER use on a real network. + accounts: [process.env.TRE_PRIVATE_KEY || '0xdd23ca549a97cb330b011aebb674730df8b14acaee42d211ab45692699ab8ba5'], + }, + }, +}`; + } + + protected getHardhatConfig(_upgradeable: boolean): string { + // hardhat-tron-based projects use a non-upgradeable single config; the + // `@openzeppelin/hardhat-upgrades` plugin is not used here. The hardhat + // config is also emitted as a CommonJS .cjs file in the README sample, + // but the TypeScript .ts variant works just as well with hardhat-tron. + const additionalImports = this.getAdditionalHardhatImports(); + const importsSection = additionalImports.map(imp => `import "${imp}";`).join('\n'); + + return `\ +import { HardhatUserConfig } from "hardhat/config"; +${importsSection} + +const config: HardhatUserConfig = ${this.getHardhatConfigJsonString()}; + +export default config; +`; + } + + protected async getPackageJson(c: Contract): Promise { + const { default: packageJson } = await import('./environments/hardhat/tron/package.json'); + packageJson.license = c.license; + return packageJson; + } + + protected async getPackageLock(_c: Contract): Promise { + // Not used. The TRON variant skips emitting a package-lock.json because + // @openzeppelin/hardhat-tron and @openzeppelin/tron-contracts are not yet + // published to the npm registry, so a lockfile would dangle. `npm install` + // resolves the dependencies fresh when the packages are available. + return undefined; + } + + protected getReadmePrerequisitesSection(): string { + return `\ +## Prerequisites + +Ensure you have the following installed: +- [Node.js 20+](https://nodejs.org/en/download/) +- [Docker](https://docs.docker.com/get-docker/) — the TRON Runtime Environment (\`tronbox/tre:dev\`) is spawned as a Docker container by \`@openzeppelin/hardhat-tron\`. + +`; + } + + protected getReadmeTestingEnvironmentSetupSection(): string { + return `\ +## TRON Runtime Environment + +\`@openzeppelin/hardhat-tron\` spawns a local \`java-tron\` node (TRE) in Docker the first time you run \`npx hardhat test\` and tears it down on exit. No manual setup is required. + +`; + } + + protected getGitIgnoreHardhatIgnition(): string { + // hardhat-ignition is not in scope for the TRON variant — we emit a plain + // ethers script instead. Nothing to gitignore here. + return ''; + } + + protected getPrintContract(c: Contract): string { + return rewriteForTron(printContract(c)); + } + + protected getReadme(c: Contract): string { + return `\ +# Sample TRON Hardhat Project (${c.name}) + +This project demonstrates a TRON-targeted Hardhat use case using \`@openzeppelin/hardhat-tron\`. It comes with the \`${c.name}\` contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, and a deploy script. + +${this.getReadmePrerequisitesSection()}## Installing dependencies + +\`\`\` +npm install +\`\`\` + +${this.getReadmeTestingEnvironmentSetupSection()}## Testing the contract + +\`\`\` +npm test +\`\`\` + +## Deploying the contract + +\`\`\` +npx hardhat run --network tre scripts/deploy.ts +\`\`\` + +The default \`tre\` network in \`hardhat.config.ts\` points at a local TRON Runtime Environment (Docker container, spawned automatically by \`@openzeppelin/hardhat-tron\`). For Shasta, Nile, or mainnet, add a network entry and pass \`--network \`. +`; + } + + override async zipHardhat(c: Contract, opts?: GenericOptions): Promise { + const zip = new JSZip(); + + const packageJson = await this.getPackageJson(c); + + zip.file(`contracts/${c.name}.sol`, this.getPrintContract(c)); + zip.file('test/test.ts', this.getTest(c, opts)); + // No hardhat-ignition: the TRON dev flow is a plain ethers deploy script, + // matching the @openzeppelin/hardhat-tron README. + zip.file('scripts/deploy.ts', this.getScript(c)); + + zip.file('.gitignore', this.getGitIgnore()); + zip.file('hardhat.config.ts', this.getHardhatConfig(c.upgradeable)); + zip.file('package.json', JSON.stringify(packageJson, null, 2)); + // package-lock.json is intentionally omitted; see getPackageLock(). + zip.file('README.md', this.getReadme(c)); + zip.file('tsconfig.json', this.getTsConfig()); + + return zip; + } +} + +export async function zipHardhatTron(c: Contract, opts?: GenericOptions): Promise { + return new HardhatTronZipGenerator().zipHardhat(c, opts); +} diff --git a/packages/core/solidity/src/zip-tronbox.test.ts b/packages/core/solidity/src/zip-tronbox.test.ts new file mode 100644 index 000000000..d13a9ef39 --- /dev/null +++ b/packages/core/solidity/src/zip-tronbox.test.ts @@ -0,0 +1,113 @@ +import type { ExecutionContext } from 'ava'; +import test from 'ava'; + +import { zipTronbox } from './zip-tronbox'; + +import { buildERC20 } from './erc20'; +import { buildERC721 } from './erc721'; +import { buildERC1155 } from './erc1155'; +import type { Contract } from './contract'; +import type { JSZipObject } from 'jszip'; +import type JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; + +// TronBox is not installed as part of the wizard tests (the binary requires +// global install and a local TRE Docker container). These tests therefore +// only verify file layout and snapshot the contents. + +test.serial('erc20 basic', async t => { + const opts: GenericOptions = { + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + }; + const c = buildERC20(opts); + await runSnapshotTest(c, t, opts); +}); + +test.serial('erc20 mintable+burnable+ownable', async t => { + const opts: GenericOptions = { + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + premint: '1000', + access: 'ownable', + burnable: true, + mintable: true, + }; + const c = buildERC20(opts); + await runSnapshotTest(c, t, opts); +}); + +test.serial('erc721 basic', async t => { + const opts: GenericOptions = { + kind: 'ERC721', + name: 'My NFT', + symbol: 'MNFT', + }; + const c = buildERC721(opts); + await runSnapshotTest(c, t, opts); +}); + +test.serial('erc1155 basic', async t => { + const opts: GenericOptions = { + kind: 'ERC1155', + name: 'My Multi', + uri: 'ipfs://example/{id}', + }; + const c = buildERC1155(opts); + await runSnapshotTest(c, t, opts); +}); + +async function runSnapshotTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { + const zip = await zipTronbox(c, opts); + + assertLayout(zip, c, t); + await assertContents(zip, c, t); +} + +function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { + const sorted = Object.keys(zip.files).sort(); + t.deepEqual(sorted, [ + '.gitignore', + 'README.md', + 'contracts/', + 'contracts/Migrations.sol', + `contracts/${c.name}.sol`, + 'migrations/', + 'migrations/1_initial_migration.js', + `migrations/2_deploy_${c.name}.js`, + 'package.json', + 'test/', + `test/${c.name}.js`, + 'tronbox-config.js', + ]); +} + +async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { + const contentComparison = [ + await getItemString(zip, `contracts/${c.name}.sol`), + await getItemString(zip, 'contracts/Migrations.sol'), + await getItemString(zip, 'migrations/1_initial_migration.js'), + await getItemString(zip, `migrations/2_deploy_${c.name}.js`), + await getItemString(zip, `test/${c.name}.js`), + await getItemString(zip, 'tronbox-config.js'), + await getItemString(zip, 'package.json'), + await getItemString(zip, 'README.md'), + await getItemString(zip, '.gitignore'), + ]; + + t.snapshot(contentComparison); +} + +async function getItemString(zip: JSZip, key: string) { + const obj = zip.files[key]; + if (obj === undefined) { + throw Error(`Item ${key} not found in zip`); + } + return `${key}:\n${await asString(obj)}`; +} + +async function asString(item: JSZipObject) { + return Buffer.from(await item.async('arraybuffer')).toString(); +} diff --git a/packages/core/solidity/src/zip-tronbox.test.ts.md b/packages/core/solidity/src/zip-tronbox.test.ts.md new file mode 100644 index 000000000..1ee170eab --- /dev/null +++ b/packages/core/solidity/src/zip-tronbox.test.ts.md @@ -0,0 +1,835 @@ +# Snapshot report for `src/zip-tronbox.test.ts` + +The actual snapshot is saved in `zip-tronbox.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## erc20 basic + +> Snapshot 1 + + [ + `contracts/MyToken.sol:␊ + // SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.6.0␊ + pragma solidity ^0.8.26;␊ + ␊ + import {TRC20} from "@openzeppelin/tron-contracts/token/TRC20/TRC20.sol";␊ + import {TRC20Permit} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Permit.sol";␊ + ␊ + contract MyToken is TRC20, TRC20Permit {␊ + constructor() TRC20("My Token", "MTK") TRC20Permit("My Token") {}␊ + }␊ + `, + `contracts/Migrations.sol:␊ + // SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.26;␊ + ␊ + contract Migrations {␊ + address public owner = msg.sender;␊ + uint public last_completed_migration;␊ + ␊ + modifier restricted() {␊ + require(msg.sender == owner, "Restricted to owner");␊ + _;␊ + }␊ + ␊ + function setCompleted(uint completed) public restricted {␊ + last_completed_migration = completed;␊ + }␊ + }␊ + `, + `migrations/1_initial_migration.js:␊ + const Migrations = artifacts.require('./Migrations.sol');␊ + ␊ + module.exports = function (deployer) {␊ + deployer.deploy(Migrations);␊ + };␊ + `, + `migrations/2_deploy_MyToken.js:␊ + const MyToken = artifacts.require('./MyToken.sol');␊ + ␊ + module.exports = function (deployer) {␊ + deployer.deploy(MyToken);␊ + };␊ + `, + `test/MyToken.js:␊ + const MyToken = artifacts.require('./MyToken.sol');␊ + ␊ + // These tests require TronBox >= 4.1.x and the TronBox Runtime Environment␊ + // (https://hub.docker.com/r/tronbox/tre) as your private network.␊ + contract('MyToken', function (accounts) {␊ + let instance;␊ + ␊ + before(async function () {␊ + instance = await MyToken.deployed();␊ + });␊ + ␊ + it('is deployed', async function () {␊ + assert.isTrue(accounts.length >= 1, 'At least one account is required.');␊ + assert.isOk(instance.address, 'Contract address should be defined');␊ + });␊ + ␊ + it('sets the expected name', async function () {␊ + assert.equal(await instance.name(), "My Token");␊ + });␊ + });␊ + `, + `tronbox-config.js:␊ + // tronbox-config.js␊ + //␊ + // TronBox configuration for projects targeting TRON. Run with one of:␊ + //␊ + // tronbox migrate --network development # local TRE in Docker␊ + // tronbox migrate --network shasta # Shasta testnet␊ + // tronbox migrate --network nile # Nile testnet␊ + // tronbox migrate --network mainnet # TRON mainnet␊ + //␊ + // Create a .env file (gitignored!) with PRIVATE_KEY_* values before deploying␊ + // to any non-development network.␊ + ␊ + module.exports = {␊ + networks: {␊ + development: {␊ + // For tronbox/tre docker image: https://hub.docker.com/r/tronbox/tre␊ + privateKey: '0000000000000000000000000000000000000000000000000000000000000001',␊ + userFeePercentage: 0,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'http://127.0.0.1:9090',␊ + network_id: '9',␊ + },␊ + shasta: {␊ + privateKey: process.env.PRIVATE_KEY_SHASTA,␊ + userFeePercentage: 50,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://api.shasta.trongrid.io',␊ + network_id: '2',␊ + },␊ + nile: {␊ + privateKey: process.env.PRIVATE_KEY_NILE,␊ + userFeePercentage: 100,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://nile.trongrid.io',␊ + network_id: '3',␊ + },␊ + mainnet: {␊ + privateKey: process.env.PRIVATE_KEY_MAINNET,␊ + userFeePercentage: 100,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://api.trongrid.io',␊ + network_id: '1',␊ + },␊ + },␊ + compilers: {␊ + solc: {␊ + version: '0.8.26',␊ + settings: {␊ + optimizer: { enabled: true, runs: 200 },␊ + evmVersion: 'cancun',␊ + },␊ + },␊ + },␊ + };␊ + `, + `package.json:␊ + {␊ + "name": "tronbox-sample",␊ + "version": "0.0.1",␊ + "description": "Sample TronBox project generated by OpenZeppelin Contracts Wizard",␊ + "license": "MIT",␊ + "scripts": {␊ + "compile": "tronbox compile",␊ + "migrate": "tronbox migrate",␊ + "test": "tronbox test",␊ + "console": "tronbox console"␊ + },␊ + "devDependencies": {␊ + "tronbox": "^4.1.0"␊ + },␊ + "dependencies": {␊ + "@openzeppelin/tron-contracts": "^0.1.0"␊ + }␊ + }`, + `README.md:␊ + # Sample TronBox Project␊ + ␊ + This project demonstrates a basic TronBox use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a migration that deploys it, and a Mocha test.␊ + ␊ + ## Prerequisites␊ + ␊ + - [Node.js 18+](https://nodejs.org/en/download/)␊ + - [Docker](https://docs.docker.com/get-docker/) — runs the local TRON Runtime Environment (\`tronbox/tre\`)␊ + - Install [TronBox](https://tronbox.io/docs/) globally: \`npm install -g tronbox\`␊ + ␊ + ## Installing dependencies␊ + ␊ + \`\`\`␊ + npm install␊ + \`\`\`␊ + ␊ + ## Running a local TRON node␊ + ␊ + In a separate terminal:␊ + ␊ + \`\`\`␊ + docker run --rm -p 9090:9090 tronbox/tre␊ + \`\`\`␊ + ␊ + ## Compiling␊ + ␊ + \`\`\`␊ + tronbox compile␊ + \`\`\`␊ + ␊ + ## Deploying␊ + ␊ + \`\`\`␊ + tronbox migrate --network development␊ + \`\`\`␊ + ␊ + For Shasta/Nile/mainnet, set the corresponding \`PRIVATE_KEY_*\` env var in a \`.env\` file and pass \`--network \`.␊ + ␊ + ## Testing␊ + ␊ + \`\`\`␊ + tronbox test␊ + \`\`\`␊ + ␊ + This will run the Mocha test in \`test/MyToken.js\` against the configured network.␊ + `, + `.gitignore:␊ + node_modules␊ + build␊ + .env␊ + `, + ] + +## erc20 mintable+burnable+ownable + +> Snapshot 1 + + [ + `contracts/MyToken.sol:␊ + // SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.6.0␊ + pragma solidity ^0.8.26;␊ + ␊ + import {Ownable} from "@openzeppelin/tron-contracts/access/Ownable.sol";␊ + import {TRC20} from "@openzeppelin/tron-contracts/token/TRC20/TRC20.sol";␊ + import {TRC20Burnable} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Burnable.sol";␊ + import {TRC20Permit} from "@openzeppelin/tron-contracts/token/TRC20/extensions/TRC20Permit.sol";␊ + ␊ + contract MyToken is TRC20, TRC20Burnable, Ownable, TRC20Permit {␊ + constructor(address recipient, address initialOwner)␊ + TRC20("My Token", "MTK")␊ + Ownable(initialOwner)␊ + TRC20Permit("My Token")␊ + {␊ + _mint(recipient, 1000 * 10 ** decimals());␊ + }␊ + ␊ + function mint(address to, uint256 amount) public onlyOwner {␊ + _mint(to, amount);␊ + }␊ + }␊ + `, + `contracts/Migrations.sol:␊ + // SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.26;␊ + ␊ + contract Migrations {␊ + address public owner = msg.sender;␊ + uint public last_completed_migration;␊ + ␊ + modifier restricted() {␊ + require(msg.sender == owner, "Restricted to owner");␊ + _;␊ + }␊ + ␊ + function setCompleted(uint completed) public restricted {␊ + last_completed_migration = completed;␊ + }␊ + }␊ + `, + `migrations/1_initial_migration.js:␊ + const Migrations = artifacts.require('./Migrations.sol');␊ + ␊ + module.exports = function (deployer) {␊ + deployer.deploy(Migrations);␊ + };␊ + `, + `migrations/2_deploy_MyToken.js:␊ + const MyToken = artifacts.require('./MyToken.sol');␊ + ␊ + module.exports = function (deployer) {␊ + // TODO: Replace with a real address (e.g. tronWeb.defaultAddress.base58).␊ + const recipient = '';␊ + // TODO: Replace with a real address (e.g. tronWeb.defaultAddress.base58).␊ + const initialOwner = '';␊ + ␊ + deployer.deploy(MyToken, recipient, initialOwner);␊ + };␊ + `, + `test/MyToken.js:␊ + const MyToken = artifacts.require('./MyToken.sol');␊ + ␊ + // These tests require TronBox >= 4.1.x and the TronBox Runtime Environment␊ + // (https://hub.docker.com/r/tronbox/tre) as your private network.␊ + // NOTE: this contract has constructor arguments. Update the placeholders in␊ + // migrations/2_deploy_MyToken.js before running 'tronbox test'.␊ + contract('MyToken', function (accounts) {␊ + let instance;␊ + ␊ + before(async function () {␊ + instance = await MyToken.deployed();␊ + });␊ + ␊ + it('is deployed', async function () {␊ + assert.isTrue(accounts.length >= 1, 'At least one account is required.');␊ + assert.isOk(instance.address, 'Contract address should be defined');␊ + });␊ + ␊ + it('sets the expected name', async function () {␊ + assert.equal(await instance.name(), "My Token");␊ + });␊ + });␊ + `, + `tronbox-config.js:␊ + // tronbox-config.js␊ + //␊ + // TronBox configuration for projects targeting TRON. Run with one of:␊ + //␊ + // tronbox migrate --network development # local TRE in Docker␊ + // tronbox migrate --network shasta # Shasta testnet␊ + // tronbox migrate --network nile # Nile testnet␊ + // tronbox migrate --network mainnet # TRON mainnet␊ + //␊ + // Create a .env file (gitignored!) with PRIVATE_KEY_* values before deploying␊ + // to any non-development network.␊ + ␊ + module.exports = {␊ + networks: {␊ + development: {␊ + // For tronbox/tre docker image: https://hub.docker.com/r/tronbox/tre␊ + privateKey: '0000000000000000000000000000000000000000000000000000000000000001',␊ + userFeePercentage: 0,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'http://127.0.0.1:9090',␊ + network_id: '9',␊ + },␊ + shasta: {␊ + privateKey: process.env.PRIVATE_KEY_SHASTA,␊ + userFeePercentage: 50,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://api.shasta.trongrid.io',␊ + network_id: '2',␊ + },␊ + nile: {␊ + privateKey: process.env.PRIVATE_KEY_NILE,␊ + userFeePercentage: 100,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://nile.trongrid.io',␊ + network_id: '3',␊ + },␊ + mainnet: {␊ + privateKey: process.env.PRIVATE_KEY_MAINNET,␊ + userFeePercentage: 100,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://api.trongrid.io',␊ + network_id: '1',␊ + },␊ + },␊ + compilers: {␊ + solc: {␊ + version: '0.8.26',␊ + settings: {␊ + optimizer: { enabled: true, runs: 200 },␊ + evmVersion: 'cancun',␊ + },␊ + },␊ + },␊ + };␊ + `, + `package.json:␊ + {␊ + "name": "tronbox-sample",␊ + "version": "0.0.1",␊ + "description": "Sample TronBox project generated by OpenZeppelin Contracts Wizard",␊ + "license": "MIT",␊ + "scripts": {␊ + "compile": "tronbox compile",␊ + "migrate": "tronbox migrate",␊ + "test": "tronbox test",␊ + "console": "tronbox console"␊ + },␊ + "devDependencies": {␊ + "tronbox": "^4.1.0"␊ + },␊ + "dependencies": {␊ + "@openzeppelin/tron-contracts": "^0.1.0"␊ + }␊ + }`, + `README.md:␊ + # Sample TronBox Project␊ + ␊ + This project demonstrates a basic TronBox use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a migration that deploys it, and a Mocha test.␊ + ␊ + ## Prerequisites␊ + ␊ + - [Node.js 18+](https://nodejs.org/en/download/)␊ + - [Docker](https://docs.docker.com/get-docker/) — runs the local TRON Runtime Environment (\`tronbox/tre\`)␊ + - Install [TronBox](https://tronbox.io/docs/) globally: \`npm install -g tronbox\`␊ + ␊ + ## Installing dependencies␊ + ␊ + \`\`\`␊ + npm install␊ + \`\`\`␊ + ␊ + ## Running a local TRON node␊ + ␊ + In a separate terminal:␊ + ␊ + \`\`\`␊ + docker run --rm -p 9090:9090 tronbox/tre␊ + \`\`\`␊ + ␊ + ## Compiling␊ + ␊ + \`\`\`␊ + tronbox compile␊ + \`\`\`␊ + ␊ + ## Deploying␊ + ␊ + \`\`\`␊ + tronbox migrate --network development␊ + \`\`\`␊ + ␊ + For Shasta/Nile/mainnet, set the corresponding \`PRIVATE_KEY_*\` env var in a \`.env\` file and pass \`--network \`.␊ + ␊ + ## Testing␊ + ␊ + \`\`\`␊ + tronbox test␊ + \`\`\`␊ + ␊ + This will run the Mocha test in \`test/MyToken.js\` against the configured network.␊ + `, + `.gitignore:␊ + node_modules␊ + build␊ + .env␊ + `, + ] + +## erc721 basic + +> Snapshot 1 + + [ + `contracts/MyNFT.sol:␊ + // SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.6.0␊ + pragma solidity ^0.8.26;␊ + ␊ + import {TRC721} from "@openzeppelin/tron-contracts/token/TRC721/TRC721.sol";␊ + ␊ + contract MyNFT is TRC721 {␊ + constructor() TRC721("My NFT", "MNFT") {}␊ + }␊ + `, + `contracts/Migrations.sol:␊ + // SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.26;␊ + ␊ + contract Migrations {␊ + address public owner = msg.sender;␊ + uint public last_completed_migration;␊ + ␊ + modifier restricted() {␊ + require(msg.sender == owner, "Restricted to owner");␊ + _;␊ + }␊ + ␊ + function setCompleted(uint completed) public restricted {␊ + last_completed_migration = completed;␊ + }␊ + }␊ + `, + `migrations/1_initial_migration.js:␊ + const Migrations = artifacts.require('./Migrations.sol');␊ + ␊ + module.exports = function (deployer) {␊ + deployer.deploy(Migrations);␊ + };␊ + `, + `migrations/2_deploy_MyNFT.js:␊ + const MyNFT = artifacts.require('./MyNFT.sol');␊ + ␊ + module.exports = function (deployer) {␊ + deployer.deploy(MyNFT);␊ + };␊ + `, + `test/MyNFT.js:␊ + const MyNFT = artifacts.require('./MyNFT.sol');␊ + ␊ + // These tests require TronBox >= 4.1.x and the TronBox Runtime Environment␊ + // (https://hub.docker.com/r/tronbox/tre) as your private network.␊ + contract('MyNFT', function (accounts) {␊ + let instance;␊ + ␊ + before(async function () {␊ + instance = await MyNFT.deployed();␊ + });␊ + ␊ + it('is deployed', async function () {␊ + assert.isTrue(accounts.length >= 1, 'At least one account is required.');␊ + assert.isOk(instance.address, 'Contract address should be defined');␊ + });␊ + ␊ + it('sets the expected name', async function () {␊ + assert.equal(await instance.name(), "My NFT");␊ + });␊ + });␊ + `, + `tronbox-config.js:␊ + // tronbox-config.js␊ + //␊ + // TronBox configuration for projects targeting TRON. Run with one of:␊ + //␊ + // tronbox migrate --network development # local TRE in Docker␊ + // tronbox migrate --network shasta # Shasta testnet␊ + // tronbox migrate --network nile # Nile testnet␊ + // tronbox migrate --network mainnet # TRON mainnet␊ + //␊ + // Create a .env file (gitignored!) with PRIVATE_KEY_* values before deploying␊ + // to any non-development network.␊ + ␊ + module.exports = {␊ + networks: {␊ + development: {␊ + // For tronbox/tre docker image: https://hub.docker.com/r/tronbox/tre␊ + privateKey: '0000000000000000000000000000000000000000000000000000000000000001',␊ + userFeePercentage: 0,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'http://127.0.0.1:9090',␊ + network_id: '9',␊ + },␊ + shasta: {␊ + privateKey: process.env.PRIVATE_KEY_SHASTA,␊ + userFeePercentage: 50,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://api.shasta.trongrid.io',␊ + network_id: '2',␊ + },␊ + nile: {␊ + privateKey: process.env.PRIVATE_KEY_NILE,␊ + userFeePercentage: 100,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://nile.trongrid.io',␊ + network_id: '3',␊ + },␊ + mainnet: {␊ + privateKey: process.env.PRIVATE_KEY_MAINNET,␊ + userFeePercentage: 100,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://api.trongrid.io',␊ + network_id: '1',␊ + },␊ + },␊ + compilers: {␊ + solc: {␊ + version: '0.8.26',␊ + settings: {␊ + optimizer: { enabled: true, runs: 200 },␊ + evmVersion: 'cancun',␊ + },␊ + },␊ + },␊ + };␊ + `, + `package.json:␊ + {␊ + "name": "tronbox-sample",␊ + "version": "0.0.1",␊ + "description": "Sample TronBox project generated by OpenZeppelin Contracts Wizard",␊ + "license": "MIT",␊ + "scripts": {␊ + "compile": "tronbox compile",␊ + "migrate": "tronbox migrate",␊ + "test": "tronbox test",␊ + "console": "tronbox console"␊ + },␊ + "devDependencies": {␊ + "tronbox": "^4.1.0"␊ + },␊ + "dependencies": {␊ + "@openzeppelin/tron-contracts": "^0.1.0"␊ + }␊ + }`, + `README.md:␊ + # Sample TronBox Project␊ + ␊ + This project demonstrates a basic TronBox use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a migration that deploys it, and a Mocha test.␊ + ␊ + ## Prerequisites␊ + ␊ + - [Node.js 18+](https://nodejs.org/en/download/)␊ + - [Docker](https://docs.docker.com/get-docker/) — runs the local TRON Runtime Environment (\`tronbox/tre\`)␊ + - Install [TronBox](https://tronbox.io/docs/) globally: \`npm install -g tronbox\`␊ + ␊ + ## Installing dependencies␊ + ␊ + \`\`\`␊ + npm install␊ + \`\`\`␊ + ␊ + ## Running a local TRON node␊ + ␊ + In a separate terminal:␊ + ␊ + \`\`\`␊ + docker run --rm -p 9090:9090 tronbox/tre␊ + \`\`\`␊ + ␊ + ## Compiling␊ + ␊ + \`\`\`␊ + tronbox compile␊ + \`\`\`␊ + ␊ + ## Deploying␊ + ␊ + \`\`\`␊ + tronbox migrate --network development␊ + \`\`\`␊ + ␊ + For Shasta/Nile/mainnet, set the corresponding \`PRIVATE_KEY_*\` env var in a \`.env\` file and pass \`--network \`.␊ + ␊ + ## Testing␊ + ␊ + \`\`\`␊ + tronbox test␊ + \`\`\`␊ + ␊ + This will run the Mocha test in \`test/MyNFT.js\` against the configured network.␊ + `, + `.gitignore:␊ + node_modules␊ + build␊ + .env␊ + `, + ] + +## erc1155 basic + +> Snapshot 1 + + [ + `contracts/MyMulti.sol:␊ + // SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.6.0␊ + pragma solidity ^0.8.26;␊ + ␊ + import {Ownable} from "@openzeppelin/tron-contracts/access/Ownable.sol";␊ + import {TRC1155} from "@openzeppelin/tron-contracts/token/TRC1155/TRC1155.sol";␊ + ␊ + contract MyMulti is TRC1155, Ownable {␊ + constructor(address initialOwner)␊ + TRC1155("ipfs://example/{id}")␊ + Ownable(initialOwner)␊ + {}␊ + ␊ + function setURI(string memory newuri) public onlyOwner {␊ + _setURI(newuri);␊ + }␊ + }␊ + `, + `contracts/Migrations.sol:␊ + // SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.26;␊ + ␊ + contract Migrations {␊ + address public owner = msg.sender;␊ + uint public last_completed_migration;␊ + ␊ + modifier restricted() {␊ + require(msg.sender == owner, "Restricted to owner");␊ + _;␊ + }␊ + ␊ + function setCompleted(uint completed) public restricted {␊ + last_completed_migration = completed;␊ + }␊ + }␊ + `, + `migrations/1_initial_migration.js:␊ + const Migrations = artifacts.require('./Migrations.sol');␊ + ␊ + module.exports = function (deployer) {␊ + deployer.deploy(Migrations);␊ + };␊ + `, + `migrations/2_deploy_MyMulti.js:␊ + const MyMulti = artifacts.require('./MyMulti.sol');␊ + ␊ + module.exports = function (deployer) {␊ + // TODO: Replace with a real address (e.g. tronWeb.defaultAddress.base58).␊ + const initialOwner = '';␊ + ␊ + deployer.deploy(MyMulti, initialOwner);␊ + };␊ + `, + `test/MyMulti.js:␊ + const MyMulti = artifacts.require('./MyMulti.sol');␊ + ␊ + // These tests require TronBox >= 4.1.x and the TronBox Runtime Environment␊ + // (https://hub.docker.com/r/tronbox/tre) as your private network.␊ + // NOTE: this contract has constructor arguments. Update the placeholders in␊ + // migrations/2_deploy_MyMulti.js before running 'tronbox test'.␊ + contract('MyMulti', function (accounts) {␊ + let instance;␊ + ␊ + before(async function () {␊ + instance = await MyMulti.deployed();␊ + });␊ + ␊ + it('is deployed', async function () {␊ + assert.isTrue(accounts.length >= 1, 'At least one account is required.');␊ + assert.isOk(instance.address, 'Contract address should be defined');␊ + });␊ + ␊ + it('sets the expected URI', async function () {␊ + assert.equal(await instance.uri(0), "ipfs://example/{id}");␊ + });␊ + });␊ + `, + `tronbox-config.js:␊ + // tronbox-config.js␊ + //␊ + // TronBox configuration for projects targeting TRON. Run with one of:␊ + //␊ + // tronbox migrate --network development # local TRE in Docker␊ + // tronbox migrate --network shasta # Shasta testnet␊ + // tronbox migrate --network nile # Nile testnet␊ + // tronbox migrate --network mainnet # TRON mainnet␊ + //␊ + // Create a .env file (gitignored!) with PRIVATE_KEY_* values before deploying␊ + // to any non-development network.␊ + ␊ + module.exports = {␊ + networks: {␊ + development: {␊ + // For tronbox/tre docker image: https://hub.docker.com/r/tronbox/tre␊ + privateKey: '0000000000000000000000000000000000000000000000000000000000000001',␊ + userFeePercentage: 0,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'http://127.0.0.1:9090',␊ + network_id: '9',␊ + },␊ + shasta: {␊ + privateKey: process.env.PRIVATE_KEY_SHASTA,␊ + userFeePercentage: 50,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://api.shasta.trongrid.io',␊ + network_id: '2',␊ + },␊ + nile: {␊ + privateKey: process.env.PRIVATE_KEY_NILE,␊ + userFeePercentage: 100,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://nile.trongrid.io',␊ + network_id: '3',␊ + },␊ + mainnet: {␊ + privateKey: process.env.PRIVATE_KEY_MAINNET,␊ + userFeePercentage: 100,␊ + feeLimit: 1000 * 1e6,␊ + fullHost: 'https://api.trongrid.io',␊ + network_id: '1',␊ + },␊ + },␊ + compilers: {␊ + solc: {␊ + version: '0.8.26',␊ + settings: {␊ + optimizer: { enabled: true, runs: 200 },␊ + evmVersion: 'cancun',␊ + },␊ + },␊ + },␊ + };␊ + `, + `package.json:␊ + {␊ + "name": "tronbox-sample",␊ + "version": "0.0.1",␊ + "description": "Sample TronBox project generated by OpenZeppelin Contracts Wizard",␊ + "license": "MIT",␊ + "scripts": {␊ + "compile": "tronbox compile",␊ + "migrate": "tronbox migrate",␊ + "test": "tronbox test",␊ + "console": "tronbox console"␊ + },␊ + "devDependencies": {␊ + "tronbox": "^4.1.0"␊ + },␊ + "dependencies": {␊ + "@openzeppelin/tron-contracts": "^0.1.0"␊ + }␊ + }`, + `README.md:␊ + # Sample TronBox Project␊ + ␊ + This project demonstrates a basic TronBox use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a migration that deploys it, and a Mocha test.␊ + ␊ + ## Prerequisites␊ + ␊ + - [Node.js 18+](https://nodejs.org/en/download/)␊ + - [Docker](https://docs.docker.com/get-docker/) — runs the local TRON Runtime Environment (\`tronbox/tre\`)␊ + - Install [TronBox](https://tronbox.io/docs/) globally: \`npm install -g tronbox\`␊ + ␊ + ## Installing dependencies␊ + ␊ + \`\`\`␊ + npm install␊ + \`\`\`␊ + ␊ + ## Running a local TRON node␊ + ␊ + In a separate terminal:␊ + ␊ + \`\`\`␊ + docker run --rm -p 9090:9090 tronbox/tre␊ + \`\`\`␊ + ␊ + ## Compiling␊ + ␊ + \`\`\`␊ + tronbox compile␊ + \`\`\`␊ + ␊ + ## Deploying␊ + ␊ + \`\`\`␊ + tronbox migrate --network development␊ + \`\`\`␊ + ␊ + For Shasta/Nile/mainnet, set the corresponding \`PRIVATE_KEY_*\` env var in a \`.env\` file and pass \`--network \`.␊ + ␊ + ## Testing␊ + ␊ + \`\`\`␊ + tronbox test␊ + \`\`\`␊ + ␊ + This will run the Mocha test in \`test/MyMulti.js\` against the configured network.␊ + `, + `.gitignore:␊ + node_modules␊ + build␊ + .env␊ + `, + ] diff --git a/packages/core/solidity/src/zip-tronbox.test.ts.snap b/packages/core/solidity/src/zip-tronbox.test.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..ce0b028626a39312a888db800684ab0902bce769 GIT binary patch literal 2984 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 bpc^`!?n3Mc?fQRgx*?Zi2o_#8A|d$~@@vmF literal 0 HcmV?d00001 diff --git a/packages/core/solidity/src/zip-tronbox.ts b/packages/core/solidity/src/zip-tronbox.ts new file mode 100644 index 000000000..c22a3ef76 --- /dev/null +++ b/packages/core/solidity/src/zip-tronbox.ts @@ -0,0 +1,284 @@ +import JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; +import type { Contract, FunctionArgument } from './contract'; +import { printContract } from './print'; +import { rewriteForTron } from './utils/transform-tron'; +import { stringifyUnicodeSafe } from './utils/sanitize'; + +// TronBox is a Truffle-derived framework for the TRON Virtual Machine. The +// download bundles: +// - the contract source (rewritten for @openzeppelin/tron-contracts), +// - migrations (`migrations/1_initial_migration.js`, `migrations/2_deploy_.js`), +// - a Mocha-based test using `artifacts.require()`, +// - `tronbox-config.js` configured for local TRE + Shasta/Nile/mainnet, +// - `package.json` with TronBox + the OZ TRON contracts library. + +// Matches the README of @openzeppelin/hardhat-tron so both download flavours stay aligned. +const TRON_SOLIDITY_VERSION = '0.8.26'; + +function getDeploymentArgs(c: Contract): string[] { + return c.constructorArgs.map(arg => placeholderForArg(arg)); +} + +function placeholderForArg(arg: FunctionArgument): string { + // Use the arg name as a local variable identifier; we declare placeholders + // above the deploy call so the user knows what to fill in. + return arg.name; +} + +function declareArgPlaceholders(c: Contract): string[] { + return c.constructorArgs.map(arg => { + if (arg.type === 'address') { + return `// TODO: Replace with a real address (e.g. tronWeb.defaultAddress.base58).\n const ${arg.name} = '';`; + } + return `// TODO: Set value for the ${arg.name} constructor argument.\n const ${arg.name} = undefined;`; + }); +} + +const migrationsContract = `\ +// SPDX-License-Identifier: MIT +pragma solidity ^${TRON_SOLIDITY_VERSION}; + +contract Migrations { + address public owner = msg.sender; + uint public last_completed_migration; + + modifier restricted() { + require(msg.sender == owner, "Restricted to owner"); + _; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } +} +`; + +const initialMigration = `\ +const Migrations = artifacts.require('./Migrations.sol'); + +module.exports = function (deployer) { + deployer.deploy(Migrations); +}; +`; + +function deployMigration(c: Contract): string { + const argDecls = declareArgPlaceholders(c); + const argList = getDeploymentArgs(c); + + const declarations = argDecls.length > 0 ? argDecls.join('\n ') + '\n\n ' : ''; + const deployCall = + argList.length > 0 ? `deployer.deploy(${c.name}, ${argList.join(', ')});` : `deployer.deploy(${c.name});`; + + return `\ +const ${c.name} = artifacts.require('./${c.name}.sol'); + +module.exports = function (deployer) { + ${declarations}${deployCall} +}; +`; +} + +function testFile(c: Contract, opts?: GenericOptions): string { + let assertion = ''; + if (opts !== undefined) { + switch (opts.kind) { + case 'ERC20': + case 'ERC721': + assertion = ` + + it('sets the expected name', async function () { + assert.equal(await instance.name(), ${stringifyUnicodeSafe(opts.name)}); + });`; + break; + case 'ERC1155': + assertion = ` + + it('sets the expected URI', async function () { + assert.equal(await instance.uri(0), ${stringifyUnicodeSafe(opts.uri)}); + });`; + break; + default: + break; + } + } + + const constructorArgNote = + c.constructorArgs.length > 0 + ? `// NOTE: this contract has constructor arguments. Update the placeholders in +// migrations/2_deploy_${c.name}.js before running 'tronbox test'. +` + : ''; + + return `\ +const ${c.name} = artifacts.require('./${c.name}.sol'); + +// These tests require TronBox >= 4.1.x and the TronBox Runtime Environment +// (https://hub.docker.com/r/tronbox/tre) as your private network. +${constructorArgNote}contract('${c.name}', function (accounts) { + let instance; + + before(async function () { + instance = await ${c.name}.deployed(); + }); + + it('is deployed', async function () { + assert.isTrue(accounts.length >= 1, 'At least one account is required.'); + assert.isOk(instance.address, 'Contract address should be defined'); + });${assertion} +}); +`; +} + +const tronboxConfig = `\ +// tronbox-config.js +// +// TronBox configuration for projects targeting TRON. Run with one of: +// +// tronbox migrate --network development # local TRE in Docker +// tronbox migrate --network shasta # Shasta testnet +// tronbox migrate --network nile # Nile testnet +// tronbox migrate --network mainnet # TRON mainnet +// +// Create a .env file (gitignored!) with PRIVATE_KEY_* values before deploying +// to any non-development network. + +module.exports = { + networks: { + development: { + // For tronbox/tre docker image: https://hub.docker.com/r/tronbox/tre + privateKey: '0000000000000000000000000000000000000000000000000000000000000001', + userFeePercentage: 0, + feeLimit: 1000 * 1e6, + fullHost: 'http://127.0.0.1:9090', + network_id: '9', + }, + shasta: { + privateKey: process.env.PRIVATE_KEY_SHASTA, + userFeePercentage: 50, + feeLimit: 1000 * 1e6, + fullHost: 'https://api.shasta.trongrid.io', + network_id: '2', + }, + nile: { + privateKey: process.env.PRIVATE_KEY_NILE, + userFeePercentage: 100, + feeLimit: 1000 * 1e6, + fullHost: 'https://nile.trongrid.io', + network_id: '3', + }, + mainnet: { + privateKey: process.env.PRIVATE_KEY_MAINNET, + userFeePercentage: 100, + feeLimit: 1000 * 1e6, + fullHost: 'https://api.trongrid.io', + network_id: '1', + }, + }, + compilers: { + solc: { + version: '${TRON_SOLIDITY_VERSION}', + settings: { + optimizer: { enabled: true, runs: 200 }, + evmVersion: 'cancun', + }, + }, + }, +}; +`; + +function packageJson(c: Contract): unknown { + return { + name: 'tronbox-sample', + version: '0.0.1', + description: 'Sample TronBox project generated by OpenZeppelin Contracts Wizard', + license: c.license, + scripts: { + compile: 'tronbox compile', + migrate: 'tronbox migrate', + test: 'tronbox test', + console: 'tronbox console', + }, + devDependencies: { + tronbox: '^4.1.0', + }, + dependencies: { + '@openzeppelin/tron-contracts': '^0.1.0', + }, + }; +} + +const gitignore = `\ +node_modules +build +.env +`; + +function readme(c: Contract): string { + return `\ +# Sample TronBox Project + +This project demonstrates a basic TronBox use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a migration that deploys it, and a Mocha test. + +## Prerequisites + +- [Node.js 18+](https://nodejs.org/en/download/) +- [Docker](https://docs.docker.com/get-docker/) — runs the local TRON Runtime Environment (\`tronbox/tre\`) +- Install [TronBox](https://tronbox.io/docs/) globally: \`npm install -g tronbox\` + +## Installing dependencies + +\`\`\` +npm install +\`\`\` + +## Running a local TRON node + +In a separate terminal: + +\`\`\` +docker run --rm -p 9090:9090 tronbox/tre +\`\`\` + +## Compiling + +\`\`\` +tronbox compile +\`\`\` + +## Deploying + +\`\`\` +tronbox migrate --network development +\`\`\` + +For Shasta/Nile/mainnet, set the corresponding \`PRIVATE_KEY_*\` env var in a \`.env\` file and pass \`--network \`. + +## Testing + +\`\`\` +tronbox test +\`\`\` + +This will run the Mocha test in \`test/${c.name}.js\` against the configured network. +`; +} + +export async function zipTronbox(c: Contract, opts?: GenericOptions): Promise { + const zip = new JSZip(); + + zip.file(`contracts/${c.name}.sol`, rewriteForTron(printContract(c))); + zip.file('contracts/Migrations.sol', migrationsContract); + + zip.file('migrations/1_initial_migration.js', initialMigration); + zip.file(`migrations/2_deploy_${c.name}.js`, deployMigration(c)); + + zip.file(`test/${c.name}.js`, testFile(c, opts)); + + zip.file('tronbox-config.js', tronboxConfig); + zip.file('package.json', JSON.stringify(packageJson(c), null, 2)); + zip.file('.gitignore', gitignore); + zip.file('README.md', readme(c)); + + return zip; +} diff --git a/packages/core/solidity/zip-env-hardhat-tron.js b/packages/core/solidity/zip-env-hardhat-tron.js new file mode 100644 index 000000000..8c40f9753 --- /dev/null +++ b/packages/core/solidity/zip-env-hardhat-tron.js @@ -0,0 +1 @@ +module.exports = require('./dist/zip-hardhat-tron'); diff --git a/packages/core/solidity/zip-env-hardhat-tron.ts b/packages/core/solidity/zip-env-hardhat-tron.ts new file mode 100644 index 000000000..0519a259c --- /dev/null +++ b/packages/core/solidity/zip-env-hardhat-tron.ts @@ -0,0 +1 @@ +export * from './src/zip-hardhat-tron'; diff --git a/packages/core/solidity/zip-env-tronbox.js b/packages/core/solidity/zip-env-tronbox.js new file mode 100644 index 000000000..e3dd5397d --- /dev/null +++ b/packages/core/solidity/zip-env-tronbox.js @@ -0,0 +1 @@ +module.exports = require('./dist/zip-tronbox'); diff --git a/packages/core/solidity/zip-env-tronbox.ts b/packages/core/solidity/zip-env-tronbox.ts new file mode 100644 index 000000000..666dd7703 --- /dev/null +++ b/packages/core/solidity/zip-env-tronbox.ts @@ -0,0 +1 @@ +export * from './src/zip-tronbox'; diff --git a/packages/ui/api/ai-assistant/function-definitions/tron.ts b/packages/ui/api/ai-assistant/function-definitions/tron.ts new file mode 100644 index 000000000..b5770886c --- /dev/null +++ b/packages/ui/api/ai-assistant/function-definitions/tron.ts @@ -0,0 +1,20 @@ +import { + solidityERC20AIFunctionDefinition, + solidityERC721AIFunctionDefinition, + solidityERC1155AIFunctionDefinition, + solidityStablecoinAIFunctionDefinition, + solidityRealWorldAssetAIFunctionDefinition, + solidityGovernorAIFunctionDefinition, + solidityCustomAIFunctionDefinition, +} from './solidity.ts'; + +// The TRON AI assistant uses the same option shape as Solidity (less Account, +// which is omitted in the UI). Function definitions are reused directly; the +// language identifier ('tron') is what differentiates assistant context. +export const tronERC20AIFunctionDefinition = solidityERC20AIFunctionDefinition; +export const tronERC721AIFunctionDefinition = solidityERC721AIFunctionDefinition; +export const tronERC1155AIFunctionDefinition = solidityERC1155AIFunctionDefinition; +export const tronStablecoinAIFunctionDefinition = solidityStablecoinAIFunctionDefinition; +export const tronRealWorldAssetAIFunctionDefinition = solidityRealWorldAssetAIFunctionDefinition; +export const tronGovernorAIFunctionDefinition = solidityGovernorAIFunctionDefinition; +export const tronCustomAIFunctionDefinition = solidityCustomAIFunctionDefinition; diff --git a/packages/ui/api/ai-assistant/types/languages.ts b/packages/ui/api/ai-assistant/types/languages.ts index 3467acc97..ae409d120 100644 --- a/packages/ui/api/ai-assistant/types/languages.ts +++ b/packages/ui/api/ai-assistant/types/languages.ts @@ -42,6 +42,7 @@ export type LanguagesContractsOptions = { cairoAlpha: CairoAlphaKindedOptions; confidential: ConfidentialKindedOptions; polkadot: Omit; + tron: Omit; stellar: Omit & { Fungible: StellarKindedOptions['Fungible'] & StellarCommonContractOptions; NonFungible: StellarKindedOptions['NonFungible'] & StellarCommonContractOptions; diff --git a/packages/ui/api/ai.ts b/packages/ui/api/ai.ts index 73a4d35d2..97e9df81e 100644 --- a/packages/ui/api/ai.ts +++ b/packages/ui/api/ai.ts @@ -4,6 +4,7 @@ import * as cairoFunctions from './ai-assistant/function-definitions/cairo.ts'; import * as cairoAlphaFunctions from './ai-assistant/function-definitions/cairo-alpha.ts'; import * as stellarFunctions from './ai-assistant/function-definitions/stellar.ts'; import * as stylusFunctions from './ai-assistant/function-definitions/stylus.ts'; +import * as tronFunctions from './ai-assistant/function-definitions/tron.ts'; import * as confidentialFunctions from './ai-assistant/function-definitions/confidential.ts'; import * as uniswapHooksFunctions from './ai-assistant/function-definitions/uniswap-hooks.ts'; import { saveChatInRedisIfDoesNotExist } from './services/redis.ts'; @@ -27,6 +28,7 @@ const getFunctionsContext = polkadotPolkadot stellarStellar stylusStylus + tronTRON uniswap-hooksUniswap Hooks diff --git a/packages/ui/public/confidential.html b/packages/ui/public/confidential.html index 103464417..393699fb7 100644 --- a/packages/ui/public/confidential.html +++ b/packages/ui/public/confidential.html @@ -69,6 +69,7 @@ polkadotPolkadot stellarStellar stylusStylus + tronTRON uniswap-hooksUniswap Hooks diff --git a/packages/ui/public/icons/tron.svg b/packages/ui/public/icons/tron.svg new file mode 100644 index 000000000..a2adb2e0a --- /dev/null +++ b/packages/ui/public/icons/tron.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/public/icons/tron_active.svg b/packages/ui/public/icons/tron_active.svg new file mode 100644 index 000000000..cf19a5148 --- /dev/null +++ b/packages/ui/public/icons/tron_active.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/public/index.html b/packages/ui/public/index.html index 998333320..3b7a7c90c 100644 --- a/packages/ui/public/index.html +++ b/packages/ui/public/index.html @@ -101,6 +101,7 @@ polkadotPolkadot stellarStellar stylusStylus + tronTRON uniswap-hooksUniswap Hooks diff --git a/packages/ui/public/polkadot.html b/packages/ui/public/polkadot.html index 12693e274..6c91e063e 100644 --- a/packages/ui/public/polkadot.html +++ b/packages/ui/public/polkadot.html @@ -70,6 +70,7 @@ polkadotPolkadot stellarStellar stylusStylus + tronTRON uniswap-hooksUniswap Hooks diff --git a/packages/ui/public/stellar.html b/packages/ui/public/stellar.html index 9df3d3277..66533991e 100644 --- a/packages/ui/public/stellar.html +++ b/packages/ui/public/stellar.html @@ -69,6 +69,7 @@ polkadotPolkadot stellarStellar stylusStylus + tronTRON uniswap-hooksUniswap Hooks diff --git a/packages/ui/public/stylus.html b/packages/ui/public/stylus.html index 75ce2a8a9..ce150fda5 100644 --- a/packages/ui/public/stylus.html +++ b/packages/ui/public/stylus.html @@ -69,6 +69,7 @@ polkadotPolkadot stellarStellar stylusStylus + tronTRON uniswap-hooksUniswap Hooks diff --git a/packages/ui/public/tron.html b/packages/ui/public/tron.html new file mode 100644 index 000000000..1ec9bdf25 --- /dev/null +++ b/packages/ui/public/tron.html @@ -0,0 +1,103 @@ + + + + + + OpenZeppelin Contracts Wizard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ Forum + Docs + GitHub OpenZeppelin + Twitter/X +
+
+ + +
+ +
+ + + + + + + + + + + + + diff --git a/packages/ui/public/uniswap-hooks.html b/packages/ui/public/uniswap-hooks.html index d5ba1c021..5c7b4d61b 100644 --- a/packages/ui/public/uniswap-hooks.html +++ b/packages/ui/public/uniswap-hooks.html @@ -99,6 +99,7 @@ polkadotPolkadot stellarStellar stylusStylus + tronTRON uniswap-hooksUniswap Hooks diff --git a/packages/ui/src/common/languages-types.ts b/packages/ui/src/common/languages-types.ts index 375e93dd8..f8a1f55b5 100644 --- a/packages/ui/src/common/languages-types.ts +++ b/packages/ui/src/common/languages-types.ts @@ -23,4 +23,5 @@ export type Language = | 'polkadot-solidity' | 'stellar' | 'stylus' + | 'tron-solidity' | 'uniswap-hooks-solidity'; diff --git a/packages/ui/src/common/post-config.ts b/packages/ui/src/common/post-config.ts index 5b4e1eb15..f62a03423 100644 --- a/packages/ui/src/common/post-config.ts +++ b/packages/ui/src/common/post-config.ts @@ -10,6 +10,7 @@ export type DownloadAction = | 'download-file' | 'download-hardhat' | 'download-foundry' + | 'download-tronbox' | 'download-scaffold' | 'download-rust-stellar'; diff --git a/packages/ui/src/main.ts b/packages/ui/src/main.ts index 635fdc0d6..4fce426ae 100644 --- a/packages/ui/src/main.ts +++ b/packages/ui/src/main.ts @@ -7,6 +7,7 @@ import ConfidentialApp from './confidential/App.svelte'; import PolkadotApp from './polkadot/App.svelte'; import StellarApp from './stellar/App.svelte'; import StylusApp from './stylus/App.svelte'; +import TronApp from './tron/App.svelte'; import UniswapHooksApp from './uniswap-hooks/App.svelte'; import VersionedApp from './common/VersionedApp.svelte'; import { postMessage } from './common/post-message'; @@ -48,7 +49,16 @@ const initialOpts: InitialOptions = { interface CompatibleSelection { compatible: true; - appType: 'solidity' | 'cairo' | 'cairo_alpha' | 'confidential' | 'polkadot' | 'stellar' | 'stylus' | 'uniswap-hooks'; + appType: + | 'solidity' + | 'cairo' + | 'cairo_alpha' + | 'confidential' + | 'polkadot' + | 'stellar' + | 'stylus' + | 'tron' + | 'uniswap-hooks'; } interface IncompatibleSelection { @@ -99,6 +109,14 @@ function evaluateSelection( return { compatible: false, compatibleVersionsSemver: soliditySemver }; } } + case 'tron': { + // Use Solidity Contracts semver — `@openzeppelin/tron-contracts` mirrors `@openzeppelin/contracts`. + if (requestedVersion === undefined || semver.satisfies(requestedVersion, soliditySemver)) { + return { compatible: true, appType: 'tron' }; + } else { + return { compatible: false, compatibleVersionsSemver: soliditySemver }; + } + } case 'stellar': { if (requestedVersion === undefined || semver.satisfies(requestedVersion, stellarSemver)) { return { compatible: true, appType: 'stellar' }; @@ -181,6 +199,9 @@ if (!selection.compatible) { case 'stylus': app = new StylusApp({ target: document.body, props: { initialTab, initialOpts } }); break; + case 'tron': + app = new TronApp({ target: document.body, props: { initialTab, initialOpts } }); + break; case 'confidential': app = new ConfidentialApp({ target: document.body, props: { initialTab, initialOpts } }); break; diff --git a/packages/ui/src/solidity/App.svelte b/packages/ui/src/solidity/App.svelte index 2d792d1bd..61ec17c31 100644 --- a/packages/ui/src/solidity/App.svelte +++ b/packages/ui/src/solidity/App.svelte @@ -119,7 +119,9 @@ } } - $: code = printContract(contract); + $: code = overrides.transformPrintedContract + ? overrides.transformPrintedContract(printContract(contract)) + : printContract(contract); $: highlightedCode = injectHyperlinks(hljs.highlight('solidity', code).value); $: hasErrors = errors[tab] !== undefined; @@ -156,6 +158,9 @@ if (overrides.omitZipFoundry) { result.downloadFoundry = false; } + if (overrides.omitOpenInRemix) { + result.openInRemix = false; + } return result; }; @@ -183,7 +188,9 @@ e.preventDefault(); if ((e.target as Element)?.classList.contains('disabled')) return; - const remappings = getVersionedRemappings(opts); + const remappings = overrides.overrideVersionedRemappings + ? overrides.overrideVersionedRemappings(opts) + : getVersionedRemappings(opts); window.open(remixURL(code, remappings, !!opts?.upgradeable).toString(), '_blank', 'noopener,noreferrer'); if (opts) { await postConfig(opts, 'remix', language); @@ -216,12 +223,17 @@ const zipFoundryModule = import('@openzeppelin/wizard/zip-env-foundry'); const downloadFoundryHandler = async () => { - const { zipFoundry } = await zipFoundryModule; - const zip = await zipFoundry(contract, opts); + const zip = + overrides.overrideZipFoundry !== undefined + ? await overrides.overrideZipFoundry(contract, opts) + : await (async () => { + const { zipFoundry } = await zipFoundryModule; + return zipFoundry(contract, opts); + })(); const blob = await zip.generateAsync({ type: 'blob' }); saveAs(blob, 'project.zip'); if (opts) { - await postConfig(opts, 'download-foundry', language); + await postConfig(opts, overrides.secondaryDownloadAction ?? 'download-foundry', language); } }; @@ -246,9 +258,15 @@
- - - + + + {/if} diff --git a/packages/ui/src/solidity/overrides.ts b/packages/ui/src/solidity/overrides.ts index 4fa2fc8e3..0096ffe72 100644 --- a/packages/ui/src/solidity/overrides.ts +++ b/packages/ui/src/solidity/overrides.ts @@ -2,6 +2,7 @@ import type { Contract, GenericOptions, Kind } from '@openzeppelin/wizard'; import type { ComponentType } from 'svelte'; import type { SupportedLanguage } from '../../api/ai-assistant/types/languages'; import type { Language } from '../common/languages-types'; +import type { DownloadAction } from '../common/post-config'; import type JSZip from 'jszip'; /** @@ -13,6 +14,14 @@ export interface Overrides { */ omitTabs: Kind[]; + /** + * Display-only labels for the kind tabs. Internal kind values stay the same + * (ERC20/ERC721/ERC1155/...) — only what users see changes. Used for + * ecosystems whose contract library renames the token standards + * (e.g. TRON uses TRC20/TRC721/TRC1155). + */ + tabLabels?: Partial>; + /** * Map from contract kind to features to omit */ @@ -33,6 +42,51 @@ export interface Overrides { */ omitZipFoundry: boolean; + /** + * Override for the second download tab (originally "Foundry"). When set, + * this function is called instead of the default `zipFoundry`; the tab + * label can be customized via `secondaryDownloadLabel`. + */ + overrideZipFoundry?: (c: Contract, opts?: GenericOptions) => Promise; + + /** + * Label overrides for the secondary (originally "Foundry") download tab. + * Set when an ecosystem replaces Foundry with a different toolchain + * (e.g. TronBox). + */ + secondaryDownloadLabel?: { + title: string; + description: string; + }; + + /** + * Analytics action emitted when the secondary download tab is used. + * Defaults to `'download-foundry'` to preserve the existing telemetry. + */ + secondaryDownloadAction?: DownloadAction; + + /** + * Whether to omit the "Open in Remix" action. Useful for ecosystems whose + * import paths or contracts library Remix cannot resolve. + */ + omitOpenInRemix?: boolean; + + /** + * Override the remappings passed to Remix when "Open in Remix" is used. + * Defaults to `@openzeppelin/wizard`'s `getVersionedRemappings(opts)`. + * Set this when the generated source uses a non-default contracts + * library (e.g. `@openzeppelin/tron-contracts`). + */ + overrideVersionedRemappings?: (opts?: GenericOptions) => string[]; + + /** + * Transform applied to the printed Solidity source before it is displayed, + * copied, downloaded as a single file, or written into a zip by the + * wizard's UI layer. Each ecosystem zip generator must apply its own + * transform internally; this hook only affects the UI-side rendering. + */ + transformPrintedContract?: (source: string) => string; + /** * A function to sanitize omitted features from the Solidity Wizard options. * Removes or modifies the options as appropriate by mutating the input object. @@ -55,10 +109,17 @@ export interface Overrides { export const defaultOverrides: Overrides = { omitTabs: [], + tabLabels: undefined, omitFeatures: new Map(), omitZipHardhat: () => false, overrideZipHardhat: undefined, omitZipFoundry: false, + overrideZipFoundry: undefined, + secondaryDownloadLabel: undefined, + secondaryDownloadAction: undefined, + omitOpenInRemix: false, + overrideVersionedRemappings: undefined, + transformPrintedContract: undefined, sanitizeOmittedFeatures: (_: GenericOptions) => {}, postConfigLanguage: undefined, aiAssistant: undefined, diff --git a/packages/ui/src/standalone.css b/packages/ui/src/standalone.css index c8b8b122b..fb12cac0d 100644 --- a/packages/ui/src/standalone.css +++ b/packages/ui/src/standalone.css @@ -145,6 +145,10 @@ body { --color-2: #e3126f; } +.nav .switch.switch-tron.active { + --color-2: #ff060a; +} + .nav .switch.switch-stellar.active { --color-2: #0f0f0f; } diff --git a/packages/ui/src/tron/App.svelte b/packages/ui/src/tron/App.svelte new file mode 100644 index 000000000..608f1ddda --- /dev/null +++ b/packages/ui/src/tron/App.svelte @@ -0,0 +1,72 @@ + + +
+ dispatch('tab-change', event.detail)} /> +
+ + diff --git a/packages/ui/src/tron/handle-unsupported-features.ts b/packages/ui/src/tron/handle-unsupported-features.ts new file mode 100644 index 000000000..b886db0a5 --- /dev/null +++ b/packages/ui/src/tron/handle-unsupported-features.ts @@ -0,0 +1,26 @@ +import type { GenericOptions, Kind } from '@openzeppelin/wizard'; + +/** + * Features that don't apply on TRON. + * + * `superchain` cross-chain bridging is OP Stack-specific (not relevant for TRON). + * Upgradeable contracts: `@openzeppelin/tron-contracts` doesn't yet ship the + * transpiled `*Upgradeable` variants, so the upgradeable downloads are hidden + * via `omitZipHardhat`/`omitZipFoundry` in tron/App.svelte rather than being + * removed from the UI form. + */ +export function defineOmitFeatures(): Map { + const omitFeatures: Map = new Map(); + omitFeatures.set('ERC20', ['superchain']); + omitFeatures.set('Stablecoin', ['superchain']); + omitFeatures.set('RealWorldAsset', ['superchain']); + return omitFeatures; +} + +export function sanitizeOmittedFeatures(opts: GenericOptions) { + if (opts.kind === 'ERC20' || opts.kind === 'Stablecoin' || opts.kind === 'RealWorldAsset') { + if (opts.crossChainBridging === 'superchain') { + opts.crossChainBridging = 'custom'; + } + } +} From 83046f7467f0d843e3e890fcd3fffeae2fb5949d Mon Sep 17 00:00:00 2001 From: Kostadin Madzherski Date: Wed, 27 May 2026 19:40:11 +0300 Subject: [PATCH 02/20] Add TRON tools to MCP + CLI packages Mirrors the existing per-ecosystem tool surface for TRON, so AI agents (via MCP) and CLI users get TRC-correct source out of the box instead of needing to manually rewrite imports/symbol names from solidity-* output. What's new: - packages/common/src/ai/descriptions/tron.ts -- TRON-flavored prompts ("Make a fungible token per the TRC-20 standard, ..."). Reused by both MCP and CLI. - packages/mcp/src/tron/ -- seven new MCP tools: tron-trc20, tron-trc721, tron-trc1155, tron-stablecoin, tron-rwa, tron-governor, tron-custom Each wraps the corresponding @openzeppelin/wizard print function with rewriteForTron(...). Schemas are reused verbatim from the Solidity schemas (options are identical; only the source-level rendering differs). Account is intentionally excluded -- same as the UI. - packages/cli/src/registry.ts -- seven new tron-* CLI commands, same shape and same rewriteForTron wrapping. Notes: - Solidity schemas are deliberately reused; no separate tron-* schemas were added. If TRON ever diverges in option shape (e.g. an upgradeable enum value that doesn't apply), the schemas can fork at that point. - Upgradeable options stay accepted in the schemas. When upgradeable is set on a TRON tool, the rewriter still produces TRC* names + tron paths. Downstream usability of those imports still depends on @openzeppelin/tron-contracts shipping upgradeable variants -- same gap noted in the main PR description. Tests: - 15 new MCP ava tests across the 7 tools (all 78 mcp tests passing) - 12 new CLI ava tests (7 auto-generated tron-* --help snapshots + 5 direct equivalence checks; all 89 cli tests passing) - Lint clean on all new files Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/cli/src/cli.test.ts | 37 +++- packages/cli/src/cli.test.ts.md | 186 +++++++++++++++++- packages/cli/src/cli.test.ts.snap | Bin 9830 -> 11590 bytes packages/cli/src/registry.ts | 47 ++++- packages/common/src/ai/descriptions/tron.ts | 16 ++ packages/common/src/index.ts | 1 + packages/mcp/src/index.ts | 1 + packages/mcp/src/server.ts | 2 + packages/mcp/src/tron/tools.ts | 22 +++ packages/mcp/src/tron/tools/custom.test.ts | 55 ++++++ packages/mcp/src/tron/tools/custom.ts | 31 +++ packages/mcp/src/tron/tools/erc1155.test.ts | 63 ++++++ packages/mcp/src/tron/tools/erc1155.ts | 36 ++++ packages/mcp/src/tron/tools/erc20.test.ts | 85 ++++++++ packages/mcp/src/tron/tools/erc20.ts | 59 ++++++ packages/mcp/src/tron/tools/erc721.test.ts | 67 +++++++ packages/mcp/src/tron/tools/erc721.ts | 55 ++++++ packages/mcp/src/tron/tools/governor.test.ts | 68 +++++++ packages/mcp/src/tron/tools/governor.ts | 59 ++++++ packages/mcp/src/tron/tools/rwa.test.ts | 71 +++++++ packages/mcp/src/tron/tools/rwa.ts | 61 ++++++ .../mcp/src/tron/tools/stablecoin.test.ts | 71 +++++++ packages/mcp/src/tron/tools/stablecoin.ts | 61 ++++++ 23 files changed, 1149 insertions(+), 5 deletions(-) create mode 100644 packages/common/src/ai/descriptions/tron.ts create mode 100644 packages/mcp/src/tron/tools.ts create mode 100644 packages/mcp/src/tron/tools/custom.test.ts create mode 100644 packages/mcp/src/tron/tools/custom.ts create mode 100644 packages/mcp/src/tron/tools/erc1155.test.ts create mode 100644 packages/mcp/src/tron/tools/erc1155.ts create mode 100644 packages/mcp/src/tron/tools/erc20.test.ts create mode 100644 packages/mcp/src/tron/tools/erc20.ts create mode 100644 packages/mcp/src/tron/tools/erc721.test.ts create mode 100644 packages/mcp/src/tron/tools/erc721.ts create mode 100644 packages/mcp/src/tron/tools/governor.test.ts create mode 100644 packages/mcp/src/tron/tools/governor.ts create mode 100644 packages/mcp/src/tron/tools/rwa.test.ts create mode 100644 packages/mcp/src/tron/tools/rwa.ts create mode 100644 packages/mcp/src/tron/tools/stablecoin.test.ts create mode 100644 packages/mcp/src/tron/tools/stablecoin.ts diff --git a/packages/cli/src/cli.test.ts b/packages/cli/src/cli.test.ts index 9606cafc8..3f23597a6 100644 --- a/packages/cli/src/cli.test.ts +++ b/packages/cli/src/cli.test.ts @@ -3,7 +3,7 @@ import { execFileSync } from 'node:child_process'; import { readdir, readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { erc20, stablecoin } from '@openzeppelin/wizard'; +import { erc20, erc721, erc1155, stablecoin, rewriteForTron } from '@openzeppelin/wizard'; import { registry } from './registry'; const CLI = join(__dirname, '..', 'dist', 'index.js'); @@ -195,3 +195,38 @@ test('nested dot options with multiple fields', t => { }), ); }); + +// --- TRON --- + +test('tron-trc20 rewrites ERC20 to TRC20', t => { + const output = run('tron-trc20', '--name', 'TestToken', '--symbol', 'TST'); + t.is(output, rewriteForTron(erc20.print({ name: 'TestToken', symbol: 'TST' }))); + t.true(output.includes('TRC20'), 'output should contain TRC20'); + t.true( + output.includes('@openzeppelin/tron-contracts/token/TRC20/TRC20.sol'), + 'output should import from @openzeppelin/tron-contracts', + ); +}); + +test('tron-trc721 rewrites ERC721 to TRC721', t => { + const output = run('tron-trc721', '--name', 'TestNFT', '--symbol', 'TNFT'); + t.is(output, rewriteForTron(erc721.print({ name: 'TestNFT', symbol: 'TNFT' }))); + t.true(output.includes('TRC721'), 'output should contain TRC721'); +}); + +test('tron-trc1155 rewrites ERC1155 to TRC1155', t => { + const output = run('tron-trc1155', '--name', 'TestMulti', '--uri', 'ipfs://example/{id}'); + t.is(output, rewriteForTron(erc1155.print({ name: 'TestMulti', uri: 'ipfs://example/{id}' }))); + t.true(output.includes('TRC1155'), 'output should contain TRC1155'); +}); + +test('tron-trc20 caps pragma at 0.8.26', t => { + const output = run('tron-trc20', '--name', 'TestToken', '--symbol', 'TST'); + t.true(output.includes('pragma solidity ^0.8.26;'), 'pragma should be capped at 0.8.26'); + t.false(output.includes('pragma solidity ^0.8.27'), 'pragma should not be 0.8.27 (above tron-solc max)'); +}); + +test('tron-stablecoin preserves stablecoin features', t => { + const output = run('tron-stablecoin', '--name', 'TestStable', '--symbol', 'TSTB'); + t.is(output, rewriteForTron(stablecoin.print({ name: 'TestStable', symbol: 'TSTB' }))); +}); diff --git a/packages/cli/src/cli.test.ts.md b/packages/cli/src/cli.test.ts.md index f58f44efe..ea9ae61b2 100644 --- a/packages/cli/src/cli.test.ts.md +++ b/packages/cli/src/cli.test.ts.md @@ -11,7 +11,7 @@ Generated by [AVA](https://avajs.dev). `Usage: npx @openzeppelin/contracts-cli [options]␊ ␊ Commands:␊ - solidity-erc20, solidity-erc721, solidity-erc1155, solidity-stablecoin, solidity-rwa, solidity-account, solidity-governor, solidity-custom, cairo-erc20, cairo-erc721, cairo-erc1155, cairo-account, cairo-multisig, cairo-governor, cairo-vesting, cairo-custom, stellar-fungible, stellar-governor, stellar-stablecoin, stellar-non-fungible, stylus-erc20, stylus-erc721, stylus-erc1155, confidential-erc7984, uniswap-hooks␊ + solidity-erc20, solidity-erc721, solidity-erc1155, solidity-stablecoin, solidity-rwa, solidity-account, solidity-governor, solidity-custom, cairo-erc20, cairo-erc721, cairo-erc1155, cairo-account, cairo-multisig, cairo-governor, cairo-vesting, cairo-custom, stellar-fungible, stellar-governor, stellar-stablecoin, stellar-non-fungible, stylus-erc20, stylus-erc721, stylus-erc1155, confidential-erc7984, uniswap-hooks, tron-trc20, tron-trc721, tron-trc1155, tron-stablecoin, tron-rwa, tron-governor, tron-custom␊ ␊ Generated contract source code is printed to stdout.␊ ␊ @@ -25,7 +25,7 @@ Generated by [AVA](https://avajs.dev). `Usage: npx @openzeppelin/contracts-cli [options]␊ ␊ Commands:␊ - solidity-erc20, solidity-erc721, solidity-erc1155, solidity-stablecoin, solidity-rwa, solidity-account, solidity-governor, solidity-custom, cairo-erc20, cairo-erc721, cairo-erc1155, cairo-account, cairo-multisig, cairo-governor, cairo-vesting, cairo-custom, stellar-fungible, stellar-governor, stellar-stablecoin, stellar-non-fungible, stylus-erc20, stylus-erc721, stylus-erc1155, confidential-erc7984, uniswap-hooks␊ + solidity-erc20, solidity-erc721, solidity-erc1155, solidity-stablecoin, solidity-rwa, solidity-account, solidity-governor, solidity-custom, cairo-erc20, cairo-erc721, cairo-erc1155, cairo-account, cairo-multisig, cairo-governor, cairo-vesting, cairo-custom, stellar-fungible, stellar-governor, stellar-stablecoin, stellar-non-fungible, stylus-erc20, stylus-erc721, stylus-erc1155, confidential-erc7984, uniswap-hooks, tron-trc20, tron-trc721, tron-trc1155, tron-stablecoin, tron-rwa, tron-governor, tron-custom␊ ␊ Generated contract source code is printed to stdout.␊ ␊ @@ -657,6 +657,188 @@ Generated by [AVA](https://avajs.dev). --info.license The license used by the contract, default is "MIT"␊ ` +## tron-trc20 --help + +> Snapshot 1 + + `tron-trc20: Make a fungible token per the TRC-20 standard, targeting the TRON Virtual Machine.␊ + ␊ + Required:␊ + --name The name of the contract␊ + --symbol The short symbol for the token␊ + ␊ + Options:␊ + --burnable Whether token holders will be able to destroy their tokens␊ + --pausable Whether privileged accounts will be able to pause specifically marked functionality. Useful for emergency response.␊ + --premint The number of tokens to premint for the deployer.␊ + --premintChainId The chain ID of the network on which to premint tokens.␊ + --mintable Whether privileged accounts will be able to create more supply or emit more tokens␊ + --callback Whether to include support for code execution after transfers and approvals on recipient contracts in a single transaction.␊ + --permit Whether without paying gas, token holders will be able to allow third parties to transfer from their account.␊ + --votes Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.␊ + --flashmint Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.␊ + --crossChainBridging Whether to allow authorized bridge contracts to mint and burn tokens for cross-chain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.␊ + --crossChainLinkAllowOverride Whether to allow replacing a crosschain link that has already been registered. Only used if crossChainBridging is set to "erc7786native".␊ + --namespacePrefix The prefix for ERC-7201 namespace identifiers. It should be derived from the project name or a unique naming convention specific to the project. Used only if the contract includes storage variables and upgradeability is enabled. Default is "myProject".␊ + --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ + --upgradeable Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract. Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.␊ + --info.securityContact Email where people can contact you to report security issues. Will only be visible if contract source code is verified.␊ + --info.license The license used by the contract, default is "MIT"␊ + ` + +## tron-trc721 --help + +> Snapshot 1 + + `tron-trc721: Make a non-fungible token per the TRC-721 standard, targeting the TRON Virtual Machine.␊ + ␊ + Required:␊ + --name The name of the contract␊ + --symbol The short symbol for the token␊ + ␊ + Options:␊ + --baseUri A base uri for the token␊ + --enumerable Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.␊ + --uriStorage Allows updating token URIs for individual token IDs␊ + --burnable Whether token holders will be able to destroy their tokens␊ + --pausable Whether privileged accounts will be able to pause specifically marked functionality. Useful for emergency response.␊ + --mintable Whether privileged accounts will be able to create more supply or emit more tokens␊ + --incremental Whether new tokens will be automatically assigned an incremental id␊ + --votes Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps (defaulting to block number if not specified).␊ + --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ + --upgradeable Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract. Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.␊ + --info.securityContact Email where people can contact you to report security issues. Will only be visible if contract source code is verified.␊ + --info.license The license used by the contract, default is "MIT"␊ + --namespacePrefix The prefix for ERC-7201 namespace identifiers. It should be derived from the project name or a unique naming convention specific to the project. Used only if the contract includes storage variables and upgradeability is enabled. Default is "myProject".␊ + ` + +## tron-trc1155 --help + +> Snapshot 1 + + `tron-trc1155: Make a multi-token contract per the TRC-1155 standard, targeting the TRON Virtual Machine.␊ + ␊ + Required:␊ + --name The name of the contract␊ + --uri The location of the metadata for the token. Clients will replace any instance of {id} in this string with the tokenId.␊ + ␊ + Options:␊ + --burnable Whether token holders will be able to destroy their tokens␊ + --pausable Whether privileged accounts will be able to pause specifically marked functionality. Useful for emergency response.␊ + --mintable Whether privileged accounts will be able to create more supply or emit more tokens␊ + --supply Whether to keep track of total supply of tokens␊ + --updatableUri Whether privileged accounts will be able to set a new URI for all token types␊ + --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ + --upgradeable Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract. Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.␊ + --info.securityContact Email where people can contact you to report security issues. Will only be visible if contract source code is verified.␊ + --info.license The license used by the contract, default is "MIT"␊ + ` + +## tron-stablecoin --help + +> Snapshot 1 + + `tron-stablecoin: Make a stablecoin token per the TRC-20 standard, targeting the TRON Virtual Machine. Experimental, some features are not audited and are subject to change.␊ + ␊ + Required:␊ + --name The name of the contract␊ + --symbol The short symbol for the token␊ + ␊ + Options:␊ + --burnable Whether token holders will be able to destroy their tokens␊ + --pausable Whether privileged accounts will be able to pause specifically marked functionality. Useful for emergency response.␊ + --premint The number of tokens to premint for the deployer.␊ + --premintChainId The chain ID of the network on which to premint tokens.␊ + --mintable Whether privileged accounts will be able to create more supply or emit more tokens␊ + --callback Whether to include support for code execution after transfers and approvals on recipient contracts in a single transaction.␊ + --permit Whether without paying gas, token holders will be able to allow third parties to transfer from their account.␊ + --votes Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.␊ + --flashmint Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.␊ + --crossChainBridging Whether to allow authorized bridge contracts to mint and burn tokens for cross-chain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.␊ + --crossChainLinkAllowOverride Whether to allow replacing a crosschain link that has already been registered. Only used if crossChainBridging is set to "erc7786native".␊ + --namespacePrefix The prefix for ERC-7201 namespace identifiers. It should be derived from the project name or a unique naming convention specific to the project. Used only if the contract includes storage variables and upgradeability is enabled. Default is "myProject".␊ + --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ + --info.securityContact Email where people can contact you to report security issues. Will only be visible if contract source code is verified.␊ + --info.license The license used by the contract, default is "MIT"␊ + --restrictions Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.␊ + --freezable Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.␊ + ` + +## tron-rwa --help + +> Snapshot 1 + + `tron-rwa: Make a real-world asset token per the TRC-20 standard, targeting the TRON Virtual Machine. Experimental, some features are not audited and are subject to change.␊ + ␊ + Required:␊ + --name The name of the contract␊ + --symbol The short symbol for the token␊ + ␊ + Options:␊ + --burnable Whether token holders will be able to destroy their tokens␊ + --pausable Whether privileged accounts will be able to pause specifically marked functionality. Useful for emergency response.␊ + --premint The number of tokens to premint for the deployer.␊ + --premintChainId The chain ID of the network on which to premint tokens.␊ + --mintable Whether privileged accounts will be able to create more supply or emit more tokens␊ + --callback Whether to include support for code execution after transfers and approvals on recipient contracts in a single transaction.␊ + --permit Whether without paying gas, token holders will be able to allow third parties to transfer from their account.␊ + --votes Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.␊ + --flashmint Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.␊ + --crossChainBridging Whether to allow authorized bridge contracts to mint and burn tokens for cross-chain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.␊ + --crossChainLinkAllowOverride Whether to allow replacing a crosschain link that has already been registered. Only used if crossChainBridging is set to "erc7786native".␊ + --namespacePrefix The prefix for ERC-7201 namespace identifiers. It should be derived from the project name or a unique naming convention specific to the project. Used only if the contract includes storage variables and upgradeability is enabled. Default is "myProject".␊ + --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ + --info.securityContact Email where people can contact you to report security issues. Will only be visible if contract source code is verified.␊ + --info.license The license used by the contract, default is "MIT"␊ + --restrictions Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.␊ + --freezable Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.␊ + ` + +## tron-governor --help + +> Snapshot 1 + + `tron-governor: Make a contract to implement governance, such as for a DAO, targeting the TRON Virtual Machine.␊ + ␊ + Required:␊ + --name The name of the contract␊ + --delay The delay since proposal is created until voting starts, default is "1 day"␊ + --period The length of period during which people can cast their vote, default is "1 week"␊ + ␊ + Options:␊ + --votes The type of voting to use␊ + --clockMode The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.␊ + --timelock The type of timelock to use␊ + --blockTime The block time of the chain in seconds, default is 12␊ + --decimals The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)␊ + --proposalThreshold Minimum number of votes an account must have to create a proposal, default is 0.␊ + --quorumMode The type of quorum mode to use␊ + --quorumPercent The percent required, in cases of quorumMode equals percent␊ + --quorumAbsolute The absolute quorum required, in cases of quorumMode equals absolute␊ + --storage Enable storage of proposal details and enumerability of proposals␊ + --settings Allow governance to update voting settings (delay, period, proposal threshold)␊ + --upgradeable Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract. Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.␊ + --info.securityContact Email where people can contact you to report security issues. Will only be visible if contract source code is verified.␊ + --info.license The license used by the contract, default is "MIT"␊ + ` + +## tron-custom --help + +> Snapshot 1 + + `tron-custom: Make a custom smart contract, targeting the TRON Virtual Machine.␊ + ␊ + Required:␊ + --name The name of the contract␊ + ␊ + Options:␊ + --pausable Whether privileged accounts will be able to pause specifically marked functionality. Useful for emergency response.␊ + --access The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.␊ + --upgradeable Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract. Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.␊ + --info.securityContact Email where people can contact you to report security issues. Will only be visible if contract source code is verified.␊ + --info.license The license used by the contract, default is "MIT"␊ + ` + ## unknown command > Snapshot 1 diff --git a/packages/cli/src/cli.test.ts.snap b/packages/cli/src/cli.test.ts.snap index 83e0ebb063d4c11cc812717e745010f22d6c5e1f..1e0b6ca5a25137cbae7e78097ce7bd35cfb4db41 100644 GIT binary patch literal 11590 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 L2Fp|o9PEDqUzSE* literal 9830 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 hooks.print(opts), uniswapHooksPrompts.Hooks), + + // TRON (Solidity post-processed for @openzeppelin/tron-contracts + TRC* token names) + 'tron-trc20': createRegistryEntry(solidityERC20Schema, opts => rewriteForTron(erc20.print(opts)), tronPrompts.TRC20), + 'tron-trc721': createRegistryEntry( + solidityERC721Schema, + opts => rewriteForTron(erc721.print(opts)), + tronPrompts.TRC721, + ), + 'tron-trc1155': createRegistryEntry( + solidityERC1155Schema, + opts => rewriteForTron(erc1155.print(opts)), + tronPrompts.TRC1155, + ), + 'tron-stablecoin': createRegistryEntry( + solidityStablecoinSchema, + opts => rewriteForTron(stablecoin.print(opts)), + tronPrompts.Stablecoin, + ), + 'tron-rwa': createRegistryEntry( + solidityRWASchema, + opts => rewriteForTron(realWorldAsset.print(opts)), + tronPrompts.RWA, + ), + 'tron-governor': createRegistryEntry( + solidityGovernorSchema, + opts => rewriteForTron(governor.print(opts)), + tronPrompts.Governor, + ), + 'tron-custom': createRegistryEntry( + solidityCustomSchema, + opts => rewriteForTron(custom.print(opts)), + tronPrompts.Custom, + ), } satisfies Record; diff --git a/packages/common/src/ai/descriptions/tron.ts b/packages/common/src/ai/descriptions/tron.ts new file mode 100644 index 000000000..0a34551d5 --- /dev/null +++ b/packages/common/src/ai/descriptions/tron.ts @@ -0,0 +1,16 @@ +// IMPORTANT: This file must not have any imports since it is used in both Node and Deno environments, +// which have different requirements for file extensions in import statements. + +// TRON prompts. Used by MCP + CLI tron-* tools. Options are otherwise +// identical to the Solidity ecosystem; only the standard names + library +// import paths differ in the output (handled by `rewriteForTron`). +export const tronPrompts = { + TRC20: 'Make a fungible token per the TRC-20 standard, targeting the TRON Virtual Machine.', + TRC721: 'Make a non-fungible token per the TRC-721 standard, targeting the TRON Virtual Machine.', + TRC1155: 'Make a multi-token contract per the TRC-1155 standard, targeting the TRON Virtual Machine.', + Stablecoin: + 'Make a stablecoin token per the TRC-20 standard, targeting the TRON Virtual Machine. Experimental, some features are not audited and are subject to change.', + RWA: 'Make a real-world asset token per the TRC-20 standard, targeting the TRON Virtual Machine. Experimental, some features are not audited and are subject to change.', + Governor: 'Make a contract to implement governance, such as for a DAO, targeting the TRON Virtual Machine.', + Custom: 'Make a custom smart contract, targeting the TRON Virtual Machine.', +}; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 35b07f282..881358831 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -4,5 +4,6 @@ export * from './ai/descriptions/confidential'; export * from './ai/descriptions/solidity'; export * from './ai/descriptions/stellar'; export * from './ai/descriptions/stylus'; +export * from './ai/descriptions/tron'; export * from './ai/descriptions/uniswap-hooks'; export * from './utils/object'; diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index 7b8013797..be7757b0e 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -3,4 +3,5 @@ export { registerCairoTools } from './cairo/tools'; export { registerConfidentialTools } from './confidential/tools'; export { registerStellarTools } from './stellar/tools'; export { registerStylusTools } from './stylus/tools'; +export { registerTronTools } from './tron/tools'; export { registerUniswapHooksTools } from './uniswap-hooks/tools'; diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index dd786937f..1bc7dedfe 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -4,6 +4,7 @@ import { registerCairoTools } from './cairo/tools.js'; import { registerConfidentialTools } from './confidential/tools.js'; import { registerStellarTools } from './stellar/tools.js'; import { registerStylusTools } from './stylus/tools.js'; +import { registerTronTools } from './tron/tools.js'; import { registerUniswapHooksTools } from './uniswap-hooks/tools.js'; import { version } from '../package.json'; @@ -28,6 +29,7 @@ If the user asks to modify an existing smart contract, use these tools to determ registerConfidentialTools(server); registerStellarTools(server); registerStylusTools(server); + registerTronTools(server); registerUniswapHooksTools(server); return server; diff --git a/packages/mcp/src/tron/tools.ts b/packages/mcp/src/tron/tools.ts new file mode 100644 index 000000000..5d6163c27 --- /dev/null +++ b/packages/mcp/src/tron/tools.ts @@ -0,0 +1,22 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerTronTRC20 } from './tools/erc20.js'; +import { registerTronTRC721 } from './tools/erc721.js'; +import { registerTronTRC1155 } from './tools/erc1155.js'; +import { registerTronStablecoin } from './tools/stablecoin.js'; +import { registerTronRWA } from './tools/rwa.js'; +import { registerTronGovernor } from './tools/governor.js'; +import { registerTronCustom } from './tools/custom.js'; + +// TRON tools reuse the Solidity schemas (options are identical) and pipe the +// output through `rewriteForTron` so token standards become TRC* and imports +// resolve from `@openzeppelin/tron-contracts`. Account is intentionally +// excluded — ERC-4337 EntryPoint is not in TRON support scope. +export function registerTronTools(server: McpServer) { + registerTronTRC20(server); + registerTronTRC721(server); + registerTronTRC1155(server); + registerTronStablecoin(server); + registerTronRWA(server); + registerTronGovernor(server); + registerTronCustom(server); +} diff --git a/packages/mcp/src/tron/tools/custom.test.ts b/packages/mcp/src/tron/tools/custom.test.ts new file mode 100644 index 000000000..ccee0e516 --- /dev/null +++ b/packages/mcp/src/tron/tools/custom.test.ts @@ -0,0 +1,55 @@ +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerTronCustom } from './custom'; +import type { DeepRequired } from '../../helpers.test'; +import { testMcpInfo, assertAPIEquivalence } from '../../helpers.test'; +import type { CustomOptions } from '@openzeppelin/wizard'; +import { custom, rewriteForTron } from '@openzeppelin/wizard'; +import { solidityCustomSchema } from '@openzeppelin/wizard-common/schemas'; +import { z } from 'zod'; + +interface Context { + tool: RegisteredTool; + schema: z.ZodObject; +} + +const test = _test as TestFn; + +test.before(t => { + t.context.tool = registerTronCustom(new McpServer(testMcpInfo)); + t.context.schema = z.object(solidityCustomSchema); +}); + +function assertHasAllSupportedFields( + t: ExecutionContext, + params: DeepRequired>, +) { + const _: DeepRequired = params; + t.pass(); +} + +const tronPrint = (opts: CustomOptions) => rewriteForTron(custom.print(opts)); + +test('basic', async t => { + const params: z.infer = { + name: 'MyCustom', + }; + await assertAPIEquivalence(t, params, tronPrint); +}); + +test('all', async t => { + const params: DeepRequired> = { + name: 'MyCustom', + pausable: true, + access: 'roles', + upgradeable: 'uups', + info: { + license: 'MIT', + securityContact: 'security@example.com', + }, + }; + assertHasAllSupportedFields(t, params); + await assertAPIEquivalence(t, params, tronPrint); +}); diff --git a/packages/mcp/src/tron/tools/custom.ts b/packages/mcp/src/tron/tools/custom.ts new file mode 100644 index 000000000..4f62cfb81 --- /dev/null +++ b/packages/mcp/src/tron/tools/custom.ts @@ -0,0 +1,31 @@ +import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { CustomOptions } from '@openzeppelin/wizard'; +import { custom, rewriteForTron } from '@openzeppelin/wizard'; +import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; +import { solidityCustomSchema } from '@openzeppelin/wizard-common/schemas'; +import { tronPrompts } from '@openzeppelin/wizard-common'; + +export function registerTronCustom(server: McpServer): RegisteredTool { + return server.tool( + 'tron-custom', + makeDetailedPrompt(tronPrompts.Custom), + solidityCustomSchema, + async ({ name, pausable, access, upgradeable, info }) => { + const opts: CustomOptions = { + name, + pausable, + access, + upgradeable, + info, + }; + return { + content: [ + { + type: 'text', + text: safePrintSolidityCodeBlock(() => rewriteForTron(custom.print(opts))), + }, + ], + }; + }, + ); +} diff --git a/packages/mcp/src/tron/tools/erc1155.test.ts b/packages/mcp/src/tron/tools/erc1155.test.ts new file mode 100644 index 000000000..47951dda7 --- /dev/null +++ b/packages/mcp/src/tron/tools/erc1155.test.ts @@ -0,0 +1,63 @@ +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerTronTRC1155 } from './erc1155'; +import type { DeepRequired } from '../../helpers.test'; +import { testMcpInfo, assertAPIEquivalence } from '../../helpers.test'; +import type { ERC1155Options } from '@openzeppelin/wizard'; +import { erc1155, rewriteForTron } from '@openzeppelin/wizard'; +import { solidityERC1155Schema } from '@openzeppelin/wizard-common/schemas'; +import { z } from 'zod'; + +interface Context { + tool: RegisteredTool; + schema: z.ZodObject; +} + +const test = _test as TestFn; + +test.before(t => { + t.context.tool = registerTronTRC1155(new McpServer(testMcpInfo)); + t.context.schema = z.object(solidityERC1155Schema); +}); + +function assertHasAllSupportedFields( + t: ExecutionContext, + params: DeepRequired>, +) { + const _: DeepRequired = params; + t.pass(); +} + +const tronPrint = (opts: ERC1155Options) => rewriteForTron(erc1155.print(opts)); + +test('basic', async t => { + const params: z.infer = { + name: 'TestMulti', + uri: 'ipfs://example/{id}', + }; + await assertAPIEquivalence(t, params, tronPrint); +}); + +test('all', async t => { + const params: DeepRequired> = { + name: 'TestMulti', + uri: 'ipfs://example/{id}', + burnable: true, + pausable: true, + mintable: true, + supply: true, + updatableUri: true, + access: 'roles', + upgradeable: 'transparent', + info: { + license: 'MIT', + securityContact: 'security@example.com', + }, + }; + + assertHasAllSupportedFields(t, params); + + await assertAPIEquivalence(t, params, tronPrint); +}); diff --git a/packages/mcp/src/tron/tools/erc1155.ts b/packages/mcp/src/tron/tools/erc1155.ts new file mode 100644 index 000000000..ee4ee683b --- /dev/null +++ b/packages/mcp/src/tron/tools/erc1155.ts @@ -0,0 +1,36 @@ +import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { ERC1155Options } from '@openzeppelin/wizard'; +import { erc1155, rewriteForTron } from '@openzeppelin/wizard'; +import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; +import { solidityERC1155Schema } from '@openzeppelin/wizard-common/schemas'; +import { tronPrompts } from '@openzeppelin/wizard-common'; + +export function registerTronTRC1155(server: McpServer): RegisteredTool { + return server.tool( + 'tron-trc1155', + makeDetailedPrompt(tronPrompts.TRC1155), + solidityERC1155Schema, + async ({ name, uri, burnable, pausable, mintable, supply, updatableUri, access, upgradeable, info }) => { + const opts: ERC1155Options = { + name, + uri, + burnable, + pausable, + mintable, + supply, + updatableUri, + access, + upgradeable, + info, + }; + return { + content: [ + { + type: 'text', + text: safePrintSolidityCodeBlock(() => rewriteForTron(erc1155.print(opts))), + }, + ], + }; + }, + ); +} diff --git a/packages/mcp/src/tron/tools/erc20.test.ts b/packages/mcp/src/tron/tools/erc20.test.ts new file mode 100644 index 000000000..e460b0bd2 --- /dev/null +++ b/packages/mcp/src/tron/tools/erc20.test.ts @@ -0,0 +1,85 @@ +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerTronTRC20 } from './erc20'; +import type { DeepRequired } from '../../helpers.test'; +import { testMcpInfo, assertAPIEquivalence } from '../../helpers.test'; +import type { ERC20Options } from '@openzeppelin/wizard'; +import { erc20, rewriteForTron } from '@openzeppelin/wizard'; +import { solidityERC20Schema } from '@openzeppelin/wizard-common/schemas'; +import { z } from 'zod'; + +interface Context { + tool: RegisteredTool; + schema: z.ZodObject; +} + +const test = _test as TestFn; + +test.before(t => { + t.context.tool = registerTronTRC20(new McpServer(testMcpInfo)); + t.context.schema = z.object(solidityERC20Schema); +}); + +function assertHasAllSupportedFields( + t: ExecutionContext, + params: DeepRequired>, +) { + const _: DeepRequired = params; + t.pass(); +} + +// The TRON tool output is the Solidity Wizard's ERC20 source piped through +// `rewriteForTron`, so we use the same helper but pre-transform the expected. +const tronPrint = (opts: ERC20Options) => rewriteForTron(erc20.print(opts)); + +test('basic', async t => { + const params: z.infer = { + name: 'TestToken', + symbol: 'TST', + }; + await assertAPIEquivalence(t, params, tronPrint); +}); + +test('renames ERC20 to TRC20 and rewrites imports', async t => { + // assertAPIEquivalence verifies the MCP tool output contains the result of + // tronPrint(params) — which has already been rewritten to TRC20 + + // @openzeppelin/tron-contracts. So if this passes, the tool is rewriting. + const params: z.infer = { + name: 'TestToken', + symbol: 'TST', + burnable: true, + permit: true, + }; + await assertAPIEquivalence(t, params, tronPrint); +}); + +test('all', async t => { + const params: DeepRequired> = { + name: 'TestToken', + symbol: 'TST', + burnable: true, + pausable: true, + premint: '1000000', + premintChainId: '1', + mintable: true, + callback: true, + permit: true, + votes: 'blocknumber', + flashmint: true, + crossChainBridging: 'custom', + crossChainLinkAllowOverride: false, + access: 'roles', + upgradeable: 'transparent', + namespacePrefix: 'myProject', + info: { + license: 'MIT', + securityContact: 'security@example.com', + }, + }; + + assertHasAllSupportedFields(t, params); + + await assertAPIEquivalence(t, params, tronPrint); +}); diff --git a/packages/mcp/src/tron/tools/erc20.ts b/packages/mcp/src/tron/tools/erc20.ts new file mode 100644 index 000000000..236a53227 --- /dev/null +++ b/packages/mcp/src/tron/tools/erc20.ts @@ -0,0 +1,59 @@ +import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { ERC20Options } from '@openzeppelin/wizard'; +import { erc20, rewriteForTron } from '@openzeppelin/wizard'; +import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; +import { solidityERC20Schema } from '@openzeppelin/wizard-common/schemas'; +import { tronPrompts } from '@openzeppelin/wizard-common'; + +export function registerTronTRC20(server: McpServer): RegisteredTool { + return server.tool( + 'tron-trc20', + makeDetailedPrompt(tronPrompts.TRC20), + solidityERC20Schema, + async ({ + name, + symbol, + burnable, + pausable, + premint, + premintChainId, + mintable, + callback, + permit, + votes, + flashmint, + crossChainBridging, + crossChainLinkAllowOverride, + access, + upgradeable, + info, + }) => { + const opts: ERC20Options = { + name, + symbol, + burnable, + pausable, + premint, + premintChainId, + mintable, + callback, + permit, + votes, + flashmint, + crossChainBridging, + crossChainLinkAllowOverride, + access, + upgradeable, + info, + }; + return { + content: [ + { + type: 'text', + text: safePrintSolidityCodeBlock(() => rewriteForTron(erc20.print(opts))), + }, + ], + }; + }, + ); +} diff --git a/packages/mcp/src/tron/tools/erc721.test.ts b/packages/mcp/src/tron/tools/erc721.test.ts new file mode 100644 index 000000000..441f251bc --- /dev/null +++ b/packages/mcp/src/tron/tools/erc721.test.ts @@ -0,0 +1,67 @@ +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerTronTRC721 } from './erc721'; +import type { DeepRequired } from '../../helpers.test'; +import { testMcpInfo, assertAPIEquivalence } from '../../helpers.test'; +import type { ERC721Options } from '@openzeppelin/wizard'; +import { erc721, rewriteForTron } from '@openzeppelin/wizard'; +import { solidityERC721Schema } from '@openzeppelin/wizard-common/schemas'; +import { z } from 'zod'; + +interface Context { + tool: RegisteredTool; + schema: z.ZodObject; +} + +const test = _test as TestFn; + +test.before(t => { + t.context.tool = registerTronTRC721(new McpServer(testMcpInfo)); + t.context.schema = z.object(solidityERC721Schema); +}); + +function assertHasAllSupportedFields( + t: ExecutionContext, + params: DeepRequired>, +) { + const _: DeepRequired = params; + t.pass(); +} + +const tronPrint = (opts: ERC721Options) => rewriteForTron(erc721.print(opts)); + +test('basic', async t => { + const params: z.infer = { + name: 'TestNFT', + symbol: 'TNFT', + }; + await assertAPIEquivalence(t, params, tronPrint); +}); + +test('all', async t => { + const params: DeepRequired> = { + name: 'TestNFT', + symbol: 'TNFT', + baseUri: 'https://example.com/', + enumerable: true, + uriStorage: true, + burnable: true, + pausable: true, + mintable: true, + incremental: true, + votes: 'blocknumber', + access: 'roles', + upgradeable: 'transparent', + namespacePrefix: 'myProject', + info: { + license: 'MIT', + securityContact: 'security@example.com', + }, + }; + + assertHasAllSupportedFields(t, params); + + await assertAPIEquivalence(t, params, tronPrint); +}); diff --git a/packages/mcp/src/tron/tools/erc721.ts b/packages/mcp/src/tron/tools/erc721.ts new file mode 100644 index 000000000..eb3119587 --- /dev/null +++ b/packages/mcp/src/tron/tools/erc721.ts @@ -0,0 +1,55 @@ +import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { ERC721Options } from '@openzeppelin/wizard'; +import { erc721, rewriteForTron } from '@openzeppelin/wizard'; +import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; +import { solidityERC721Schema } from '@openzeppelin/wizard-common/schemas'; +import { tronPrompts } from '@openzeppelin/wizard-common'; + +export function registerTronTRC721(server: McpServer): RegisteredTool { + return server.tool( + 'tron-trc721', + makeDetailedPrompt(tronPrompts.TRC721), + solidityERC721Schema, + async ({ + name, + symbol, + baseUri, + enumerable, + uriStorage, + burnable, + pausable, + mintable, + incremental, + votes, + access, + upgradeable, + namespacePrefix, + info, + }) => { + const opts: ERC721Options = { + name, + symbol, + baseUri, + enumerable, + uriStorage, + burnable, + pausable, + mintable, + incremental, + votes, + access, + upgradeable, + namespacePrefix, + info, + }; + return { + content: [ + { + type: 'text', + text: safePrintSolidityCodeBlock(() => rewriteForTron(erc721.print(opts))), + }, + ], + }; + }, + ); +} diff --git a/packages/mcp/src/tron/tools/governor.test.ts b/packages/mcp/src/tron/tools/governor.test.ts new file mode 100644 index 000000000..3d583db4b --- /dev/null +++ b/packages/mcp/src/tron/tools/governor.test.ts @@ -0,0 +1,68 @@ +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerTronGovernor } from './governor'; +import type { DeepRequired } from '../../helpers.test'; +import { testMcpInfo, assertAPIEquivalence } from '../../helpers.test'; +import type { GovernorOptions } from '@openzeppelin/wizard'; +import { governor, rewriteForTron } from '@openzeppelin/wizard'; +import { solidityGovernorSchema } from '@openzeppelin/wizard-common/schemas'; +import { z } from 'zod'; + +interface Context { + tool: RegisteredTool; + schema: z.ZodObject; +} + +const test = _test as TestFn; + +test.before(t => { + t.context.tool = registerTronGovernor(new McpServer(testMcpInfo)); + t.context.schema = z.object(solidityGovernorSchema); +}); + +function assertHasAllSupportedFields( + t: ExecutionContext, + params: DeepRequired>, +) { + const _: DeepRequired> = params; + t.pass(); +} + +const tronPrint = (opts: GovernorOptions) => rewriteForTron(governor.print(opts)); + +test('basic', async t => { + const params: z.infer = { + name: 'MyGovernor', + delay: '1 day', + period: '1 week', + }; + await assertAPIEquivalence(t, params, tronPrint); +}); + +test('all', async t => { + const params: DeepRequired> = { + name: 'MyGovernor', + delay: '1 day', + period: '1 week', + votes: 'erc20votes', + clockMode: 'blocknumber', + timelock: 'openzeppelin', + blockTime: 12, + decimals: 18, + proposalThreshold: '1', + quorumMode: 'absolute', + quorumPercent: 0, + quorumAbsolute: '5', + storage: true, + settings: true, + upgradeable: 'uups', + info: { + license: 'MIT', + securityContact: 'security@example.com', + }, + }; + assertHasAllSupportedFields(t, params); + await assertAPIEquivalence(t, params, tronPrint); +}); diff --git a/packages/mcp/src/tron/tools/governor.ts b/packages/mcp/src/tron/tools/governor.ts new file mode 100644 index 000000000..55c82d055 --- /dev/null +++ b/packages/mcp/src/tron/tools/governor.ts @@ -0,0 +1,59 @@ +import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { GovernorOptions } from '@openzeppelin/wizard'; +import { governor, rewriteForTron } from '@openzeppelin/wizard'; +import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; +import { solidityGovernorSchema } from '@openzeppelin/wizard-common/schemas'; +import { tronPrompts } from '@openzeppelin/wizard-common'; + +export function registerTronGovernor(server: McpServer): RegisteredTool { + return server.tool( + 'tron-governor', + makeDetailedPrompt(tronPrompts.Governor), + solidityGovernorSchema, + async ({ + name, + delay, + period, + votes, + clockMode, + timelock, + blockTime, + decimals, + proposalThreshold, + quorumMode, + quorumPercent, + quorumAbsolute, + storage, + settings, + upgradeable, + info, + }) => { + const opts: GovernorOptions = { + name, + delay, + period, + votes, + clockMode, + timelock, + blockTime, + decimals, + proposalThreshold, + quorumMode, + quorumPercent, + quorumAbsolute, + storage, + settings, + upgradeable, + info, + }; + return { + content: [ + { + type: 'text', + text: safePrintSolidityCodeBlock(() => rewriteForTron(governor.print(opts))), + }, + ], + }; + }, + ); +} diff --git a/packages/mcp/src/tron/tools/rwa.test.ts b/packages/mcp/src/tron/tools/rwa.test.ts new file mode 100644 index 000000000..19fd3a401 --- /dev/null +++ b/packages/mcp/src/tron/tools/rwa.test.ts @@ -0,0 +1,71 @@ +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerTronRWA } from './rwa'; +import type { DeepRequired } from '../../helpers.test'; +import { testMcpInfo, assertAPIEquivalence } from '../../helpers.test'; +import type { StablecoinOptions } from '@openzeppelin/wizard'; +import { realWorldAsset, rewriteForTron } from '@openzeppelin/wizard'; +import { solidityRWASchema } from '@openzeppelin/wizard-common/schemas'; +import { z } from 'zod'; + +interface Context { + tool: RegisteredTool; + schema: z.ZodObject; +} + +const test = _test as TestFn; + +test.before(t => { + t.context.tool = registerTronRWA(new McpServer(testMcpInfo)); + t.context.schema = z.object(solidityRWASchema); +}); + +function assertHasAllSupportedFields( + t: ExecutionContext, + params: DeepRequired>, +) { + const _: DeepRequired> = params; + t.pass(); +} + +const tronPrint = (opts: StablecoinOptions) => rewriteForTron(realWorldAsset.print(opts)); + +test('basic', async t => { + const params: z.infer = { + name: 'TestRWA', + symbol: 'TRWA', + }; + await assertAPIEquivalence(t, params, tronPrint); +}); + +test('all', async t => { + const params: DeepRequired> = { + name: 'TestRWA', + symbol: 'TRWA', + burnable: true, + pausable: true, + premint: '1000000', + premintChainId: '1', + mintable: true, + callback: true, + permit: true, + votes: 'blocknumber', + flashmint: true, + crossChainBridging: 'custom', + crossChainLinkAllowOverride: false, + access: 'roles', + restrictions: 'allowlist', + freezable: true, + namespacePrefix: 'myProject', + info: { + license: 'MIT', + securityContact: 'security@example.com', + }, + }; + + assertHasAllSupportedFields(t, params); + + await assertAPIEquivalence(t, params, tronPrint); +}); diff --git a/packages/mcp/src/tron/tools/rwa.ts b/packages/mcp/src/tron/tools/rwa.ts new file mode 100644 index 000000000..4c41e63ed --- /dev/null +++ b/packages/mcp/src/tron/tools/rwa.ts @@ -0,0 +1,61 @@ +import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { StablecoinOptions } from '@openzeppelin/wizard'; +import { realWorldAsset, rewriteForTron } from '@openzeppelin/wizard'; +import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; +import { solidityRWASchema } from '@openzeppelin/wizard-common/schemas'; +import { tronPrompts } from '@openzeppelin/wizard-common'; + +export function registerTronRWA(server: McpServer): RegisteredTool { + return server.tool( + 'tron-rwa', + makeDetailedPrompt(tronPrompts.RWA), + solidityRWASchema, + async ({ + name, + symbol, + burnable, + pausable, + premint, + premintChainId, + mintable, + callback, + permit, + votes, + flashmint, + crossChainBridging, + crossChainLinkAllowOverride, + access, + info, + restrictions, + freezable, + }) => { + const opts: StablecoinOptions = { + name, + symbol, + burnable, + pausable, + premint, + premintChainId, + mintable, + callback, + permit, + votes, + flashmint, + crossChainBridging, + crossChainLinkAllowOverride, + access, + info, + restrictions, + freezable, + }; + return { + content: [ + { + type: 'text', + text: safePrintSolidityCodeBlock(() => rewriteForTron(realWorldAsset.print(opts))), + }, + ], + }; + }, + ); +} diff --git a/packages/mcp/src/tron/tools/stablecoin.test.ts b/packages/mcp/src/tron/tools/stablecoin.test.ts new file mode 100644 index 000000000..9a76094a6 --- /dev/null +++ b/packages/mcp/src/tron/tools/stablecoin.test.ts @@ -0,0 +1,71 @@ +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerTronStablecoin } from './stablecoin'; +import type { DeepRequired } from '../../helpers.test'; +import { testMcpInfo, assertAPIEquivalence } from '../../helpers.test'; +import type { StablecoinOptions } from '@openzeppelin/wizard'; +import { stablecoin, rewriteForTron } from '@openzeppelin/wizard'; +import { solidityStablecoinSchema } from '@openzeppelin/wizard-common/schemas'; +import { z } from 'zod'; + +interface Context { + tool: RegisteredTool; + schema: z.ZodObject; +} + +const test = _test as TestFn; + +test.before(t => { + t.context.tool = registerTronStablecoin(new McpServer(testMcpInfo)); + t.context.schema = z.object(solidityStablecoinSchema); +}); + +function assertHasAllSupportedFields( + t: ExecutionContext, + params: DeepRequired>, +) { + const _: DeepRequired> = params; + t.pass(); +} + +const tronPrint = (opts: StablecoinOptions) => rewriteForTron(stablecoin.print(opts)); + +test('basic', async t => { + const params: z.infer = { + name: 'TestStable', + symbol: 'TST', + }; + await assertAPIEquivalence(t, params, tronPrint); +}); + +test('all', async t => { + const params: DeepRequired> = { + name: 'TestStable', + symbol: 'TST', + burnable: true, + pausable: true, + premint: '1000000', + premintChainId: '1', + mintable: true, + callback: true, + permit: true, + votes: 'blocknumber', + flashmint: true, + crossChainBridging: 'custom', + crossChainLinkAllowOverride: false, + access: 'roles', + restrictions: 'allowlist', + freezable: true, + namespacePrefix: 'myProject', + info: { + license: 'MIT', + securityContact: 'security@example.com', + }, + }; + + assertHasAllSupportedFields(t, params); + + await assertAPIEquivalence(t, params, tronPrint); +}); diff --git a/packages/mcp/src/tron/tools/stablecoin.ts b/packages/mcp/src/tron/tools/stablecoin.ts new file mode 100644 index 000000000..bc77de622 --- /dev/null +++ b/packages/mcp/src/tron/tools/stablecoin.ts @@ -0,0 +1,61 @@ +import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { StablecoinOptions } from '@openzeppelin/wizard'; +import { stablecoin, rewriteForTron } from '@openzeppelin/wizard'; +import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; +import { solidityStablecoinSchema } from '@openzeppelin/wizard-common/schemas'; +import { tronPrompts } from '@openzeppelin/wizard-common'; + +export function registerTronStablecoin(server: McpServer): RegisteredTool { + return server.tool( + 'tron-stablecoin', + makeDetailedPrompt(tronPrompts.Stablecoin), + solidityStablecoinSchema, + async ({ + name, + symbol, + burnable, + pausable, + premint, + premintChainId, + mintable, + callback, + permit, + votes, + flashmint, + crossChainBridging, + crossChainLinkAllowOverride, + access, + info, + restrictions, + freezable, + }) => { + const opts: StablecoinOptions = { + name, + symbol, + burnable, + pausable, + premint, + premintChainId, + mintable, + callback, + permit, + votes, + flashmint, + crossChainBridging, + crossChainLinkAllowOverride, + access, + info, + restrictions, + freezable, + }; + return { + content: [ + { + type: 'text', + text: safePrintSolidityCodeBlock(() => rewriteForTron(stablecoin.print(opts))), + }, + ], + }; + }, + ); +} From deee7cc9f06b6674eb95271d8afddd8a839975a9 Mon Sep 17 00:00:00 2001 From: Kostadin Madzherski Date: Wed, 27 May 2026 19:57:21 +0300 Subject: [PATCH 03/20] Address CodeRabbit review feedback Targets the 5 actionable comments on PR #806: - transform-tron.test.ts: fold the four pragma-cap assertions into single-line t.is(...) calls so they conform to project Prettier rules (was flagged as a CI-blocker on the static-checks job). - zip-hardhat-tron.ts: prepend a temporary-limitation warning to the generated README's "Installing dependencies" section noting that @openzeppelin/hardhat-tron and @openzeppelin/tron-contracts may not yet be on the npm registry. Users get a clear hint when npm install 404s instead of a confusing failure. - zip-tronbox.ts: same warning for the TronBox template, mentioning @openzeppelin/tron-contracts. - tron.html: add parity meta tags + Google Tag Manager + GA snippets that all the other per-language pages already have so /tron has matching social previews and traffic analytics. - tron App.svelte: hide the TronBox download button when opts.upgradeable is set, mirroring the existing hardhat-tron gate (TronBox doesn't have an upgradeable workflow either, so the secondary download was reachable even when it would emit an unbuildable project). - Refactor Overrides.omitZipFoundry from `boolean` to `(opts?: GenericOptions) => boolean` so ecosystems can gate on per-call options the same way `omitZipHardhat` already does. Polkadot's existing `omitZipFoundry: true` becomes `() => true`. Snapshots regenerated for both zip-hardhat-tron and zip-tronbox. Not addressed: CodeRabbit also flagged a missing `ERC4626: 'TRC4626'` entry in tabLabels. The wizard does not have an `ERC4626` Kind/tab (the kinds are ERC20/721/1155/Stablecoin/RealWorldAsset/Account/ Governor/Custom), so tabLabels has no key to add. ERC4626 references that appear in generated source (e.g. from a tokenized-vault Custom contract) are already handled by `rewriteForTron`'s symbol pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../solidity/src/utils/transform-tron.test.ts | 20 +++-------- .../solidity/src/zip-hardhat-tron.test.ts.md | 8 +++++ .../src/zip-hardhat-tron.test.ts.snap | Bin 3291 -> 3447 bytes .../core/solidity/src/zip-hardhat-tron.ts | 2 ++ .../core/solidity/src/zip-tronbox.test.ts.md | 8 +++++ .../solidity/src/zip-tronbox.test.ts.snap | Bin 2984 -> 3157 bytes packages/core/solidity/src/zip-tronbox.ts | 2 ++ packages/ui/public/tron.html | 32 ++++++++++++++++++ packages/ui/src/polkadot/App.svelte | 2 +- packages/ui/src/solidity/App.svelte | 2 +- packages/ui/src/solidity/overrides.ts | 9 +++-- packages/ui/src/tron/App.svelte | 4 ++- 12 files changed, 67 insertions(+), 22 deletions(-) diff --git a/packages/core/solidity/src/utils/transform-tron.test.ts b/packages/core/solidity/src/utils/transform-tron.test.ts index 6f1c14945..4b79be010 100644 --- a/packages/core/solidity/src/utils/transform-tron.test.ts +++ b/packages/core/solidity/src/utils/transform-tron.test.ts @@ -64,24 +64,12 @@ test('preserves the entire program when no TRON-relevant tokens present', t => { test('caps pragma at 0.8.26 (the current tron-solc maximum)', t => { // Above the cap — downgraded. - t.is( - rewriteForTron('pragma solidity ^0.8.27;\ncontract Foo {}\n'), - 'pragma solidity ^0.8.26;\ncontract Foo {}\n', - ); - t.is( - rewriteForTron('pragma solidity ^0.8.30;\ncontract Foo {}\n'), - 'pragma solidity ^0.8.26;\ncontract Foo {}\n', - ); + t.is(rewriteForTron('pragma solidity ^0.8.27;\ncontract Foo {}\n'), 'pragma solidity ^0.8.26;\ncontract Foo {}\n'); + t.is(rewriteForTron('pragma solidity ^0.8.30;\ncontract Foo {}\n'), 'pragma solidity ^0.8.26;\ncontract Foo {}\n'); // At the cap — unchanged. - t.is( - rewriteForTron('pragma solidity ^0.8.26;\ncontract Foo {}\n'), - 'pragma solidity ^0.8.26;\ncontract Foo {}\n', - ); + t.is(rewriteForTron('pragma solidity ^0.8.26;\ncontract Foo {}\n'), 'pragma solidity ^0.8.26;\ncontract Foo {}\n'); // Below the cap — unchanged. - t.is( - rewriteForTron('pragma solidity ^0.8.20;\ncontract Foo {}\n'), - 'pragma solidity ^0.8.20;\ncontract Foo {}\n', - ); + t.is(rewriteForTron('pragma solidity ^0.8.20;\ncontract Foo {}\n'), 'pragma solidity ^0.8.20;\ncontract Foo {}\n'); }); test('handles a realistic ERC20 wizard output', t => { diff --git a/packages/core/solidity/src/zip-hardhat-tron.test.ts.md b/packages/core/solidity/src/zip-hardhat-tron.test.ts.md index 72711925f..890b61fb8 100644 --- a/packages/core/solidity/src/zip-hardhat-tron.test.ts.md +++ b/packages/core/solidity/src/zip-hardhat-tron.test.ts.md @@ -125,6 +125,8 @@ Generated by [AVA](https://avajs.dev). ␊ ## Installing dependencies␊ ␊ + > :warning: Temporary limitation: this template depends on \`@openzeppelin/hardhat-tron\` and \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for either package, retry after they are published, or install them from a local checkout / git URL in the meantime.␊ + ␊ \`\`\`␊ npm install␊ \`\`\`␊ @@ -334,6 +336,8 @@ Generated by [AVA](https://avajs.dev). ␊ ## Installing dependencies␊ ␊ + > :warning: Temporary limitation: this template depends on \`@openzeppelin/hardhat-tron\` and \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for either package, retry after they are published, or install them from a local checkout / git URL in the meantime.␊ + ␊ \`\`\`␊ npm install␊ \`\`\`␊ @@ -490,6 +494,8 @@ Generated by [AVA](https://avajs.dev). ␊ ## Installing dependencies␊ ␊ + > :warning: Temporary limitation: this template depends on \`@openzeppelin/hardhat-tron\` and \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for either package, retry after they are published, or install them from a local checkout / git URL in the meantime.␊ + ␊ \`\`\`␊ npm install␊ \`\`\`␊ @@ -656,6 +662,8 @@ Generated by [AVA](https://avajs.dev). ␊ ## Installing dependencies␊ ␊ + > :warning: Temporary limitation: this template depends on \`@openzeppelin/hardhat-tron\` and \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for either package, retry after they are published, or install them from a local checkout / git URL in the meantime.␊ + ␊ \`\`\`␊ npm install␊ \`\`\`␊ diff --git a/packages/core/solidity/src/zip-hardhat-tron.test.ts.snap b/packages/core/solidity/src/zip-hardhat-tron.test.ts.snap index 9c15d84457b6e580ad14dc4e54b2efd2aee46f7a..a7a1a1fda889c5f09251fd2ea74a5ffbcf8faa76 100644 GIT binary patch literal 3447 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 Zu4vH}ExMvb_Z4f={U2p|{oGnp005~hjz0hZ literal 3291 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 ZDdjz-yr-1+l+M1V^j{>(y#O9g007hvH>Usq diff --git a/packages/core/solidity/src/zip-hardhat-tron.ts b/packages/core/solidity/src/zip-hardhat-tron.ts index 464120c6f..3d90b2b51 100644 --- a/packages/core/solidity/src/zip-hardhat-tron.ts +++ b/packages/core/solidity/src/zip-hardhat-tron.ts @@ -118,6 +118,8 @@ This project demonstrates a TRON-targeted Hardhat use case using \`@openzeppelin ${this.getReadmePrerequisitesSection()}## Installing dependencies +> :warning: Temporary limitation: this template depends on \`@openzeppelin/hardhat-tron\` and \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for either package, retry after they are published, or install them from a local checkout / git URL in the meantime. + \`\`\` npm install \`\`\` diff --git a/packages/core/solidity/src/zip-tronbox.test.ts.md b/packages/core/solidity/src/zip-tronbox.test.ts.md index 1ee170eab..1f95e1330 100644 --- a/packages/core/solidity/src/zip-tronbox.test.ts.md +++ b/packages/core/solidity/src/zip-tronbox.test.ts.md @@ -163,6 +163,8 @@ Generated by [AVA](https://avajs.dev). ␊ ## Installing dependencies␊ ␊ + > :warning: Temporary limitation: this template depends on \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for that package, retry after it is published, or install it from a local checkout / git URL in the meantime.␊ + ␊ \`\`\`␊ npm install␊ \`\`\`␊ @@ -382,6 +384,8 @@ Generated by [AVA](https://avajs.dev). ␊ ## Installing dependencies␊ ␊ + > :warning: Temporary limitation: this template depends on \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for that package, retry after it is published, or install it from a local checkout / git URL in the meantime.␊ + ␊ \`\`\`␊ npm install␊ \`\`\`␊ @@ -581,6 +585,8 @@ Generated by [AVA](https://avajs.dev). ␊ ## Installing dependencies␊ ␊ + > :warning: Temporary limitation: this template depends on \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for that package, retry after it is published, or install it from a local checkout / git URL in the meantime.␊ + ␊ \`\`\`␊ npm install␊ \`\`\`␊ @@ -793,6 +799,8 @@ Generated by [AVA](https://avajs.dev). ␊ ## Installing dependencies␊ ␊ + > :warning: Temporary limitation: this template depends on \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for that package, retry after it is published, or install it from a local checkout / git URL in the meantime.␊ + ␊ \`\`\`␊ npm install␊ \`\`\`␊ diff --git a/packages/core/solidity/src/zip-tronbox.test.ts.snap b/packages/core/solidity/src/zip-tronbox.test.ts.snap index ce0b028626a39312a888db800684ab0902bce769..d5a5b2e0adba45487d1d6d2242274e4eaf8f84d3 100644 GIT binary patch literal 3157 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 bpc^`!?n3Mc?fQRgx*?Zi2o_#8A|d$~@@vmF diff --git a/packages/core/solidity/src/zip-tronbox.ts b/packages/core/solidity/src/zip-tronbox.ts index c22a3ef76..768ec2182 100644 --- a/packages/core/solidity/src/zip-tronbox.ts +++ b/packages/core/solidity/src/zip-tronbox.ts @@ -228,6 +228,8 @@ This project demonstrates a basic TronBox use case. It comes with a contract gen ## Installing dependencies +> :warning: Temporary limitation: this template depends on \`@openzeppelin/tron-contracts\`, which may not yet be available on the public npm registry. If \`npm install\` fails with a 404 for that package, retry after it is published, or install it from a local checkout / git URL in the meantime. + \`\`\` npm install \`\`\` diff --git a/packages/ui/public/tron.html b/packages/ui/public/tron.html index 1ec9bdf25..cf8840ea2 100644 --- a/packages/ui/public/tron.html +++ b/packages/ui/public/tron.html @@ -4,9 +4,37 @@ OpenZeppelin Contracts Wizard + + + + + + + + + + + + + + + + + + @@ -38,6 +66,10 @@ + + +
diff --git a/packages/ui/src/polkadot/App.svelte b/packages/ui/src/polkadot/App.svelte index 0a2164905..d335886d8 100644 --- a/packages/ui/src/polkadot/App.svelte +++ b/packages/ui/src/polkadot/App.svelte @@ -22,7 +22,7 @@ const overrides: Overrides = { omitTabs: ['Account'], omitFeatures: defineOmitFeatures(), - omitZipFoundry: true, + omitZipFoundry: () => true, omitZipHardhat: (opts?: GenericOptions) => { return !!opts?.upgradeable; }, diff --git a/packages/ui/src/solidity/App.svelte b/packages/ui/src/solidity/App.svelte index 61ec17c31..b5afb19b7 100644 --- a/packages/ui/src/solidity/App.svelte +++ b/packages/ui/src/solidity/App.svelte @@ -155,7 +155,7 @@ if (overrides.omitZipHardhat(opts)) { result.downloadHardhat = false; } - if (overrides.omitZipFoundry) { + if (overrides.omitZipFoundry(opts)) { result.downloadFoundry = false; } if (overrides.omitOpenInRemix) { diff --git a/packages/ui/src/solidity/overrides.ts b/packages/ui/src/solidity/overrides.ts index 0096ffe72..e855b2a3c 100644 --- a/packages/ui/src/solidity/overrides.ts +++ b/packages/ui/src/solidity/overrides.ts @@ -38,9 +38,12 @@ export interface Overrides { overrideZipHardhat: ((c: Contract, opts?: GenericOptions) => Promise) | undefined; /** - * Whether to omit the Download Foundry package feature + * Whether to omit the Download Foundry package feature. + * Accepts the current generic options so ecosystems can gate on, for example, + * `opts.upgradeable` when their downstream toolchain doesn't support it + * (matches the shape of `omitZipHardhat`). */ - omitZipFoundry: boolean; + omitZipFoundry: (opts?: GenericOptions) => boolean; /** * Override for the second download tab (originally "Foundry"). When set, @@ -113,7 +116,7 @@ export const defaultOverrides: Overrides = { omitFeatures: new Map(), omitZipHardhat: () => false, overrideZipHardhat: undefined, - omitZipFoundry: false, + omitZipFoundry: () => false, overrideZipFoundry: undefined, secondaryDownloadLabel: undefined, secondaryDownloadAction: undefined, diff --git a/packages/ui/src/tron/App.svelte b/packages/ui/src/tron/App.svelte index 608f1ddda..09576fafa 100644 --- a/packages/ui/src/tron/App.svelte +++ b/packages/ui/src/tron/App.svelte @@ -35,7 +35,9 @@ const { zipHardhatTron } = await zipHardhatTronModule; return zipHardhatTron(c, opts); }, - omitZipFoundry: false, + // Mirror the hardhat gate: TronBox doesn't have an upgradeable flow either, + // so hide the second download button when the user has opted into upgradeable. + omitZipFoundry: (opts?: GenericOptions) => !!opts?.upgradeable, overrideZipFoundry: async (c: Contract, opts?: GenericOptions) => { const { zipTronbox } = await zipTronboxModule; return zipTronbox(c, opts); From d57b6114e387386fa4b84b020a217f64a00b285e Mon Sep 17 00:00:00 2001 From: Kostadin Madzherski Date: Wed, 27 May 2026 20:00:36 +0300 Subject: [PATCH 04/20] Add changeset for TRON support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `check` job requires a changeset for any package-affecting diff. This PR bumps four packages (wizard, wizard-common, contracts-mcp, contracts-cli) — all patch-level, matching the project's existing convention of using `patch` for new features pre-1.0. Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/add-tron-support.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/add-tron-support.md diff --git a/.changeset/add-tron-support.md b/.changeset/add-tron-support.md new file mode 100644 index 000000000..1ebd009af --- /dev/null +++ b/.changeset/add-tron-support.md @@ -0,0 +1,8 @@ +--- +'@openzeppelin/wizard': patch +'@openzeppelin/wizard-common': patch +'@openzeppelin/contracts-mcp': patch +'@openzeppelin/contracts-cli': patch +--- + +Add TRON support: new `rewriteForTron` utility, `zip-hardhat-tron` and `zip-tronbox` generators, and `tron-*` tools in the MCP server and CLI. Generated source uses `TRC20` / `TRC721` / `TRC1155` and `@openzeppelin/tron-contracts` import paths, with the `pragma solidity` line capped at the current `tron-solc` maximum (`^0.8.26`). Upgradeable downloads are intentionally gated off on TRON until `@openzeppelin/tron-contracts` ships the transpiled upgradeable variants. From 5647ceda74a5e436ebac67bf67f2e898d85a0d7a Mon Sep 17 00:00:00 2001 From: Kostadin Madzherski Date: Wed, 27 May 2026 20:13:23 +0300 Subject: [PATCH 05/20] Trigger Socket Security rescan Empty commit to force Socket Security to re-evaluate the PR after @SocketSecurity ignore-all was posted. The flagged packages (@aws-sdk/core, @aws-sdk/credential-provider-process) are transitive deps inside hardhat/upgradeable/package-lock.json which this PR doesn't modify. Co-Authored-By: Claude Opus 4.7 (1M context) From 51b61c7a2e6c8ff02e417deaabaa7c7222b09cab Mon Sep 17 00:00:00 2001 From: Kostadin Madzherski Date: Thu, 28 May 2026 09:24:54 +0300 Subject: [PATCH 06/20] Default Governor blockTime to 3 seconds on TRON Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/add-tron-support.md | 2 +- packages/cli/src/cli.test.ts.md | 4 ++-- packages/cli/src/cli.test.ts.snap | Bin 11590 -> 11630 bytes packages/cli/src/registry.ts | 3 ++- .../common/src/ai/descriptions/solidity.ts | 2 +- packages/core/solidity/src/index.ts | 2 +- .../core/solidity/src/utils/transform-tron.ts | 5 +++++ packages/mcp/src/tron/tools/governor.test.ts | 5 +++-- packages/mcp/src/tron/tools/governor.ts | 7 +++++-- packages/ui/src/solidity/App.svelte | 6 +++++- .../ui/src/solidity/GovernorControls.svelte | 10 +++++++++- packages/ui/src/solidity/overrides.ts | 8 ++++++++ packages/ui/src/tron/App.svelte | 6 +++++- 13 files changed, 47 insertions(+), 13 deletions(-) diff --git a/.changeset/add-tron-support.md b/.changeset/add-tron-support.md index 1ebd009af..2f1d1716e 100644 --- a/.changeset/add-tron-support.md +++ b/.changeset/add-tron-support.md @@ -5,4 +5,4 @@ '@openzeppelin/contracts-cli': patch --- -Add TRON support: new `rewriteForTron` utility, `zip-hardhat-tron` and `zip-tronbox` generators, and `tron-*` tools in the MCP server and CLI. Generated source uses `TRC20` / `TRC721` / `TRC1155` and `@openzeppelin/tron-contracts` import paths, with the `pragma solidity` line capped at the current `tron-solc` maximum (`^0.8.26`). Upgradeable downloads are intentionally gated off on TRON until `@openzeppelin/tron-contracts` ships the transpiled upgradeable variants. +Add TRON support: new `rewriteForTron` utility, `zip-hardhat-tron` and `zip-tronbox` generators, and `tron-*` tools in the MCP server and CLI. Generated source uses `TRC20` / `TRC721` / `TRC1155` and `@openzeppelin/tron-contracts` import paths, with the `pragma solidity` line capped at the current `tron-solc` maximum (`^0.8.26`). Upgradeable downloads are intentionally gated off on TRON until `@openzeppelin/tron-contracts` ships the transpiled upgradeable variants. The Governor's `blockTime` defaults to 3 seconds on TRON (matching SR consensus) instead of inheriting Ethereum's 12, across the UI, CLI registry, and MCP `tron-governor` tool; exported as `TRON_DEFAULT_BLOCK_TIME` from `@openzeppelin/wizard`. diff --git a/packages/cli/src/cli.test.ts.md b/packages/cli/src/cli.test.ts.md index ea9ae61b2..fd1db887a 100644 --- a/packages/cli/src/cli.test.ts.md +++ b/packages/cli/src/cli.test.ts.md @@ -212,7 +212,7 @@ Generated by [AVA](https://avajs.dev). --votes The type of voting to use␊ --clockMode The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.␊ --timelock The type of timelock to use␊ - --blockTime The block time of the chain in seconds, default is 12␊ + --blockTime The block time of the chain in seconds. Defaults to 12 for Solidity (Ethereum) and 3 for TRON.␊ --decimals The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)␊ --proposalThreshold Minimum number of votes an account must have to create a proposal, default is 0.␊ --quorumMode The type of quorum mode to use␊ @@ -809,7 +809,7 @@ Generated by [AVA](https://avajs.dev). --votes The type of voting to use␊ --clockMode The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.␊ --timelock The type of timelock to use␊ - --blockTime The block time of the chain in seconds, default is 12␊ + --blockTime The block time of the chain in seconds. Defaults to 12 for Solidity (Ethereum) and 3 for TRON.␊ --decimals The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)␊ --proposalThreshold Minimum number of votes an account must have to create a proposal, default is 0.␊ --quorumMode The type of quorum mode to use␊ diff --git a/packages/cli/src/cli.test.ts.snap b/packages/cli/src/cli.test.ts.snap index 1e0b6ca5a25137cbae7e78097ce7bd35cfb4db41..dfb1945ed565e6333b182e276b9de57ab31356d9 100644 GIT binary patch delta 9983 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 delta 9942 zcmZvhRZN}@)MasZcefUOad&rjcP*|39-QLtP~5#}af-WZad-EZ!hD&T2IK{OB9 zN_^W}KS^Jb*r{Nhx~sCUlJuB?nlN4$@x4LJDCiAQ!=2$I=shSNm~9hDY0G~e-!EGK zF02v7RsUkA>Ur6Vxn}LTjxj+8+X?fseU{wMcxI(mZq3}u;)3ZwS=pIt#@W5#b6)Mz zx18(uXK&!9AN}T#mwN(#4wFg;{sy4r>TmkfXZ#3y(-hti65TtSUjyJ@-;S zltGLzw#l?sD)>GBv>xB-j&XEU<_x1lTzWUyJlr3BQA<4B6nli+)-Z{4RTGq`8etDy z!ZdI8r$*&GLF~xm)2a(01*D?33&6zO#VmD5s<)TIAY5aNgxd8N&}(slyon7^y*U`1 zOJx}7?))IgG@gSx#~QeykVKefKrGw0shGU-PmKzx{?w~UdD(d-Fw7LhYhNKq*wTzW z&}R|srQ;13qog#tR7Ca3`pC7h3f#m8LAtPR({Cl&1m4$&F|tcn@+&ed|-MtYGRHqTtJx^h>S|Qk@$rB#~UEJiADqFi~rsw zv_9AAGhQ|9%Iew<8~L)b?SIVV+i`k$Nm4W^X;dT3iSnT!$7bV`Jm=aD&v``*!(Xwfc0)>)(RlD# zMXkvvT|}SgzR>@z%!a_^?Pk~Zd$Y{ZYJ_Q?8lb65?Ss|Mu#4N_v6rW7f3VoOY@jPg z%{wh#S5L%boXs?=C4?N$v=p%*UAyLuGTC~$vxQ$Qx@*xjo{MG^FFV}PvK-Sh)<>GX zeQZiXfQACr-)gzupOZV#c6@)o>u z-{KIoVJrhKDeX3F=GdSy@{bAIfFgLJ!*%zObpHUMpyC}J>Td)DIzMt-a(FVJ+>w}8 zKRO{LAnk=Yarlke!#mqrI(^-M@5jrvOOGis>M%fmz>7XTn0gg~Uf`TZM93GaKCl*? z(*RPd-lUSAfjPZleOLQ4r`dsS&`uHuJ@H*#5{qJ%)swhTD&U&%_Egtn9XIEy?)rz%d)ucYpvJBELYl7(L>XceQ>G4grVFrW;&V>SC8(#<8o_f4!>O@$^oJ8 z2;Cf}r-r@}5N73k)r@BWcQVgd<|MeoHw3+2(B8|Yacx_u{aOr=+NJ?-#aU%nCx6=J z&brkEp7{%oLc%L_qqOIikM?x{-~fVMSxM#8EMa5&h0G-pk`G4Ib>y9eI^JsWU13$9 zNd3G%ee)<8Wg7F-%RD9opUKEeQExe56qyQt1EKvA z(@}nP8SjCjo~cRx$}ji|u9#P4B^75nha$DnPefel;t`_sW>lSz-IR1Y3dt5|I#K6BW&KG;nOw}rr z1Jkf4&i%bS>*fuh`LOJGPzkyI<5FDtEyo*5dHtVK{oHf?fnDuB>($+mhrf5AqI1pW zRg0SQG2!DAT=D#>wf@Jmy+t*@_A+UuoeNQ-&0BV(6a8xvOj@5y!PVjdZly?;zvsGi zGZ^pHly2FKlzNU*Rc1U@ResRx@}O(O~BRLq^X|E zaq3oF^?Rr!U&=>!knb7#Z{p*H}Z;AvB#?((*ek^94j z1MUb*NDKfzJb&^>U{?}MBAEw@QnFn^*!9^%TGXPD<}A^(T_NOrucr@TOQ~{Naf%(9 z?n0zkC05R445QOkJMk1yXlMp6*Noucv+tS=sp;RV)JqIcMYnULn+GMV?w^|l;eb_h zT*ioDGHOqb`rD3!`}=60O7Da3=)~=;`}|AdpJ{-d(qQp0mzwx71?^%Lu0FTjHde^> zI4yE+6R?O@S#H%o6YIQ;ROoQCL?KJU=h^C9OcNu9LZ^x|EytUQU&d~j>%US1^~UvRn*APgmI7H_d}lM%C$}YCW#EV_ zR7wHbQH7nzB7YWW19PmBv=}YN^OsOd1Ps7e#awPpE(5+_K;s(2uI99u_=3b*Wz>NlXDi!s-saZ!&cdDO}t zfyu5XA0?=5+2R*PqNOCGxVWhmwCZr_fvDRoh~Dv)PycA?e2NJIq_DpOlkVTM#pJRQ z<)e&RL5?b&-83JLz(b5UK{|=TjK-e&_b!ETyrr}4^#DItdDYdoSU2j3jn!W7pxb#t z`nQtCQ2ZmskQ=}_@nOwX$39u!lovLL(J!%o_a!qrJ)T1#(JnqLiu5JR4LlzG;GmF? zHLUT7{-BUBP#A{!whTJQ<%d1O6dMtgHBN)GeAVN?E!9Pe}aZdAuwIf^zxsyWv-lECpJ2>t5X?dGrQlbf&HI-65#0%E%sWP<#A(=(h+t zP&zbx4vU3czjYM62FkfjI7;)T{dRe+mwnM$f-{`K<#QlOYp0}Mm93^7F7|E;mGtiz zHsV$NTz4SK8F+8g`Jnh18&~1^D1iSo{ycp=p=ZpU@qseHeibc5+)?maU4lZ$fueWn3L_u3q5O(Ql9geOXlt*(i&iifOnwHfoxuvrc}! zkzD#uY+A&i!~2Nli17Ye>yRTqCwxU3GwX&mya-e-duIDS{Xq+xuxe zJGn;Cn&bP;NIB7(2lL837FWs}lMDngB@5tL=JiJ)K?=ZO#PBZ+SSD7Oe?3fXgd@Wj z8Rpz;CnG}k(mM6Ud6eO>P+&Gcs)JUs(g#?Ky(Du z(@M51WQoK`Vc+rF2$wRzxoAwod9cHw?g*`@>ZP6xxu2%*-j*be_(v5y0_# z9I9G=%C3t8-;E^>H$|Bj+LlaFr@9xXs-bit3H$0!6g-_kjC$q_rx59c)hx$s12 zqE>a7*TnVIy%P^khlRS#xaKkVsU3V69H6*T)=ssWM+m=j^)K&=-hnh%3<~-OIqF4v z8)f9fyF`WwTezw?PXjJ|f#O|QaiICRY1B>1b%KN5Ql4{|^Mc%rej`$OPRr%DRwoEFPmqD0HuB4( zynM1l^*h*!p5(GnYBYG|wE&ayM=6((y{xegNskl%c;Tym0f#;|Rr>)r>H$V%yPL*W z{vnyfMrlbyVkM%@zr3oOIy%m>%?l``mPwwe=7;LRK^IIy%lWW0yi*yj!PN_K_CAvc z1NHSRgyea$+q;r1yv1H1N&l9-Bd`fx?w{IPxMPFmK}n!M zfX?`i$6fJ)Xvr~;M8{l?$U9k30&`olNF(7 zaSZLoCj!j|@EvHaO>s5$e!QG!wGC-`(spqE4~&WW!5)Y(SYCN_4GN1=?Y|brMz(*k zvsC0Y$chKvkV7E?ug)TwD{oAP1U(`7#1d=yb+ltx<7w1l@AO- zUmW_MlWw;vQ~NmLkgrc2EX6^mH<<@ffc8)vxxjdEwA8M^ereMRYY)pE92XqAuZ{le zB{P0?q$)H{vpQZ#N9VHmH{4`x$}9^?Vh!bm@mx2q!A^nToo)XW=SS)52%mqYaUQ|B z4yXB~P~P^&X?=O+vWMVt4`sq-dcVwj{(gr%&4j+MZ<4; zb85Zv^S@#MojFr5!&KT}EfzuL^qzi;M^oj!9!Nr~Fi7d2raghBU)&WT9y8t8nd~XW zanoj?Rd-VQQcU3SO8*Bq@)~Ap%fJ6*r3abS1$A#iX;w=qOF`kS`(Ci%r1wL4^$Ke7 z;rk^gbI0v`Z}?C(AInD7ibU`Bgi_*2uT&|bc`G#V!y?cNoD%NwI`By}dkDPmb}a`A zcYqr!KOpkg(y9@nLUE`R9K2#(2#%jJqeh@knY?4b0|mI=n44tgx*GW>1GD?xG|6}_ zm05H5zrMaCAaP~o{>ig0q2$^twiF>Hbx=E;ko#NT()-eJT6%FNU=+etzR{!f6xiqo z&d8xo*GkW%<~ohPd+%d92;}d_Lx#!?+Pj66kQu=?D&lB1RvEWpwH=$ zIe?7^MiINFZwNzqPn2U8M%|flWO$Cs26%iBwLcXhQP{;irzj`H3{%6YATb z!rZ8TQKF8E;R7DvNr_KdpZD^*sgK;k_z z7339&Z5Cew{ThS=IXeXTXrskQM>qwU=a8=NRB0UGhd&JNA5{Say6k>clG%d|EP#Lke z{u!6AAhIv02-%oirBpH7GUxPu*yF-1Nd2DXCk=J_Yb|}fdp!lb6h|-r`^o$pRtbk7 zorEp3XQOR1n#sj-uQ?tf9$+j~1z+GN{7RsWTb*>V_7h84i1WltDiNu({5)c{O^Ke& zekx>tR%XmNZd?u6f|TEA_rqO9l(eVlF)3!F7hzX zHN~1>tatJ}oT5KVogCsjm&zI>)9vN->_B?$7jq$V-EzVOfuNxk7Vl6etE>A&gNdxy z_QNWh0+c?wV;wsZ1%EQhmDt6GM3&wS-=OefSha5pO$T+z#{RztIBB{UUK12RBO!%S z!#q0f)7g|daMLTZo4^wMx&d;%^cN!|X5&P=pRcssKkDt`Fx0LGL)82TwPPY`9@n~C zFpW|OL22R(4)<)QQ75#5zoEWr5#*ekEi9hbM0zZSDLUd=q!;M*!t0j!v^A^$sW?S2 z@B3<1JJvp|IZX_Mwm=amA#I8bjP#4Ya56CTgLtRmwFk>5opj1yFtEKS8p9mWA(+lEju87k6lQR zgQR*c3M3zq#0a*5z8s~c7h3lTq*6&=GKpU7U<|ghYWS7TPjfL;25G3{=o_Ju=NNol zTt_Qc%HYnS4Im9O&kgavAHIt`T5UKA7k=C8EHzbuNU_yFjBY{%o*Hx}#b!FUBTU6O zO=f(`1VSLNoAMis!hZGFfq6Qjy^>d~S1eT0Nar@9LVdPkZ!X;PxKUEhkUdHf53vW@ z6QVE0Q!)u9Kj50m{>kIs7-DbZDTP;nD2+?yqc`9NY=MZ9mr5MSoG(^p^CaEOdb#9< zuiEigOw+qUm!qZ)frjXkFf6vp`;9EfYnSam|Jje9rOZ*B`psEz!Za!}S5#?tCQBqa zdnSpD#*7fEdl!=Ch%lle{h^9!=x1`zwE68QDFUyD9$}4-93;gkvc1)Lh3MivITifr z#jLA9#Q{(ic0@F!M~<~YkTt_khFJNVwg=)46Z^;1hF>^>GzxsoP#$&MqVmzbHn zmQ}{!Ri}IGQtFao7F7~bDN89BHT;IE%&~v6-bM2s6ETGdtzpF>rgY*p#a7BUVjwLo z6!_J`93a=A?hWQ2B*Wg-j-q~$&4r#M2|U#&)dRXEa*yqTd(~zP9ZXcTn-n+P1TfY? z)l@@idEkUhxi2{()*Jhj4hb!el{BW*8xZ$)PRx!(ciDxmhUChjL5Q7kS+2~YmHuO! z`&R_EBkC^jbNBHlem>u&Wnt#I&U#!6nA{G@!+LRYx@YM*CE`uFY( zAzXp;w81YAVju=vs#=Rxf;`~6*RWSMjw_%^P~99;t`L-U4o#BPN8er+t&)7 z|LfMzKCQ!>CjwV#o{p2M@+FLo2rrI(4at|B&WGVcR(T?82ayW*x_|n2$ERu`^gUur z*ri&Wn#SPRPV4S?;KqhlX2HCi4F4MWoL}%+)Lj<*;hv2+pw1Z_riY4hfz3&BItV}n zOGj2qn)z<&^mdX8us;{spftm(V(0O4g?sVhb7o_H#B`a|Lq0n)cA`0ZbhByDng+Eh zWH4sJ*(JhS#-Y{o_mu3r8liXmD~E{1+!gLLqio3gPN~n9hDPu?Ek@%!pCrrirbSL~ zxL0vCH#IhzCecUjSARrtLN!9G{}1rSf8d{8%=mLO)V@P<&6N$s;r=&-tguehF!LRu ztVd$1X^Q#yP&aEGOtZweq`Ey|K4Q~n2;E`ySF>xkt8j<1_CeU>W5*sFE0XR!0)^?j zAC%H8LSl&|id^6MzL!}(^$M>5+xNOJRXwsMFJ{P3O%1rNuuZS@$yU*S3sQkI1JR;r zEsP8F8~ZO#bWf2(qOAEapj#TMn@^wigwG-_%8%QKTg!T8*N{O(6iiTok)q=y)|J@ha(S7dHU%h;!FAfpM|bz%bdZ0Z(%kzLVEej;kT^CdcF!AOh28 zBofM%bVrb2CYH~GM<4umiO&^3_z{N7>r$eXq-Ge5Oo1(6Dy*nXsNbO4l&RmN8Uv#J z#=8x#Jb@Q9&^qg{YTejGl3KdThs~*wsL>gPcUQrO9C01#LUX`k)(Q+6hD)KPU^y&S zLG0>uxYQC3PKq~@fSOcAGjxM6ebv@V#e@bStnO)*kP{oE($ChC`mR|msV!fA`Dvc4 zm;W}sY6@k|2N(7GKF$bwb?2&#=JZNUzw?2RcgL|usIGgXe*#y51UzVBp7OD7)HTUw;ud*x1B2X8XHS?n-I^4Ie#9PS`9l;vj+k4VB{Q?A=Lh@ z%a7v`W3ERwvPk1cJK1v6u=DVmm2ZCr^C~bj-6n{oB2r?$VgdqL8wdfm^f9qh_4KZZ@e3 z>JfWwuWKRScdZAeFE{q$Cx{OE`7%xqZ4G|IQco+nCO}v9yC3?#L>qo;%b%ZoK_1^H zSlZ%trZ~Ph9mX9Hzc{l&FWb%A6cHXTB{C-QMn`_rb}1n| z`YIbL_RlsQ0UHe_Vy+I(!>2`<4us7$D*#*ZW5gYHl#CVogDE4~)48_3PO8pna1FB{ zm=c(B>U{T(W`!*bVqYLt#OEXHjJ^sUf~`iG6jYb?VYT)_#(b^aYz(;K!38I#3n8ZA zPhg;s-WIlfs^Ug53^zQm82y|PJex@W19h-}iiYml!X?g9g}J9A+%7?aUKXx1rL{DO z_lx(to{#YPE?IjBsU0;*v}^2y_vA5{f(&^69~C{T#C4%bv4#D=O>}@srK=fu!-zal z#o_tkbebh$l2h#^RDH!gq9%0t5rjhhG&?!##(Op5tZ>7?OxucY*^Ea<7On5#q(}Ca zwvDRTB?1gNc%-Lpc1z>EJrB)78||hNsr2dG)FK#3H$|o3^X8F40LE@VnA}r%83bY7 z`03xebd{=+X~tOp8kBJ*ZdYW}WI5 ziCW@>Zw&ZK-C{4=fxir4r`2gxjhpq@p;+VN>*U{&-dQh**CYXEvwuf-n6~r;x?jx=L z-{@0KB2hc%1Bx3~uwOA%|SGa_N*!^e`t`=M(L5^GEL%x`T%XM{0P8M z2v?co6LVm=aYF=nYNQ&oja%e>l35!9LTbyJ0!)J;TALHyupJ>@QJ@?u8;#?tJluDv zYtietQ4H07p9(9y`Tx;5pF8HG61N_%!a7H5ZzQ8+)dOnUQ)p%Q9|%kyd?U^isd~vE zaQ!JI7k6h_44SNiZr?SNTos?O$>U09i5Rt+c0eP4u9Kh`N;pX%{xhM+WAyxBZ5A88 z{&uug=i5z}*19+zlWZDX8q}`|lQl$tQCg$FojU5hpnJLe9HKOPEDp%)m;_an!ajrS#Krj&r!8tw-t#D zBh|qC%2@-Az^(yOCQ%`~l)H$EJ&-X?NT+g62k<{Cl-o%yJDdz%r&Zci=v=pzRN#;D zgki8q59Od;?x-=7L=4xDbH;=h$bz-Lel;TmN4f z%DrA4_r(7hChE$=G!JZbVxNd?K6gpCJW*#Lz&1a>J?0DpPX73-+Z#kR_%o(Xd`=T~ zGf@jrYUGv;E3U7>NBe}_V4gDJjDK;V<-1_wOgAOZKf&1@cNn@hX;L#yW3CSzCL^D# zd?o#sa$$`k$B(Ycx~BuY?z~MDZdyxv2-mO}TU{nNsV~Gv9B%O6G90TNi02jx%25x_ ze4`?pylSX{DoQ#01h#7vUR!8p1-JmtUWMP%uG3McUq*_b+;+Dc8`J}_<}KaB^eR5B zDA+$w(T$-lA_NW1*Lx>vfX?yRls_xdIAB~p)(a4dV|*52AX&`>y+V{(*)eJmmA8en zE{w*jTwUz6oN{cmZMZX1>)ItB$h0f_{Ef*_1bxS=!yRsLrP%KPiNNco_9qg#k*z4# ztXafSizw{AMz=k?{>$Z>*{8E;#HTwU;Wq{EtpIdXalymUyS+SZeV+WG)XaTkm&bC* zx{o>vX1aAfcP3CC&X6^+oz50Q2edR%h}NQnOh$gv$Y diff --git a/packages/cli/src/registry.ts b/packages/cli/src/registry.ts index 0fa296277..80af13ffe 100644 --- a/packages/cli/src/registry.ts +++ b/packages/cli/src/registry.ts @@ -11,6 +11,7 @@ import { governor, custom, rewriteForTron, + TRON_DEFAULT_BLOCK_TIME, } from '@openzeppelin/wizard'; import { solidityPrompts, tronPrompts } from '@openzeppelin/wizard-common'; import { @@ -183,7 +184,7 @@ export const registry = { ), 'tron-governor': createRegistryEntry( solidityGovernorSchema, - opts => rewriteForTron(governor.print(opts)), + opts => rewriteForTron(governor.print({ ...opts, blockTime: opts.blockTime ?? TRON_DEFAULT_BLOCK_TIME })), tronPrompts.Governor, ), 'tron-custom': createRegistryEntry( diff --git a/packages/common/src/ai/descriptions/solidity.ts b/packages/common/src/ai/descriptions/solidity.ts index 37ae5abdf..f7b366d11 100644 --- a/packages/common/src/ai/descriptions/solidity.ts +++ b/packages/common/src/ai/descriptions/solidity.ts @@ -85,7 +85,7 @@ export const solidityAccountDescriptions = { export const solidityGovernorDescriptions = { delay: 'The delay since proposal is created until voting starts, default is "1 day"', period: 'The length of period during which people can cast their vote, default is "1 week"', - blockTime: 'The block time of the chain in seconds, default is 12', + blockTime: 'The block time of the chain in seconds. Defaults to 12 for Solidity (Ethereum) and 3 for TRON.', proposalThreshold: 'Minimum number of votes an account must have to create a proposal, default is 0.', decimals: 'The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)', diff --git a/packages/core/solidity/src/index.ts b/packages/core/solidity/src/index.ts index 3c9c3c4fc..8cb34dcc6 100644 --- a/packages/core/solidity/src/index.ts +++ b/packages/core/solidity/src/index.ts @@ -54,4 +54,4 @@ export { formatLinesWithSpaces, spaceBetween } from './utils/format-lines'; export { findCover } from './utils/find-cover'; export type { PremintCalculation } from './erc20'; export { calculatePremint as calculateERC20Premint, scaleByPowerOfTen } from './erc20'; -export { rewriteForTron } from './utils/transform-tron'; +export { rewriteForTron, TRON_DEFAULT_BLOCK_TIME } from './utils/transform-tron'; diff --git a/packages/core/solidity/src/utils/transform-tron.ts b/packages/core/solidity/src/utils/transform-tron.ts index fece5ce94..537cf3491 100644 --- a/packages/core/solidity/src/utils/transform-tron.ts +++ b/packages/core/solidity/src/utils/transform-tron.ts @@ -23,6 +23,11 @@ // Bump this when tron-solc catches up to the mainline Wizard's Solidity version. const TRON_SOLC_MAX_MINOR = 26; +// TRON blocks are produced every 3 seconds (SR consensus), versus Ethereum's +// ~12s. Used wherever the Governor's `blockTime` would otherwise inherit the +// Solidity default of 12 (UI, CLI registry, and MCP tron-governor tool). +export const TRON_DEFAULT_BLOCK_TIME = 3; + const PATH_ROOT_PATTERN = /@openzeppelin\/contracts\//g; const TOKEN_ERC20_DIR_PATTERN = /\/token\/ERC20\//g; const TOKEN_ERC721_DIR_PATTERN = /\/token\/ERC721\//g; diff --git a/packages/mcp/src/tron/tools/governor.test.ts b/packages/mcp/src/tron/tools/governor.test.ts index 3d583db4b..bd8da1d69 100644 --- a/packages/mcp/src/tron/tools/governor.test.ts +++ b/packages/mcp/src/tron/tools/governor.test.ts @@ -6,7 +6,7 @@ import { registerTronGovernor } from './governor'; import type { DeepRequired } from '../../helpers.test'; import { testMcpInfo, assertAPIEquivalence } from '../../helpers.test'; import type { GovernorOptions } from '@openzeppelin/wizard'; -import { governor, rewriteForTron } from '@openzeppelin/wizard'; +import { governor, rewriteForTron, TRON_DEFAULT_BLOCK_TIME } from '@openzeppelin/wizard'; import { solidityGovernorSchema } from '@openzeppelin/wizard-common/schemas'; import { z } from 'zod'; @@ -30,7 +30,8 @@ function assertHasAllSupportedFields( t.pass(); } -const tronPrint = (opts: GovernorOptions) => rewriteForTron(governor.print(opts)); +const tronPrint = (opts: GovernorOptions) => + rewriteForTron(governor.print({ ...opts, blockTime: opts.blockTime ?? TRON_DEFAULT_BLOCK_TIME })); test('basic', async t => { const params: z.infer = { diff --git a/packages/mcp/src/tron/tools/governor.ts b/packages/mcp/src/tron/tools/governor.ts index 55c82d055..8c4036538 100644 --- a/packages/mcp/src/tron/tools/governor.ts +++ b/packages/mcp/src/tron/tools/governor.ts @@ -1,6 +1,6 @@ import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { GovernorOptions } from '@openzeppelin/wizard'; -import { governor, rewriteForTron } from '@openzeppelin/wizard'; +import { governor, rewriteForTron, TRON_DEFAULT_BLOCK_TIME } from '@openzeppelin/wizard'; import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; import { solidityGovernorSchema } from '@openzeppelin/wizard-common/schemas'; import { tronPrompts } from '@openzeppelin/wizard-common'; @@ -35,7 +35,10 @@ export function registerTronGovernor(server: McpServer): RegisteredTool { votes, clockMode, timelock, - blockTime, + // TRON produces blocks every ~3s (vs ~12s on Ethereum); apply that + // default when the caller hasn't supplied one so the generated + // voting delay/period in blocks matches TRON's chain. + blockTime: blockTime ?? TRON_DEFAULT_BLOCK_TIME, decimals, proposalThreshold, quorumMode, diff --git a/packages/ui/src/solidity/App.svelte b/packages/ui/src/solidity/App.svelte index b5afb19b7..5f44ed32a 100644 --- a/packages/ui/src/solidity/App.svelte +++ b/packages/ui/src/solidity/App.svelte @@ -397,7 +397,11 @@
- +
diff --git a/packages/ui/src/solidity/GovernorControls.svelte b/packages/ui/src/solidity/GovernorControls.svelte index e6ce3f727..e393b8c32 100644 --- a/packages/ui/src/solidity/GovernorControls.svelte +++ b/packages/ui/src/solidity/GovernorControls.svelte @@ -13,9 +13,17 @@ const defaults = governor.defaults; + // Ecosystems that inherit the Solidity Wizard (e.g. TRON) can override the + // assumed block time so the voting delay/period are computed against their + // chain's cadence (TRON ~3s vs Ethereum ~12s). + export let defaultBlockTime: number | undefined = undefined; + + const effectiveBlockTime = defaultBlockTime ?? defaults.blockTime; + export let opts: Required = { kind: 'Governor', ...defaults, + blockTime: effectiveBlockTime, proposalThreshold: '', // default to empty in UI quorumAbsolute: '', // default to empty in UI info: { ...infoDefaults }, // create new object since Info is nested @@ -203,7 +211,7 @@ {}, postConfigLanguage: undefined, aiAssistant: undefined, + defaultBlockTime: undefined, }; diff --git a/packages/ui/src/tron/App.svelte b/packages/ui/src/tron/App.svelte index 09576fafa..cd2eed50a 100644 --- a/packages/ui/src/tron/App.svelte +++ b/packages/ui/src/tron/App.svelte @@ -8,7 +8,7 @@ // Rewrite ERC20/721/1155/4626 to TRC* and @openzeppelin/contracts to // @openzeppelin/tron-contracts for the in-page display + single-file download. // The zip generators apply the same transform internally. - import { rewriteForTron, type Contract, type GenericOptions } from '@openzeppelin/wizard'; + import { rewriteForTron, TRON_DEFAULT_BLOCK_TIME, type Contract, type GenericOptions } from '@openzeppelin/wizard'; export let initialTab: string | undefined = 'ERC20'; export let initialOpts: InitialOptions = {}; @@ -54,6 +54,10 @@ transformPrintedContract: rewriteForTron, sanitizeOmittedFeatures, postConfigLanguage: 'tron-solidity', + // TRON SR consensus produces a block every ~3s, so the Governor's + // "1 block = N seconds" field should default to 3 here instead of + // inheriting Ethereum's 12. + defaultBlockTime: TRON_DEFAULT_BLOCK_TIME, aiAssistant: { svelteComponent: createWiz<'tron'>(), language: 'tron', From ab0c2530c6374d04d4011be5ff5ee6d26e272aa3 Mon Sep 17 00:00:00 2001 From: Kostadin Madzherski Date: Thu, 28 May 2026 10:14:56 +0300 Subject: [PATCH 07/20] Hyperlink @openzeppelin/tron-contracts imports in TRON UI Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/ui/src/solidity/inject-hyperlinks.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/solidity/inject-hyperlinks.ts b/packages/ui/src/solidity/inject-hyperlinks.ts index 2a15e21c8..280a5ec6f 100644 --- a/packages/ui/src/solidity/inject-hyperlinks.ts +++ b/packages/ui/src/solidity/inject-hyperlinks.ts @@ -6,16 +6,27 @@ export function injectHyperlinks(code: string) { /"(@openzeppelin\/)(contracts-upgradeable\/|contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g; const importCommunityContractsRegex = /"(@openzeppelin\/)(community-contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g; + // @openzeppelin/tron-contracts mirrors @openzeppelin/contracts on the TRON + // VM. Its versioning is independent (currently 0.x) and doesn't ship git + // tags that mirror @openzeppelin/contracts, so we link to the default + // branch instead of pinning a tag like the Solidity link does. + const importTronContractsRegex = + /"(@openzeppelin\/)(tron-contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g; const compatibleCommunityContractsRegexSingle = /Community Contracts commit ([a-fA-F0-9]{7,40})/; const compatibleCommunityContractsRegexGlobal = new RegExp(compatibleCommunityContractsRegexSingle.source, 'g'); const compatibleCommunityContractsGitCommit = code.match(compatibleCommunityContractsRegexSingle)?.[1]; - let result = code.replace( - importContractsRegex, - `"$1$2$3"`, - ); + let result = code + .replace( + importContractsRegex, + `"$1$2$3"`, + ) + .replace( + importTronContractsRegex, + `"$1$2$3"`, + ); if (compatibleCommunityContractsGitCommit !== undefined) { result = result From 0c63d47afe209a4b8e2babc0e4aff9312ed39b5b Mon Sep 17 00:00:00 2001 From: Kostadin Madzherski Date: Thu, 28 May 2026 10:15:54 +0300 Subject: [PATCH 08/20] Use master branch for tron-contracts import links Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/ui/src/solidity/inject-hyperlinks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/solidity/inject-hyperlinks.ts b/packages/ui/src/solidity/inject-hyperlinks.ts index 280a5ec6f..8917b0e54 100644 --- a/packages/ui/src/solidity/inject-hyperlinks.ts +++ b/packages/ui/src/solidity/inject-hyperlinks.ts @@ -25,7 +25,7 @@ export function injectHyperlinks(code: string) { ) .replace( importTronContractsRegex, - `"$1$2$3"`, + `"$1$2$3"`, ); if (compatibleCommunityContractsGitCommit !== undefined) { From 5b9aba89b8028d74bff489baa2af0243bf2239ad Mon Sep 17 00:00:00 2001 From: Kostadin Madzherski Date: Wed, 17 Jun 2026 17:24:22 +0300 Subject: [PATCH 09/20] Address @ericglau review feedback on TRON PR - Gate `superchain` cross-chain bridging on the CLI and MCP TRON surfaces (previously only the UI sanitized it). Promote the gate to a shared `sanitizeTronOptions` helper in `@openzeppelin/wizard`, consumed by the UI, CLI registry, and MCP tron-* handlers so all three behave identically. - Fix unresolvable dependency ranges in the generated projects: the Hardhat template pinned `@openzeppelin/hardhat-tron: ^1.0.0` (excludes 0.x) and `tron-contracts: ^0.1.0` (excludes 0.0.1); align with the intended publish versions (`^0.1.0` / `^0.0.1`), and the same for the TronBox `package.json`. - Stop the TronBox migration from silently deploying `undefined`: non-address constructor args now emit commented-out declarations and the deploy call is commented out behind a TODO when any arg is unset, matching mainline zip-hardhat/zip-foundry behaviour (an unedited migrate errors or no-ops). - Add `override` to every method in `HardhatTronZipGenerator` that overrides `HardhatZipGenerator`, so it stays correct under `noImplicitOverride`. - Extract the TRON brand colour into a `--tron-red` CSS variable in vars.css, referenced from both App.svelte and standalone.css. - Keep the TRON-specific Governor `blockTime` note out of the shared Solidity description: revert `solidityGovernorDescriptions.blockTime` and add a TRON-only `tronGovernorSchema`/`tronGovernorDescriptions`, consumed by the tron-governor CLI registry entry and MCP tool. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/src/cli.test.ts.md | 4 +- packages/cli/src/cli.test.ts.snap | Bin 11630 -> 11590 bytes packages/cli/src/registry.ts | 14 +++++-- .../common/src/ai/descriptions/solidity.ts | 2 +- packages/common/src/ai/descriptions/tron.ts | 8 ++++ packages/common/src/ai/schemas/index.ts | 2 + packages/common/src/ai/schemas/tron.ts | 13 +++++++ .../environments/hardhat/tron/package.json | 4 +- packages/core/solidity/src/index.ts | 2 +- .../core/solidity/src/utils/transform-tron.ts | 16 ++++++++ .../solidity/src/zip-hardhat-tron.test.ts.md | 16 ++++---- .../src/zip-hardhat-tron.test.ts.snap | Bin 3447 -> 3448 bytes .../core/solidity/src/zip-hardhat-tron.ts | 20 +++++----- .../core/solidity/src/zip-tronbox.test.ts.md | 8 ++-- .../solidity/src/zip-tronbox.test.ts.snap | Bin 3157 -> 3157 bytes packages/core/solidity/src/zip-tronbox.ts | 35 +++++++++++++----- packages/mcp/src/tron/tools/erc20.ts | 4 +- packages/mcp/src/tron/tools/governor.ts | 4 +- packages/mcp/src/tron/tools/rwa.ts | 4 +- packages/mcp/src/tron/tools/stablecoin.ts | 4 +- packages/ui/src/common/styles/vars.css | 2 + packages/ui/src/standalone.css | 2 +- packages/ui/src/tron/App.svelte | 2 +- .../src/tron/handle-unsupported-features.ts | 13 +++---- 24 files changed, 120 insertions(+), 59 deletions(-) create mode 100644 packages/common/src/ai/schemas/tron.ts diff --git a/packages/cli/src/cli.test.ts.md b/packages/cli/src/cli.test.ts.md index fd1db887a..7bbc1adf0 100644 --- a/packages/cli/src/cli.test.ts.md +++ b/packages/cli/src/cli.test.ts.md @@ -212,7 +212,7 @@ Generated by [AVA](https://avajs.dev). --votes The type of voting to use␊ --clockMode The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.␊ --timelock The type of timelock to use␊ - --blockTime The block time of the chain in seconds. Defaults to 12 for Solidity (Ethereum) and 3 for TRON.␊ + --blockTime The block time of the chain in seconds, default is 12␊ --decimals The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)␊ --proposalThreshold Minimum number of votes an account must have to create a proposal, default is 0.␊ --quorumMode The type of quorum mode to use␊ @@ -809,7 +809,7 @@ Generated by [AVA](https://avajs.dev). --votes The type of voting to use␊ --clockMode The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.␊ --timelock The type of timelock to use␊ - --blockTime The block time of the chain in seconds. Defaults to 12 for Solidity (Ethereum) and 3 for TRON.␊ + --blockTime The block time of the chain in seconds, default is 3.␊ --decimals The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)␊ --proposalThreshold Minimum number of votes an account must have to create a proposal, default is 0.␊ --quorumMode The type of quorum mode to use␊ diff --git a/packages/cli/src/cli.test.ts.snap b/packages/cli/src/cli.test.ts.snap index dfb1945ed565e6333b182e276b9de57ab31356d9..eb058c77664de89bb81e16756859e1e25325cdfb 100644 GIT binary patch literal 11590 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 T3vo?eeM1iT4oa9xfP?)n`@~=S literal 11630 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 diff --git a/packages/cli/src/registry.ts b/packages/cli/src/registry.ts index 80af13ffe..2db28869a 100644 --- a/packages/cli/src/registry.ts +++ b/packages/cli/src/registry.ts @@ -11,6 +11,7 @@ import { governor, custom, rewriteForTron, + sanitizeTronOptions, TRON_DEFAULT_BLOCK_TIME, } from '@openzeppelin/wizard'; import { solidityPrompts, tronPrompts } from '@openzeppelin/wizard-common'; @@ -23,6 +24,7 @@ import { solidityAccountSchema, solidityGovernorSchema, solidityCustomSchema, + tronGovernorSchema, } from '@openzeppelin/wizard-common/schemas'; import { @@ -161,7 +163,11 @@ export const registry = { 'uniswap-hooks': createRegistryEntry(uniswapHooksHooksSchema, opts => hooks.print(opts), uniswapHooksPrompts.Hooks), // TRON (Solidity post-processed for @openzeppelin/tron-contracts + TRC* token names) - 'tron-trc20': createRegistryEntry(solidityERC20Schema, opts => rewriteForTron(erc20.print(opts)), tronPrompts.TRC20), + 'tron-trc20': createRegistryEntry( + solidityERC20Schema, + opts => rewriteForTron(erc20.print(sanitizeTronOptions(opts))), + tronPrompts.TRC20, + ), 'tron-trc721': createRegistryEntry( solidityERC721Schema, opts => rewriteForTron(erc721.print(opts)), @@ -174,16 +180,16 @@ export const registry = { ), 'tron-stablecoin': createRegistryEntry( solidityStablecoinSchema, - opts => rewriteForTron(stablecoin.print(opts)), + opts => rewriteForTron(stablecoin.print(sanitizeTronOptions(opts))), tronPrompts.Stablecoin, ), 'tron-rwa': createRegistryEntry( solidityRWASchema, - opts => rewriteForTron(realWorldAsset.print(opts)), + opts => rewriteForTron(realWorldAsset.print(sanitizeTronOptions(opts))), tronPrompts.RWA, ), 'tron-governor': createRegistryEntry( - solidityGovernorSchema, + tronGovernorSchema, opts => rewriteForTron(governor.print({ ...opts, blockTime: opts.blockTime ?? TRON_DEFAULT_BLOCK_TIME })), tronPrompts.Governor, ), diff --git a/packages/common/src/ai/descriptions/solidity.ts b/packages/common/src/ai/descriptions/solidity.ts index f7b366d11..37ae5abdf 100644 --- a/packages/common/src/ai/descriptions/solidity.ts +++ b/packages/common/src/ai/descriptions/solidity.ts @@ -85,7 +85,7 @@ export const solidityAccountDescriptions = { export const solidityGovernorDescriptions = { delay: 'The delay since proposal is created until voting starts, default is "1 day"', period: 'The length of period during which people can cast their vote, default is "1 week"', - blockTime: 'The block time of the chain in seconds. Defaults to 12 for Solidity (Ethereum) and 3 for TRON.', + blockTime: 'The block time of the chain in seconds, default is 12', proposalThreshold: 'Minimum number of votes an account must have to create a proposal, default is 0.', decimals: 'The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)', diff --git a/packages/common/src/ai/descriptions/tron.ts b/packages/common/src/ai/descriptions/tron.ts index 0a34551d5..6af79a0cb 100644 --- a/packages/common/src/ai/descriptions/tron.ts +++ b/packages/common/src/ai/descriptions/tron.ts @@ -14,3 +14,11 @@ export const tronPrompts = { Governor: 'Make a contract to implement governance, such as for a DAO, targeting the TRON Virtual Machine.', Custom: 'Make a custom smart contract, targeting the TRON Virtual Machine.', }; + +// TRON-specific overrides of the Solidity Governor descriptions. Only the +// fields that differ from `solidityGovernorDescriptions` are listed here; the +// rest are reused. TRON's SR consensus produces a block every ~3s (vs ~12s on +// Ethereum), so the Governor's "block time" field defaults to 3 here. +export const tronGovernorDescriptions = { + blockTime: 'The block time of the chain in seconds, default is 3.', +}; diff --git a/packages/common/src/ai/schemas/index.ts b/packages/common/src/ai/schemas/index.ts index 1f028bf3d..d4d89185f 100644 --- a/packages/common/src/ai/schemas/index.ts +++ b/packages/common/src/ai/schemas/index.ts @@ -34,6 +34,8 @@ export { export { stylusCommonSchema, stylusERC20Schema, stylusERC721Schema, stylusERC1155Schema } from './stylus'; +export { tronGovernorSchema } from './tron'; + export { confidentialCommonSchema, confidentialERC7984Schema } from './confidential'; export { diff --git a/packages/common/src/ai/schemas/tron.ts b/packages/common/src/ai/schemas/tron.ts new file mode 100644 index 000000000..8821e68cc --- /dev/null +++ b/packages/common/src/ai/schemas/tron.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; +import { tronGovernorDescriptions } from '../../index'; +import { solidityGovernorSchema } from './solidity'; + +// TRON tools reuse the Solidity schemas almost verbatim — the options are +// identical and only the generated standard names / import paths differ +// (handled downstream by `rewriteForTron`). The one exception is the Governor's +// `blockTime`, whose description must mention TRON's ~3s default without +// leaking that note into the shared Solidity (and Polkadot) schemas. +export const tronGovernorSchema = { + ...solidityGovernorSchema, + blockTime: z.number().optional().describe(tronGovernorDescriptions.blockTime), +} as const satisfies z.ZodRawShape; diff --git a/packages/core/solidity/src/environments/hardhat/tron/package.json b/packages/core/solidity/src/environments/hardhat/tron/package.json index 9378374a3..a7de15574 100644 --- a/packages/core/solidity/src/environments/hardhat/tron/package.json +++ b/packages/core/solidity/src/environments/hardhat/tron/package.json @@ -11,8 +11,8 @@ "devDependencies": { "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", "@nomicfoundation/hardhat-ethers": "^3.0.9", - "@openzeppelin/hardhat-tron": "^1.0.0", - "@openzeppelin/tron-contracts": "^0.1.0", + "@openzeppelin/hardhat-tron": "^0.1.0", + "@openzeppelin/tron-contracts": "^0.0.1", "ethers": "^6.14.0", "hardhat": "^2.26.0" } diff --git a/packages/core/solidity/src/index.ts b/packages/core/solidity/src/index.ts index 8cb34dcc6..d69159712 100644 --- a/packages/core/solidity/src/index.ts +++ b/packages/core/solidity/src/index.ts @@ -54,4 +54,4 @@ export { formatLinesWithSpaces, spaceBetween } from './utils/format-lines'; export { findCover } from './utils/find-cover'; export type { PremintCalculation } from './erc20'; export { calculatePremint as calculateERC20Premint, scaleByPowerOfTen } from './erc20'; -export { rewriteForTron, TRON_DEFAULT_BLOCK_TIME } from './utils/transform-tron'; +export { rewriteForTron, sanitizeTronOptions, TRON_DEFAULT_BLOCK_TIME } from './utils/transform-tron'; diff --git a/packages/core/solidity/src/utils/transform-tron.ts b/packages/core/solidity/src/utils/transform-tron.ts index 537cf3491..0674bdbbd 100644 --- a/packages/core/solidity/src/utils/transform-tron.ts +++ b/packages/core/solidity/src/utils/transform-tron.ts @@ -36,6 +36,22 @@ const INTERFACE_PATTERN = /\bIERC(20|721|1155|4626)/g; const SYMBOL_PATTERN = /\bERC(20|721|1155|4626)/g; const PRAGMA_PATTERN = /(pragma\s+solidity\s+\^0\.8\.)(\d+)(\s*;)/g; +// `superchain` cross-chain bridging is OP Stack-specific: it pulls in +// `draft-ERC20Bridgeable` plus the hardcoded OP-Stack `0x42...0028` predeploy, +// neither of which exists on the TVM. The UI form hides the option, but the CLI +// and MCP surfaces reuse the full Solidity schemas, so they funnel options +// through this gate to downgrade `superchain` to `custom` before printing. +// Mutates in place (to match the UI's `sanitizeOmittedFeatures` override +// contract) and returns the same object for ergonomic use at call sites. +type TronSanitizableOptions = { crossChainBridging?: false | 'custom' | 'erc7786native' | 'superchain' }; + +export function sanitizeTronOptions(opts: T): T { + if (opts.crossChainBridging === 'superchain') { + opts.crossChainBridging = 'custom'; + } + return opts; +} + export function rewriteForTron(source: string): string { return source .replace(PATH_ROOT_PATTERN, '@openzeppelin/tron-contracts/') diff --git a/packages/core/solidity/src/zip-hardhat-tron.test.ts.md b/packages/core/solidity/src/zip-hardhat-tron.test.ts.md index 890b61fb8..e2b8f3c6a 100644 --- a/packages/core/solidity/src/zip-hardhat-tron.test.ts.md +++ b/packages/core/solidity/src/zip-hardhat-tron.test.ts.md @@ -71,8 +71,8 @@ Generated by [AVA](https://avajs.dev). "devDependencies": {␊ "@nomicfoundation/hardhat-chai-matchers": "^2.0.6",␊ "@nomicfoundation/hardhat-ethers": "^3.0.9",␊ - "@openzeppelin/hardhat-tron": "^1.0.0",␊ - "@openzeppelin/tron-contracts": "^0.1.0",␊ + "@openzeppelin/hardhat-tron": "^0.1.0",␊ + "@openzeppelin/tron-contracts": "^0.0.1",␊ "ethers": "^6.14.0",␊ "hardhat": "^2.26.0"␊ }␊ @@ -277,8 +277,8 @@ Generated by [AVA](https://avajs.dev). "devDependencies": {␊ "@nomicfoundation/hardhat-chai-matchers": "^2.0.6",␊ "@nomicfoundation/hardhat-ethers": "^3.0.9",␊ - "@openzeppelin/hardhat-tron": "^1.0.0",␊ - "@openzeppelin/tron-contracts": "^0.1.0",␊ + "@openzeppelin/hardhat-tron": "^0.1.0",␊ + "@openzeppelin/tron-contracts": "^0.0.1",␊ "ethers": "^6.14.0",␊ "hardhat": "^2.26.0"␊ }␊ @@ -440,8 +440,8 @@ Generated by [AVA](https://avajs.dev). "devDependencies": {␊ "@nomicfoundation/hardhat-chai-matchers": "^2.0.6",␊ "@nomicfoundation/hardhat-ethers": "^3.0.9",␊ - "@openzeppelin/hardhat-tron": "^1.0.0",␊ - "@openzeppelin/tron-contracts": "^0.1.0",␊ + "@openzeppelin/hardhat-tron": "^0.1.0",␊ + "@openzeppelin/tron-contracts": "^0.0.1",␊ "ethers": "^6.14.0",␊ "hardhat": "^2.26.0"␊ }␊ @@ -606,8 +606,8 @@ Generated by [AVA](https://avajs.dev). "devDependencies": {␊ "@nomicfoundation/hardhat-chai-matchers": "^2.0.6",␊ "@nomicfoundation/hardhat-ethers": "^3.0.9",␊ - "@openzeppelin/hardhat-tron": "^1.0.0",␊ - "@openzeppelin/tron-contracts": "^0.1.0",␊ + "@openzeppelin/hardhat-tron": "^0.1.0",␊ + "@openzeppelin/tron-contracts": "^0.0.1",␊ "ethers": "^6.14.0",␊ "hardhat": "^2.26.0"␊ }␊ diff --git a/packages/core/solidity/src/zip-hardhat-tron.test.ts.snap b/packages/core/solidity/src/zip-hardhat-tron.test.ts.snap index a7a1a1fda889c5f09251fd2ea74a5ffbcf8faa76..fc97db0b3afeff03298512d932f975894361618e 100644 GIT binary patch delta 3344 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 Zu4vH}ExMvb_Z4f={U2p|{oGnp005c2TciL0 diff --git a/packages/core/solidity/src/zip-hardhat-tron.ts b/packages/core/solidity/src/zip-hardhat-tron.ts index 3d90b2b51..d212db687 100644 --- a/packages/core/solidity/src/zip-hardhat-tron.ts +++ b/packages/core/solidity/src/zip-hardhat-tron.ts @@ -11,13 +11,13 @@ import { rewriteForTron } from './utils/transform-tron'; const TRON_SOLIDITY_VERSION = '0.8.26'; class HardhatTronZipGenerator extends HardhatZipGenerator { - protected getAdditionalHardhatImports(): string[] { + protected override getAdditionalHardhatImports(): string[] { // hardhat-tron does NOT use @nomicfoundation/hardhat-toolbox; the plugin // composes the smaller ethers + chai-matchers plugins that it actually needs. return ['@nomicfoundation/hardhat-ethers', '@nomicfoundation/hardhat-chai-matchers', '@openzeppelin/hardhat-tron']; } - protected getHardhatConfigJsonString(): string { + protected override getHardhatConfigJsonString(): string { return `\ { solidity: { @@ -48,7 +48,7 @@ class HardhatTronZipGenerator extends HardhatZipGenerator { }`; } - protected getHardhatConfig(_upgradeable: boolean): string { + protected override getHardhatConfig(_upgradeable: boolean): string { // hardhat-tron-based projects use a non-upgradeable single config; the // `@openzeppelin/hardhat-upgrades` plugin is not used here. The hardhat // config is also emitted as a CommonJS .cjs file in the README sample, @@ -66,13 +66,13 @@ export default config; `; } - protected async getPackageJson(c: Contract): Promise { + protected override async getPackageJson(c: Contract): Promise { const { default: packageJson } = await import('./environments/hardhat/tron/package.json'); packageJson.license = c.license; return packageJson; } - protected async getPackageLock(_c: Contract): Promise { + protected override async getPackageLock(_c: Contract): Promise { // Not used. The TRON variant skips emitting a package-lock.json because // @openzeppelin/hardhat-tron and @openzeppelin/tron-contracts are not yet // published to the npm registry, so a lockfile would dangle. `npm install` @@ -80,7 +80,7 @@ export default config; return undefined; } - protected getReadmePrerequisitesSection(): string { + protected override getReadmePrerequisitesSection(): string { return `\ ## Prerequisites @@ -91,7 +91,7 @@ Ensure you have the following installed: `; } - protected getReadmeTestingEnvironmentSetupSection(): string { + protected override getReadmeTestingEnvironmentSetupSection(): string { return `\ ## TRON Runtime Environment @@ -100,17 +100,17 @@ Ensure you have the following installed: `; } - protected getGitIgnoreHardhatIgnition(): string { + protected override getGitIgnoreHardhatIgnition(): string { // hardhat-ignition is not in scope for the TRON variant — we emit a plain // ethers script instead. Nothing to gitignore here. return ''; } - protected getPrintContract(c: Contract): string { + protected override getPrintContract(c: Contract): string { return rewriteForTron(printContract(c)); } - protected getReadme(c: Contract): string { + protected override getReadme(c: Contract): string { return `\ # Sample TRON Hardhat Project (${c.name}) diff --git a/packages/core/solidity/src/zip-tronbox.test.ts.md b/packages/core/solidity/src/zip-tronbox.test.ts.md index 1f95e1330..81566743c 100644 --- a/packages/core/solidity/src/zip-tronbox.test.ts.md +++ b/packages/core/solidity/src/zip-tronbox.test.ts.md @@ -147,7 +147,7 @@ Generated by [AVA](https://avajs.dev). "tronbox": "^4.1.0"␊ },␊ "dependencies": {␊ - "@openzeppelin/tron-contracts": "^0.1.0"␊ + "@openzeppelin/tron-contracts": "^0.0.1"␊ }␊ }`, `README.md:␊ @@ -368,7 +368,7 @@ Generated by [AVA](https://avajs.dev). "tronbox": "^4.1.0"␊ },␊ "dependencies": {␊ - "@openzeppelin/tron-contracts": "^0.1.0"␊ + "@openzeppelin/tron-contracts": "^0.0.1"␊ }␊ }`, `README.md:␊ @@ -569,7 +569,7 @@ Generated by [AVA](https://avajs.dev). "tronbox": "^4.1.0"␊ },␊ "dependencies": {␊ - "@openzeppelin/tron-contracts": "^0.1.0"␊ + "@openzeppelin/tron-contracts": "^0.0.1"␊ }␊ }`, `README.md:␊ @@ -783,7 +783,7 @@ Generated by [AVA](https://avajs.dev). "tronbox": "^4.1.0"␊ },␊ "dependencies": {␊ - "@openzeppelin/tron-contracts": "^0.1.0"␊ + "@openzeppelin/tron-contracts": "^0.0.1"␊ }␊ }`, `README.md:␊ diff --git a/packages/core/solidity/src/zip-tronbox.test.ts.snap b/packages/core/solidity/src/zip-tronbox.test.ts.snap index d5a5b2e0adba45487d1d6d2242274e4eaf8f84d3..d12c2f7f9ae8d0237a47e2909faad5a3e2a5f0c5 100644 GIT binary patch delta 3115 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 F0029j5_A9n diff --git a/packages/core/solidity/src/zip-tronbox.ts b/packages/core/solidity/src/zip-tronbox.ts index 768ec2182..732da9938 100644 --- a/packages/core/solidity/src/zip-tronbox.ts +++ b/packages/core/solidity/src/zip-tronbox.ts @@ -1,6 +1,6 @@ import JSZip from 'jszip'; import type { GenericOptions } from './build-generic'; -import type { Contract, FunctionArgument } from './contract'; +import type { Contract } from './contract'; import { printContract } from './print'; import { rewriteForTron } from './utils/transform-tron'; import { stringifyUnicodeSafe } from './utils/sanitize'; @@ -17,13 +17,16 @@ import { stringifyUnicodeSafe } from './utils/sanitize'; const TRON_SOLIDITY_VERSION = '0.8.26'; function getDeploymentArgs(c: Contract): string[] { - return c.constructorArgs.map(arg => placeholderForArg(arg)); + // The arg name doubles as the local variable identifier declared above the + // deploy call (see declareArgPlaceholders). + return c.constructorArgs.map(arg => arg.name); } -function placeholderForArg(arg: FunctionArgument): string { - // Use the arg name as a local variable identifier; we declare placeholders - // above the deploy call so the user knows what to fill in. - return arg.name; +// Non-address args have no usable placeholder value, so the user must fill them +// in before deploying. Address args get the obviously-invalid '' +// sentinel, which compiles but fails loudly at deploy time if left unedited. +function hasUnsetArgs(c: Contract): boolean { + return c.constructorArgs.some(arg => arg.type !== 'address'); } function declareArgPlaceholders(c: Contract): string[] { @@ -31,7 +34,9 @@ function declareArgPlaceholders(c: Contract): string[] { if (arg.type === 'address') { return `// TODO: Replace with a real address (e.g. tronWeb.defaultAddress.base58).\n const ${arg.name} = '';`; } - return `// TODO: Set value for the ${arg.name} constructor argument.\n const ${arg.name} = undefined;`; + // No safe default — emit a commented-out declaration so the migration can + // never silently deploy an `undefined` value. + return `// TODO: Set the ${arg.name} constructor argument, then uncomment it and the deploy call below.\n // const ${arg.name} = /* ... */;`; }); } @@ -67,8 +72,18 @@ function deployMigration(c: Contract): string { const argList = getDeploymentArgs(c); const declarations = argDecls.length > 0 ? argDecls.join('\n ') + '\n\n ' : ''; - const deployCall = - argList.length > 0 ? `deployer.deploy(${c.name}, ${argList.join(', ')});` : `deployer.deploy(${c.name});`; + + let deployCall: string; + if (argList.length === 0) { + deployCall = `deployer.deploy(${c.name});`; + } else if (hasUnsetArgs(c)) { + // At least one argument has no usable placeholder; leave the deploy call + // commented out so an unedited `tronbox migrate` is a no-op rather than + // deploying with missing values. + deployCall = `// TODO: Uncomment once the constructor arguments above are set.\n // deployer.deploy(${c.name}, ${argList.join(', ')});`; + } else { + deployCall = `deployer.deploy(${c.name}, ${argList.join(', ')});`; + } return `\ const ${c.name} = artifacts.require('./${c.name}.sol'); @@ -203,7 +218,7 @@ function packageJson(c: Contract): unknown { tronbox: '^4.1.0', }, dependencies: { - '@openzeppelin/tron-contracts': '^0.1.0', + '@openzeppelin/tron-contracts': '^0.0.1', }, }; } diff --git a/packages/mcp/src/tron/tools/erc20.ts b/packages/mcp/src/tron/tools/erc20.ts index 236a53227..e23c53492 100644 --- a/packages/mcp/src/tron/tools/erc20.ts +++ b/packages/mcp/src/tron/tools/erc20.ts @@ -1,6 +1,6 @@ import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { ERC20Options } from '@openzeppelin/wizard'; -import { erc20, rewriteForTron } from '@openzeppelin/wizard'; +import { erc20, rewriteForTron, sanitizeTronOptions } from '@openzeppelin/wizard'; import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; import { solidityERC20Schema } from '@openzeppelin/wizard-common/schemas'; import { tronPrompts } from '@openzeppelin/wizard-common'; @@ -50,7 +50,7 @@ export function registerTronTRC20(server: McpServer): RegisteredTool { content: [ { type: 'text', - text: safePrintSolidityCodeBlock(() => rewriteForTron(erc20.print(opts))), + text: safePrintSolidityCodeBlock(() => rewriteForTron(erc20.print(sanitizeTronOptions(opts)))), }, ], }; diff --git a/packages/mcp/src/tron/tools/governor.ts b/packages/mcp/src/tron/tools/governor.ts index 8c4036538..b6ad5c65d 100644 --- a/packages/mcp/src/tron/tools/governor.ts +++ b/packages/mcp/src/tron/tools/governor.ts @@ -2,14 +2,14 @@ import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server import type { GovernorOptions } from '@openzeppelin/wizard'; import { governor, rewriteForTron, TRON_DEFAULT_BLOCK_TIME } from '@openzeppelin/wizard'; import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; -import { solidityGovernorSchema } from '@openzeppelin/wizard-common/schemas'; +import { tronGovernorSchema } from '@openzeppelin/wizard-common/schemas'; import { tronPrompts } from '@openzeppelin/wizard-common'; export function registerTronGovernor(server: McpServer): RegisteredTool { return server.tool( 'tron-governor', makeDetailedPrompt(tronPrompts.Governor), - solidityGovernorSchema, + tronGovernorSchema, async ({ name, delay, diff --git a/packages/mcp/src/tron/tools/rwa.ts b/packages/mcp/src/tron/tools/rwa.ts index 4c41e63ed..28a237d6a 100644 --- a/packages/mcp/src/tron/tools/rwa.ts +++ b/packages/mcp/src/tron/tools/rwa.ts @@ -1,6 +1,6 @@ import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { StablecoinOptions } from '@openzeppelin/wizard'; -import { realWorldAsset, rewriteForTron } from '@openzeppelin/wizard'; +import { realWorldAsset, rewriteForTron, sanitizeTronOptions } from '@openzeppelin/wizard'; import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; import { solidityRWASchema } from '@openzeppelin/wizard-common/schemas'; import { tronPrompts } from '@openzeppelin/wizard-common'; @@ -52,7 +52,7 @@ export function registerTronRWA(server: McpServer): RegisteredTool { content: [ { type: 'text', - text: safePrintSolidityCodeBlock(() => rewriteForTron(realWorldAsset.print(opts))), + text: safePrintSolidityCodeBlock(() => rewriteForTron(realWorldAsset.print(sanitizeTronOptions(opts)))), }, ], }; diff --git a/packages/mcp/src/tron/tools/stablecoin.ts b/packages/mcp/src/tron/tools/stablecoin.ts index bc77de622..cccbf2ef6 100644 --- a/packages/mcp/src/tron/tools/stablecoin.ts +++ b/packages/mcp/src/tron/tools/stablecoin.ts @@ -1,6 +1,6 @@ import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { StablecoinOptions } from '@openzeppelin/wizard'; -import { stablecoin, rewriteForTron } from '@openzeppelin/wizard'; +import { stablecoin, rewriteForTron, sanitizeTronOptions } from '@openzeppelin/wizard'; import { safePrintSolidityCodeBlock, makeDetailedPrompt } from '../../utils'; import { solidityStablecoinSchema } from '@openzeppelin/wizard-common/schemas'; import { tronPrompts } from '@openzeppelin/wizard-common'; @@ -52,7 +52,7 @@ export function registerTronStablecoin(server: McpServer): RegisteredTool { content: [ { type: 'text', - text: safePrintSolidityCodeBlock(() => rewriteForTron(stablecoin.print(opts))), + text: safePrintSolidityCodeBlock(() => rewriteForTron(stablecoin.print(sanitizeTronOptions(opts)))), }, ], }; diff --git a/packages/ui/src/common/styles/vars.css b/packages/ui/src/common/styles/vars.css index 78b499dda..fd30d2106 100644 --- a/packages/ui/src/common/styles/vars.css +++ b/packages/ui/src/common/styles/vars.css @@ -41,6 +41,8 @@ --uniswap-pink: #f50eb4; + --tron-red: #ff060a; + /* Dimensions (scale taken from Tailwind) */ --size-1: 0.25rem; diff --git a/packages/ui/src/standalone.css b/packages/ui/src/standalone.css index fb12cac0d..b5391ab0c 100644 --- a/packages/ui/src/standalone.css +++ b/packages/ui/src/standalone.css @@ -146,7 +146,7 @@ body { } .nav .switch.switch-tron.active { - --color-2: #ff060a; + --color-2: var(--tron-red); } .nav .switch.switch-stellar.active { diff --git a/packages/ui/src/tron/App.svelte b/packages/ui/src/tron/App.svelte index cd2eed50a..1c847d886 100644 --- a/packages/ui/src/tron/App.svelte +++ b/packages/ui/src/tron/App.svelte @@ -71,7 +71,7 @@