Proposal: Wallet Contexts
This proposal outlines a change to the wallet to help make the wallet's behavior easier to understand, and to more easily facilitate certain behaviors that depend on the wallet.
Motivation
Users want to easily understand every transaction that occurs in their wallet. In Sia, there are more reasons a transaction can occur than other cryptocurrencies. Users can receive and send Siacoin of their own volition, which is currently relatively easy to understand, but the software also transacts on behalf of users in order to form contracts and transfer data.
Currently, these non-user-initiated transactions appear exactly the same to users as user-initiated transactions. This is confusing behavior: "why is Sia spending my money?", "What are all these small transactions?" are common sentiments expressed on reddit, the forum, and elsewhere.
In addition, there is a use case for establishing 'sub-wallets' or contextual wallets outside of being able to tag transactions related to a certain behavior. An example use case for this would be server operators wishing to serve many wallet users from a single application running a single Sia node. This isn't currently possible, but would be with the proposed changes.
A major motivation for this change is the Allowance UX improvement proposal: https://github.com/NebulousLabs/Sia/issues/2246. This proposal outlines a change to the wallet which would easily facilitate that behavior.
High Level Overview
Each transaction in the wallet should be tagged with metadata. This metadata should indicate the context
from which the transaction originated. Every transaction in the wallet should have an associated context
, in effect creating contextualized 'sub-wallets' within the wallet module. Every wallet API call should be updated to take a context
parameter. Contexts
have a configurable balance, and if a transaction would result in lower than the configured limit the wallet will return an insufficient balance
error.
Wallet API calls with context:
GET /wallet/transactions
-> returns every transaction in the wallet.
GET /wallet/transactions?context="contractor"
-> returns every transaction in the wallet's contractor
context.
POST /wallet/siacoins
-> sends siacoins.
POST /wallet/siacoins?context="contractor"
-> sends siacoins, from the wallet's contractor
context.
Other existing wallet API calls can be updated similarly to support contexts. Two new API calls are hereby proposed:
GET /wallet/contexts
-> list every context currently tracked by the wallet, and their balances.
POST /wallet/contexts
-> add or update an existing context's spending limit.
One field should be added to the modules.ProcessedTransaction
type, Context
. The new ProcessedTransaction
type therefore has the following signature:
ProcessedTransaction struct {
Transaction types.Transaction `json:"transaction"`
TransactionID types.TransactionID `json:"transactionid"`
ConfirmationHeight types.BlockHeight `json:"confirmationheight"`
ConfirmationTimestamp types.Timestamp `json:"confirmationtimestamp"`
Context string `json:"context"`
Inputs []ProcessedInput `json:"inputs"`
Outputs []ProcessedOutput `json:"outputs"`
}
Implementation
Two new database buckets should be added: bucketContextBalances
and bucketTransactionContexts
.
bucketContextBalances
stores the mapping of context names -> unspent balances. bucketTransactionContexts
stores the mapping of transaction id -> contexts.
The TransactionBuilder
interface should be updated to support contexts.
One method should be added, SetContext
:
func (tb *TransactionBuilder) SetContext(context string)
SetContext
configures a transaction builder to use the specified context
.
When funding transactions, the transaction builder will first check if its context
has enough to fund the transaction. If it does, the context's balance should be updated accordingly.
The Wallet
interface will also need to be changed slightly. Proposed are the following additional methods:
// SetContextLimit sets the spending limit for a wallet context to the
// value specified by limit. Transactions which use the specified context
// will be limited to `limit` spending. Calling SetContextLimit again on
// the same context resets the spending limit to the value provided.
func (w *Wallet) SetContextLimit(context string, limit types.Currency)
// Contexts returns every WalletContext currently in use by the wallet.
func (w *Wallet) Contexts() []modules.WalletContext
A modules.WalletContext
has the following type signature:
WalletContext struct {
Name string `json:"name"`
Balance types.Currency `json:"balance"`
}
ConfirmedBalance
, UnconfirmedBalance
, SendSiacoins
, and SendSiafunds
must also be updated to take a context
parameter.
As the comment explains, SetContextLimit
sets a spending limit for a given
context
. Contexts
returns every context in the wallet.
Considerations
Under this model (storing contexts in the wallet database locally), the context of outputs will not be recoverable from seed.