123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- --- tokenomics
- -- @module tokenomics
- -- Depends on consent
- local tokenomics = {
- name = "Tokenomics",
- slug = "tokenomics",
- desc = "A simple library for token economics",
- hide = true;
- }
- --- (Required) Data for module
- -- Variables that module uses during the course of a process
- -- Can be blank
- tokenomics.data = {
- result = nil
- }
- --- (Required): config for module
- -- @field consent Require consent to create?
- -- @field token_variables the data that goes into the token
- -- @field token_slug A no-spaces slug for the token
- -- @field initial_treasury Quantity in org treasury
- -- @field negative_spend Boolean: can users spend negative tokens? (for mutual credit)
- -- @field balances Table of user balances
- tokenomics.config = {
- consent = false,
- token_slug = "token",
- token_variables = {
- treasury = 0,
- negative_spend = true,
- balances = {}
- }
- }
- --- (Required): initiate function: creates a token in an org
- -- set up the token data structure
- -- create an org treasury
- -- @param result (optional) Callback if this module is embedded in other modules
- -- @function initiate
- function tokenomics:initiate(result)
- -- TODO need to create a series of interactions to get the info from users
- self.data.result = result
- if self.org.tokens and self.org.tokens[slug] then
- modpol.interactions.message(
- self.initiator, "Token slug taken, aborting")
- self.org:delete_process(self.id)
- else
- if self.config.consent then
- self:call_module(
- "consent",
- self.initiator,
- {
- prompt = "Create token "..
- self.config.token_slug.."?",
- votes_required = #self.org.members
- },
- function()
- modpol.interactions.message_org(
- self.initiator, self.org.id,
- "Consent reached: creating token "..
- self.config.token_slug)
- self:create_token()
- end
- )
- else
- self:create_token()
- end
- end
- end
- function tokenomics:create_token()
- if not self.org.tokens then self.org.tokens = {} end
- self.org.tokens[self.config.token_slug] =
- self.config.token_variables
- self.org:record("Created token "..self.config.token_slug,
- self.slug)
- modpol.interactions.message_org(
- self.initiator, self.org.id,
- "Created token "..self.config.token_slug)
- if self.data.result then self.data.result() end
- -- call this wherever process might end:
- self.org:delete_process(self.id)
- end
- -----------------------------------------
- -- Utility functions
- -- all need to account for the fact that some users may not yet have balances
- -- all need to write to persistent data
- -- amount can be positive or negative (except transfer)
- -- returns balance
- -- if no user, get treasury balance
- -- @param org Name (string) or id (num)
- -- @param token Slug (string)
- -- @param user Name (string)
- function tokenomics.balance(org, token, user)
- local this_org = modpol.orgs.get_org(org)
- if not this_org[token] then return nil, "Token not found" end
- if not user then
- return this_org[token].treasury
- end
- if not this_org[user] then
- return nil, "User not found"
- else
- local user_balance = this_org[token].balances[user]
- if user_balance then
- return user_balance
- else
- return 0
- end
- end
- end
- -- @param org Org name (string) or id (number)
- -- @param token Token slug (string)
- function tokenomics.change_balance(org, token, user, amount)
- local this_org = modpol.orgs.get_org(org)
- if not this_org then
- return nil, "Cannot change balance: Org not found"
- elseif not this_org.tokens then
- return nil, "Cannot change balance: no tokens found"
- elseif not this_org.tokens[token] then
- return nil, "Cannot change balance: token not found"
- elseif not this_org.members[user] then
- return nil, "Cannot change balance: user not in org"
- elseif not tonumber(amount) then
- return nil, "Cannot change balance: invalid amount"
- else
- local old_balance = this_org.tokens[token].balances[user]
- if not old_balance then old_balance = 0 end
- local new_balance = old_balance + amount
- this_org.tokens[token].balances[user] = new_balance
- local msg = "Your balance of token "..token..
- " changed from "..old_balance.." to "..new_balance
- modpol.interactions.message(user, msg)
- self.org:record(
- "Changed token balance for "..user,self.slug)
- end
- end
- -- @param amount Positive number
- function tokenomics.transfer(org, token, sender, recipient, amount)
- local sender_balance = tokenomics.balance(org, token, sender)
- local recipient_balance = tokenomics.balance(org, token, recipient)
- if not sender_balance or recipient_balance then
- return nil, "Transfer failed, user not found"
- else
- sender_balance = sender_balance - amount
- recipient_balance = recipient_balance + amount
- local neg_spend = modpol.orgs.get_org(org).tokens[token].negative_spend
- if sender_balance < 0 and not neg_spend then
- return nil, "Transfer failed, negative spend not allowed"
- else
- tokenomics.change_balance(
- org, token, sender, sender_balance)
- tokenomics.change_balance(
- org, token, recipient, recipient_balance)
- return "Transfer complete"
- end
- end
- end
- -- @param amount Can be positive or negative, assumes flow from treasury to recipient
- function tokenomics.treasury_transfer(org, token, recipient, amount)
- local this_org = modpol.orgs.get_org(org)
- if not this_org then
- return nil, "Cannot transfer treasury: Org not found"
- elseif not this_org.tokens then
- return nil, "Cannot transfer treasury: no tokens found"
- elseif not this_org.tokens[token] then
- return nil, "Cannot transfer treasury: token not found"
- elseif not this_org.members[user] then
- return nil, "Cannot transfer treasury: user not in org"
- elseif not tonumber(amount) then
- return nil, "Cannot change balance: invalid amount"
- else
- local new_treasury = this_org.tokens[token].treasury - amount
- local new_recipient_balance = tokenomics.balance(org, token, recipient) + amount
- if new_treasury < 0 and not this_org.tokens[token].negative_spend then
- return nil, "Transfer failed, negative spend not allowed"
- elseif new_recipient_balance < 0 and not this_org.tokens[token].negative_spend then
- return nil, "Transfer failed, negative spend not allowed"
- else
- this_org.tokens[token].treasury = new_treasury
- self.org:record("Treasury payment",self.slug)
- tokenomics.change_balance(org, token, recipient, amount)
- end
- end
- end
- -- creates new tokens in the org treasury
- function tokenomics.issue(org, token, amount)
- local this_org = modpol.orgs.get_org(org)
- if not this_org then
- return nil, "Cannot transfer treasury: Org not found"
- elseif not this_org.tokens then
- return nil, "Cannot transfer treasury: no tokens found"
- elseif not this_org.tokens[token] then
- return nil, "Cannot transfer treasury: token not found"
- elseif not tonumber(amount) then
- return nil, "Cannot change balance: invalid amount"
- else
- this_org.tokens[token].treasury =
- this_org.tokens[token].treasury + amount
- self.org:record("Issued tokes to treasury","tokenomics")
- end
- end
- ------------------------------------------
- --- (Required) Add to module table
- modpol.modules.tokenomics = tokenomics
|