Learn
Wallet contract
Wallet structure

Tact wallet contract

Wallet structure

Contract structure

Tact language allows to define behaviour of contracts with convenient tools as Contract, Trait, Receiver, Message. Generally simple contract has such structure:

  • Includes
  • Custom Messages and Structs declaration
  • Contract's body
    • Fields

    • Init function

    • Functions

    • Receivers

    • Getters

Wallet contract structure

As we mentioned earlier, contract consists of

  1. Includes and struct definition
  2. Contract's body

For contract wallet we will define struct Transfer as base struct for messages:

 
struct Transfer {
seqno: Int as uint32;
mode: Int as uint8;
to: Address;
amount: Int as coins;
body: Cell?;
}

Next will be wallet contract body with all included sections:

 
contract Wallet {
 
    // contract fields
    
    // init function
    
    // receivers functions
    
    // get functions
 
}
 

Wallet fields

Now, let's take a look one by one these section and find out their functions. First - contract fields. In these section we describe data, that will be store in Blockchain inside contract's storage. Shortly, it calls on-chain. For features of wallet contract we declare following:

  • seqno - is field that store last executed transaction id. Used for deduplication of transactions.
  • key - key of owner. Used for checks if transaction asked from wallet owner.
  • walletId - wallet id, serves for supporting different(up to uint64) instance of wallets based on one key. For each walledId we deploy unique smart contract with access by same key.
 
contract Wallet {
 
    seqno: Int as uint32 = 0;
    key: Int as uint256;
    walletId: Int as uint64;
 
    // init function
    
    // receivers functions
    
    // get functions
}
 

Wallet init

The init() function define first state of our smart contract for deploying process. To deploy our wallet contract we will keep public key and id in its store. Usually, public keypair - public and secret keys computes locally on device which initiate deployment. Secret key stores locally for future signing transaction of owner, and public key sent as argument in init() function. Wallet ID, according to definition of field allows to create several(up to uin64) wallets based on same keypair.

contract Wallet {
 
    // contract fields
    
    init(key: Int, walletId: Int) {
        self.key = key;
        self.walletId = walletId;
    }
    
    // receivers functions
 
    // get functions
    
}

Wallet receivers

Contract receivers define how contract acts depending on what it was received in incoming message. Notice, that when contract sends outgoing message or do computation it pays network fees from its contract balance. Read more about fees in TON here. For wallet contract we describe the following:

  • msg: TransferMessage - receiver that handles incoming message and performs outcoming message from our wallet. It will check op_code and seqno to be sure, that transaction valid. If requires successes, we will increment seqno counter and send outgoing message.
  • msg: Slice - if msg_body is Slice we check that incoming message was not bounced before, and if this requirements successes increment our seqno counter.
  • "notify" - receiver declares actions when incoming message contents string comment "notify". Here it will increment seqno field.
  • "duplicate" - receiver declares actions when incoming message contents string comment "duplicate". Here it will increment seqno field.
  • bounced() - special receiver for handling bounced messages.
contract Wallet {
 
    // contract fields
    
    // init function
    
    receive(msg: TransferMessage) {
 
        // Check Signature
        let op_hash: Int = msg.transfer.toCell().hash();
        require(checkSignature(op_hash, msg.signature, self.key), "Invalid signature");
        require(msg.transfer.seqno == self.seqno, "Invalid seqno");
 
        // Increment seqno
        self.seqno = self.seqno + 1;
 
        // Send message
        send(SendParameters{value: msg.transfer.amount, to: msg.transfer.to, mode: msg.transfer.mode, body: msg.transfer.body});
    }
 
    receive(msg: Slice) {
            self.seqno = self.seqno + 1;
    }
 
    receive("notify") {
            self.seqno = self.seqno + 1;
    }
 
    receive("duplicate") {
        // Create new wallet
        let walletInit: StateInit = initOf Wallet(self.key, self.walletId + 1);
    }
    
    bounced(msg: Slice) {
        // TODO: Handle
    }
    
    // get functions
    
}
    

Wallet getters

Get functions allows to get information about contract's data for free. It's helpfully for us, as we want get seqno before every transaction we want made.

  • get publicKey() - returns Integer number of public key for smart contract;
  • get walletId() - returns walletId that was used to initiate this wallet;
  • get seqno() - returns current seqno of wallet
 
    // contract fields
    
    // init function
    
    // receivers functions
 
    get fun publicKey(): Int {
        return self.key;
    }
 
    get fun walletId(): Int {
        return self.walletId;
    }
 
    get fun seqno(): Int {
        return self.seqno;
    }
}