Try   HackMD

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.

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 : abigMap<[address,Frequency],tez> of mandates for XTZ
  • direct_debit_mandates_history : abigMap<[address,Frequency],last_execution_timestamp> of executed mandates for XTZ
  • direct_debit_mandates : abigMap<[address,nat,Frequency],nat> of mandates for FA tokens
  • direct_debit_mandates_history : abigMap<[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

BobWebAppUIBankAccountSCTempleWalletBankAccountUIAliceBobWebAppUIBankAccountSCTempleWalletBankAccountUIAliceAlice lost her "tz1_alice" keysstatus = "RECOVERING" , balance += "quick_recovery_stake" XTZAlice waits for "quick_recovery_period" daysstatus = "ACTIVE" , owners += "tz1_alice2"owners -= "tz1_alice"Login(tz1_alice)Login(tz1_alice)"tz1_alice" loggedCreateBankAccount(10tez)Bank account "KT1_BA"Login(KT1_BA)"KT1_BA" loggedTransfer(tz1_bob,1tez)send BeaconEvent eventAsk to confirm the transferAsk to sign transfer_XTZ(tz1_bob,1tez)Ask to confirmsigned operation "(transfer_XTZ(tz1_bob,1tez))"transfer(unit,1tez,tz1_bob)start_recovery(tz1_alice2) + pay "quick_recovery_stake" XTZstart_recovery(tz1_alice2)claim_recovery()claim_recovery(tz1_alice2)revoke("tz1_alice")revoke("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

VodafoneBankAccountSCAliceVodafoneBankAccountSCAliceregister the mandatepause of 10secondsrevoke the mandateadd_direct_debit_mandate_XTZ(Tz..Vodafone,1month,10tez)exec_direct_debit_mandate_XTZ(1month,10tez)10tezexec_direct_debit_mandate_XTZ(1month,10tez)error "You cannot ask for more than the mandate indicated"revoke_direct_debit_mandate_XTZ(Tz..Vodafone,1month)

An implementation is available here

Copyright and related rights waived via
CC0.