> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tac.build/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

<Info>
  **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.
</Info>

## 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

<Tabs>
  <Tab title="Smart Account Blueprint">
    **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
  </Tab>

  <Tab title="Smart Account Factory">
    **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
  </Tab>
</Tabs>

## *Smart Account Blueprint* Implementation

The contract is already deployed on TAC Mainnet and Testnet.

<Note>
  *Smart Account Blueprint* is addressed as `TacSmartAccount` in provided code snippets
</Note>

```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

<AccordionGroup>
  <Accordion title="Advanced Execution Methods" icon="play">
    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
  </Accordion>

  <Accordion title="One-Time Ticket System" icon="ticket">
    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
  </Accordion>

  <Accordion title="Token & NFT Support" icon="coins">
    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
  </Accordion>

  <Accordion title="Access Control" icon="shield-check">
    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
  </Accordion>
</AccordionGroup>

## *Smart Account Factory* Implementation

The contract is already deployed on TAC Mainnet and Testnet.

<Note>
  *Smart Account Factory* is addressed as `TacSAFactory` in provided code snippets
</Note>

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

<Info>
  TAC provides a single factory instance that all developers can use. This
  approach ensures consistency and reduces deployment costs.
</Info>

* **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]
);
```
