--- tzip: 0XX title: Bank account smart contract author: Benjamin Fuentes (@benji_fuentes) status: Draft created: 2024-04-05 --- # Bank account smart contract The objective of this TZIP is to propose some features to cover what a decentralized bank account can do, also called "Programmable money". ## Abstract TZIP-0XX proposes a standard to replicate what bank accounts do today but in a decentralized manner This document provides an overview and rationale for the global solution as well as an implementation. ## Motivation Account abstraction has raised concerns to the web3 developers of the importance of UX and the gap we have today. We are focusing here on two major topics which are **Account Management** and **Account Global Rules**. Let's review what we have on Tezos about **Account Management and recovery mechanisms**: - **Ledger/Trezor**: you need a second backup device or write the seed on a paper secured somewhere at bootstrap time only once - **Temple/Trustwallet/Airgap/Umami**: you have an import/export feature, so you need to store the credentials file on another secure location - **Phone secure enclave**: There is no solution to back up the keys as you cannot export it. There is no other solution like the one proposed by Cometh (EVM) or Braavos (Starknet) Let's review what we have on Tezos about **Account Global Rules**: - **HomebaseDao**: This use case is an assembly of stakeholders managing a treasury (with or without a governance token). There are voting propositions, weighted votes, and a transfer of money to a specific address - **TzSafe**: It is a multi-signature wallet where propositions should pass a threshold and it can execute different transfers of XTZ, and FA tokens or run arbitrary code. There is no specific rule to manage the Treasury, the Treasury balance is altered by proposals **This does not cover what we expect from a personal/shared bank account** ## General The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in [RFC 2119][RFC 2119]. [RFC 2119]: https://www.ietf.org/rfc/rfc2119.txt The use cases we want to cover : - **Account management: the ability to enroll any device that can sign transactions** - **Enrollment**: add a new address to the smart contract wallet. I can be one of our addresses or from any other person. In this scenario, we call it a "shared account" - **Revocation**: remove an address from the smart contract wallet because a device can be stolen or lost - **Asset recovery by a third party**: It can happen for 2 reasons : - the owner has lost the devices, or someone stole it - the owner is dead or unable to act - **Global rules: the ability to fine-grained control your assets** - **Spending limits**: modern accounts have all quota because hacks happen and an unsolicited debit call could be made. Centralized banks are not always responsible for reimbursing the client. But in some countries, they have to do it, and so, spending limits are a protection for banks to not lose too much money - **Direct debit mandates**: direct debit mandates are an important feature of a bank account. It helps you to pay regular approved bills without any action from your side. On the other side, if these authorizations are hacked (for example if someone steals your CB and does NFC payment) then it is a real problem. This feature is important because it makes your account **non-interactive**, and as so, you can pay as you go on a game without explicitly needing to validate each transaction one by one ## Specification ### Storage - `owners`: a set of `addresses`. - `inheritors`: a set of `addresses`. - `quick_recovery_stake`: The minimum stake a claimer should send to be able to call this function. By default it is equal to the balance of the account, otherwise, it is an amount in `tez`. - `quick_recovery_period`: The time in days for the owner of the smart contract wallet to wait until he can retake control of his SC wallet. By default, it is 1 week. - `status`: An indicator of the status of the SC wallet - `ACTIVE`: default status - `RECOVERING(address,timestamp)`: when the owner or someone else is trying to recover the account - `DEAD`: the owner is considered dead. All entry points are disabled except `reboot_inheritancy_countdown` and `claim_inheritancy` - `inheritancy_countdown_date`: The date when an inheritor can claim a part of the assets, and so, the owner is considered dead - `inheritancy_claims`: a `set<address>` of all claims done on XTZ by inheritors address - `inheritancy_FA_claims`: a `bigMap<[address,address],unit>` of all claims done on FA tokens by inheritors address/FA token address - `spending_limit_XTZ`: maximum `tez` to be sent on a unique transfer - `spending_limit_FA`: maximum `BigMap<[address,nat],nat>` quantity of token n from contract fa to be sent on a unique transfer - `direct_debit_mandates` : a`bigMap<[address,Frequency],tez>` of mandates for XTZ - `direct_debit_mandates_history` : a`bigMap<[address,Frequency],last_execution_timestamp>` of executed mandates for XTZ - `direct_debit_mandates` : a`bigMap<[address,nat,Frequency],nat>` of mandates for FA tokens - `direct_debit_mandates_history` : a`bigMap<[address,nat,Frequency],last_execution_timestamp>` of executed mandates for FA tokens ### Types - `Frequency`: SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR ### Entry points #### `enroll` Enroll an address to the smart contract wallet. Only an owner can do it ``` function enroll(owner : address) ``` #### `revoke` Revoke an address to the smart contract wallet. Only an owner can do it ``` function revoke(owner : address) ``` #### `transfer_XTZ` Send native money to someone. Only an owner can do it ``` function transfer_XTZ(target : address,amount : mutez) ``` #### `transfer_FA` Send FA tokens to someone. Only an owner can do it ``` function transfer_FA(list<{ from_ : address; txs : list<{ to_ : address; amount : Amount.T; token_id : Token.T; }> }>) ``` #### `proof_of_event` Used to generate proof of ownership of the smart contract wallet and allows login with it. Only an owner can do it ``` function proof_of_event(payload : bytes) ``` #### `start_recovery` Initiate a process of recovery due to complete lost or stolen keys. This function requires passing some tez in order to avoid spam. @see `quick_recovery_stake` and `quick_recovery_period` settings. It is only possible if the contract is `ACTIVE`. ``` function start_recovery(claimer : address) ``` #### `claim_recovery` This function should enroll the claimer address as an owner after a certain period of time has passed and the recovery has not been canceled (i.e. in status `RECOVERING`). If ok, the status passes to `ACTIVE`. @see `quick_recovery_stake` and `quick_recovery_period` settings ``` function claim_recovery ``` #### `stop_recovery` Stop the recovery process. This function can be called only by an owner. The status is back to `ACTIVE`. ``` function stop_recovery() ``` #### `set_inheritors` Add inheritors to the contract. Only an owner can do it. ``` function set_inheritors(inheritors : set<address>) ``` #### `remove_inheritors` Remove inheritors from the contract. Only an owner can do it. ``` function remove_inheritors(inheritors_to_remove : set<address>) ``` #### `claim_inheritancy` An inheritor can make a claim to get 1/n assets stored on the contract to its address if the countdown date has passed, where n is the number of inheritors. He can call this function only once. The contract is then considered as status `DEAD`. ``` function claim_inheritancy() ``` #### `claim_inheritancy_FA` An inheritor can make a claim to get 1/n FA assets owned by the contract to its address if the countdown date has passed, where n is the number of inheritors. He can call this function only once. The contract is then considered as status `DEAD`. ``` function claim_inheritancy_FA(fa_address : address) ``` #### `reboot_inheritancy_countdown` Only owners of the contract can postpone by `nbdays` the countdown date. Status passes to `ACTIVE`. ``` function reboot_inheritancy_countdown(nbdays : nat) ``` #### `set_spending_limit_XTZ` Set the spending limit. Only at minimum 2 owner signatures can do this ``` function set_spending_limit_XTZ(amount : tez) ``` #### `set_spending_limit_FA` Set the spending limit on the FA token. Only at minimum 2 owner signatures can do this ``` function set_spending_limit_XTZ(quantity : nat, token_id : nat ) ``` #### `add_direct_debit_mandate_XTZ` Add a new direct debit mandate in XTZ. Only owners can do it ``` function add_direct_debit_mandate_XTZ(beneficiary : address,frequency : Frequency,amount : tez) ``` #### `revoke_direct_debit_mandate_XTZ` Revoke a direct debit mandate in XTZ. Only owners can do it ``` function revoke_direct_debit_mandate_XTZ(beneficiary : address,frequency : Frequency) ``` #### `execute_direct_debit_mandate_XTZ` Execute a direct debit mandate in XTZ. Only the beneficiary can call this function at the correct date and not twice ``` function execute_direct_debit_mandate_XTZ(frequency : Frequency) ``` #### `add_direct_debit_mandate_FA` Add a new direct debit mandate in FA. Only owners can do it ``` function add_direct_debit_mandate_FA(beneficiary : address,token_id : nat,frequency : Frequency,amount : tez) ``` #### `revoke_direct_debit_mandate_FA` Revoke a direct debit mandate in FA. Only owners can do it ``` function revoke_direct_debit_mandate_FA(beneficiary : address,token_id : nat,frequency : Frequency) ``` #### `exec_direct_debit_mandate_FA` Execute a direct debit mandate in FA. Only the beneficiary can call this function at the correct date and not twice ``` function execute_direct_debit_mandate_FA(token_id :nat,frequency : Frequency) ``` ## Security Considerations We recommend exploring the possibility of using a mobile phone secure enclave for signing natively Tezos transactions. It does not exist today but it could be implemented on a new tz5 address. This allows you enrolling several tz addresses and simply sign transactions with a finger touch ## Implementations ### Scenario 1 : Login + quick recovery Alice configures her bank account with 1 device. She logs in with her bank account to a decentralized application and she makes a payment. Then she loses her phone and tries to make a quick recovery. She succeeds and kickoffs the lost phone from the bank account ownership ```mermaid sequenceDiagram Alice->>BankAccountUI: Login(tz1_alice) BankAccountUI->>TempleWallet:Login(tz1_alice) TempleWallet-->>Alice: "tz1_alice" logged Alice->>BankAccountSC: CreateBankAccount(10tez) BankAccountSC-->>Alice: Bank account "KT1_BA" Alice->>WebAppUI: Login(KT1_BA) WebAppUI-->>Alice: "KT1_BA" logged Alice->>WebAppUI: Transfer(tz1_bob,1tez) WebAppUI-->>BankAccountUI: send BeaconEvent event BankAccountUI-->>Alice: Ask to confirm the transfer Alice-->>TempleWallet: Ask to sign transfer_XTZ(tz1_bob,1tez) TempleWallet-->>Alice: Ask to confirm Alice->>BankAccountSC: signed operation "(transfer_XTZ(tz1_bob,1tez))" BankAccountSC-->>Bob : transfer(unit,1tez,tz1_bob) Note right of Alice : Alice lost her "tz1_alice" keys Alice->>BankAccountUI: start_recovery(tz1_alice2) + pay "quick_recovery_stake" XTZ BankAccountUI->>BankAccountSC: start_recovery(tz1_alice2) Note right of BankAccountSC : status = "RECOVERING" , balance += "quick_recovery_stake" XTZ Note right of Alice : Alice waits for "quick_recovery_period" days Alice->>BankAccountUI: claim_recovery() BankAccountUI->>BankAccountSC: claim_recovery(tz1_alice2) Note right of BankAccountSC : status = "ACTIVE" , owners += "tz1_alice2" Alice->>BankAccountUI: revoke("tz1_alice") BankAccountUI->>BankAccountSC: revoke("tz1_alice") Note right of BankAccountSC : owners -= "tz1_alice" ``` ### Scenario 2: Pay my mobile subscription bills every month with a direct debit mandate Alice is contracting a mobile subscription to be paid 10tez each month. She registers a direct debit mandate on her bank account. Every month, her bill is paid automatically ```mermaid sequenceDiagram Alice->>BankAccountSC: add_direct_debit_mandate_XTZ(Tz..Vodafone,1month,10tez) Note right of BankAccountSC : register the mandate Vodafone-->>BankAccountSC: exec_direct_debit_mandate_XTZ(1month,10tez) BankAccountSC-->>Vodafone: 10tez Note right of Vodafone : pause of 10seconds Vodafone-->>BankAccountSC: exec_direct_debit_mandate_XTZ(1month,10tez) BankAccountSC-->>Vodafone: error "You cannot ask for more than the mandate indicated" Alice->>BankAccountSC: revoke_direct_debit_mandate_XTZ(Tz..Vodafone,1month) Note right of BankAccountSC : revoke the mandate ``` [An implementation is available here](https://github.com/marigold-dev/bankaccount) ## Copyright Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).