Skip to main content
To start using Inco features in your contract, all you need is to add this line at the top of your solidity file:
import {e, ebool, euint256, inco} "@inco/lightning/src/Lib.sol";
and add this line at the top of the contract body:
using e for *;
Now you have access to encrypted types and operations in your contract. Here is a full example of a simple fungible token:
The following example is present with comments explaining the code here in the lightning-rod template.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

// This line of import is all you need to get started with Inco
import {euint256, ebool, e, inco} from "@inco/lightning/src/Lib.sol";

/// @notice a fungible token whose balances and transfer values are confidential
contract SimpleConfidentialToken {
    using e for euint256;
    using e for ebool;
    using e for uint256;
    using e for bytes;
    using e for address;

    // since balances are represented by handles, just calling balanceOf() without having the decryption access
    // on the corresponding handle won't allow to know the actual balance
    mapping(address => euint256) public balanceOf;

    constructor() {
        // mint 1000 tokens to the deployer
        // we consider this token to have 9 decimals, which is standard for confidential fungible tokens
        // units are GWEI instead of WADs (WADs being 18 decimals, used in ERC20)
        // `uint256(1000 * 1e9).asEuint256()` is a trivial encrypt, i.e it converts a known value into
        // an e-type. We can also write `e.asEuint256(1000 * 1e9);`
        balanceOf[msg.sender] = uint256(1000 * 1e9).asEuint256();
    }

    // this function is meant to be called by EOAs or smart wallets as valueInput is an encrypted amount that should
    // be generated using @inco/js sdk
    function transfer(address to, bytes memory valueInput) external payable returns (ebool) {
        require(msg.value == inco.getFee(),"Fee not paid");
        // `newInput` returns an euint256 from an encrypted input, if the encrypted input is malformed, it will return
        // an euint256 with a value of 0. Depending on how malformed the input is, an external observer may know
        // that the fallback 0 value has been returned. Just use the js sdk to avoid that.
        // The second parameter of newInput will receive decryption rights on the euint256, and should be the account
        // that created the encrypted input.
        euint256 value = valueInput.newEuint256(msg.sender);
        return _transfer(to, value);
    }

    // this function is meant to be called by smart contracts, as it is using an existing euint256.
    // caller has to use `value.allow(tokenAddress)` before calling this function or it will revert.
    function transfer(address to, euint256 value) public returns (ebool success) {
        // always perform this check on encrypted parameters. In this case, a malicious caller could try to pass
        // the handle corresponding of a victim's balance to deduce its value, it wouldn't revert without this check
        // as this token contract holds allowance on the balances of all holders.
        require(msg.sender.isAllowed(value), "SimpleConfidentialToken: unauthorized value handle access");

        return _transfer(to, value);
    }

    function _transfer(address to, euint256 value) internal returns (ebool success) {
        // check if the sender has enough balance to transfer the value
        success = balanceOf[msg.sender].ge(value);
        // Solidity can't revert on an insufficient balance as it has no way of knowing the actual value of the ebool.
        // Instead, we use the multiplexer pattern, replacing the actual transferred value for a 0 (trivial encrypt)
        // if the balance is insufficient, which is equivalent to doing nothing.
        euint256 transferredValue = success.select(value, uint256(0).asEuint256());

        // saving handles used multiple times in memory variables to save some gas
        euint256 senderNewBalance = balanceOf[msg.sender].sub(transferredValue);
        euint256 receiverNewBalance = balanceOf[to].add(transferredValue);

        balanceOf[msg.sender] = senderNewBalance;
        balanceOf[to] = receiverNewBalance;

        // allow the sender to see its new balance
        senderNewBalance.allow(msg.sender);
        // allow the receiver to see its new balance
        receiverNewBalance.allow(to);
        // allow this contract to be able to compute over the new balances in future transfers
        senderNewBalance.allowThis();
        receiverNewBalance.allowThis();
        // let the caller know if the transfer was successful
        success.allow(msg.sender);
    }
}

This contract is explained in depth in the Concepts Guide.