Skip to main content

Account Balances

::: note

The example code can be found in the Wasp repository.


Each chain in the IOTA Smart Contracts is a separate ledger, different from the UTXO ledger. Multiple chains add another dimension on top of the UTXO Ledger. Smart contracts can exchange assets between themselves on the same chain and also between different chains, as well as with addresses on the UTXO Ledger. We will skip explaining the whole picture for the time being and will concentrate on one specific use case.

Imagine you have a wallet, the private key (with the address), and some tokens on that address on the UTXO Ledger, the Tangle. The use case is about sending those tokens to a smart contract on a chain and receiving these tokens back to the address.

Here we explore the concept of on-chain account(a.k.a. just account). On the UTXO Ledger, the private key is represented by the address (the hash of the public key). That address holds balances of colored tokens. Those tokens are controlled by the private key.

In IOTA Smart Contracts, we extend the concept of address with the concept of account. An account contains colored tokens just like an address. The account is located on some chain, and it is controlled by the same private key as the associated address. So, an address can control tokens on the UTXO Ledger (Level 1 or L1) and on each chain (Level 2 or L2).

So, the chain essentially is a custodian of the tokens deposited in its accounts.

The following test demonstrates how a wallet can deposit tokens in a chain account and then withdraw them.

func TestTutorial5(t *testing.T) {
env := solo.New(t, false, false, seed)
chain := env.NewChain(nil, "ex5")
// create a wallet with 1000000 iotas.
// the wallet has address and it is globally identified
// through a universal identifier: the agent ID
userWallet, userAddress := env.NewKeyPairWithFunds(env.NewSeedFromIndex(5))
userAgentID := iscp.NewAgentID(userAddress, 0)

env.AssertAddressBalance(userAddress, colored.IOTA, utxodb.FundsFromFaucetAmount)
chain.AssertAccountBalance(userAgentID, colored.IOTA, 0) // empty on-chain

t.Logf("Address of the userWallet is: %s", userAddress.Base58())
numIotas := env.GetAddressBalance(userAddress, colored.IOTA)
t.Logf("balance of the userWallet is: %d iota", numIotas)
env.AssertAddressBalance(userAddress, colored.IOTA, utxodb.FundsFromFaucetAmount)

// send 42 iotas from wallet to own account on-chain, controlled by the same wallet
req := solo.NewCallParams(accounts.Contract.Name, accounts.FuncDeposit.Name).WithIotas(42)
_, err := chain.PostRequestSync(req, userWallet)
require.NoError(t, err)

// check address balance: must be 42 iotas less
env.AssertAddressBalance(userAddress, colored.IOTA, utxodb.FundsFromFaucetAmount-42)
// check the on-chain account. Must contain 42 iotas
chain.AssertAccountBalance(userAgentID, colored.IOTA, 42)

// withdraw all iotas back to the sender
req = solo.NewCallParams(accounts.Contract.Name, accounts.FuncWithdraw.Name).WithIotas(1)
_, err = chain.PostRequestSync(req, userWallet)
require.NoError(t, err)

// we are back to initial situation: IOTA is fee-less!
env.AssertAddressBalance(userAddress, colored.IOTA, utxodb.FundsFromFaucetAmount)
chain.AssertAccountBalance(userAgentID, colored.IOTA, 0) // empty

The example above creates a chain, then creates a wallet with utxodb.FundsFromFaucetAmount iotas (1 Mi) and sends (deposits) 42 iotas to the corresponding on-chain account by posting a deposit request to the accounts core contract on that chain. That account will now contain 42 iotas. The address on the UTXO Ledger will contain 42 iotas less, of course.

In the next step, the same wallet (userWallet) will withdraw all 42 iotas back to the address by sending a withdraw request to the accounts contract on the same chain.

If the same request would be posted from another user wallet (another private key), the withdraw request would fail. Try it! Only the owner of the address can move those funds from the on-chain account.