Skip to main content

Sending Tokens From IOTA Smart Contracts to the Tangle

The programmer of the example smart contract implemented the entry point withdrawIota. What it is for? The answer is: if not for this method, any tokens sent to the smart contract will be essentially lost: there is no other way to withdraw tokens from the smart contract.

The entry point requires the caller to be an address and to be equal to the creator of the instance of the contract. The creator (its agentID) is stored when a contract is deployed and is always contained in the registry of contracts. The smart contract example is programmed to panic if the caller is not the creator. This way the smart contract only allows to withdraw tokens contained in the smart contract by its creator.

If authorization conditions are satisfied, the contract calls the transfer_to_address sandbox function to send all iotas, owned by the contract, to the caller's address.

What if you send some other colored tokens, not ordinary iotas, to the smart contract? As in this contract you can only withdraw iotas, those will stay there forever.

The following Solo test demonstrates how it works:

func TestTutorial8(t *testing.T) {
// create deterministic solo environment
env := solo.New(t, false, false, seed)
// deploy new chain
chain := env.NewChain(nil, "ex8")

// create a user's wallet (private key) with address and request iotas from the faucet.
userWallet, userAddress := env.NewKeyPairWithFunds(env.NewSeedFromIndex(5))
userAgentID := iscp.NewAgentID(userAddress, 0)
t.Logf("userAgentID: %s", userAgentID)

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

// the chain owner (default) send a request to the root contract to grant right to deploy
// contract on the chain to the use
req := solo.NewCallParams(root.Contract.Name, root.FuncGrantDeployPermission.Name, root.ParamDeployer, userAgentID).WithIotas(1)
_, err := chain.PostRequestSync(req, nil)
require.NoError(t, err)

// user deploys wasm smart contract on the chain under the name "example1"
// the wasm binary is in the file
err = chain.DeployWasmContract(userWallet, "example1", "example_tutorial_bg.wasm")
require.NoError(t, err)

contractAgentID := iscp.NewAgentID(chain.ChainID.AsAddress(), iscp.Hn("example1"))

// the deployment of the smart contract required 1 requests to the root contract:
// - to submit binary to the on-chain "blob" registry
// - to deploy contract from the blob
// Two tokens were taken from the user account to form requests and then were
// deposited to the user's account on the chain
env.AssertAddressBalance(userAddress, colored.IOTA, solo.Saldo-2)
chain.AssertAccountBalance(contractAgentID, colored.IOTA, 0) // empty on-chain
chain.AssertAccountBalance(userAgentID, colored.IOTA, 0)

// user send a "storeString" request to the smart contract. It attaches 42 iotas to the request
// It also takes 1 iota for the request token
// Result is 42 iotas moved to the smart contract's account
req = solo.NewCallParams("example1", "storeString", "paramString", "Hello, world!").
WithIotas(42)
_, err = chain.PostRequestSync(req, userWallet)
require.NoError(t, err)

chain.AssertAccountBalance(contractAgentID, colored.IOTA, 42)
chain.AssertAccountBalance(userAgentID, colored.IOTA, 0)
env.AssertAddressBalance(userAddress, colored.IOTA, solo.Saldo-44)

// user withdraws all iotas from the smart contract back
// Out of 42 iotas 41 iota is coming back to the user's address, 1 iotas
// is accrued to the user on chain
req = solo.NewCallParams("example1", "withdrawIota")
req.WithIotas(1)
_, err = chain.PostRequestSync(req, userWallet)
require.NoError(t, err)

chain.AssertAccountBalance(contractAgentID, colored.IOTA, 0)
chain.AssertAccountBalance(userAgentID, colored.IOTA, 0)
env.AssertAddressBalance(userAddress, colored.IOTA, solo.Saldo-44+42)
}