--- Tokenomics. -- @module tokenomics local tokenomics = { name = "Tokenomics", slug = "tokenomics", desc = "A simple library for token economics", hide = true; } tokenomics.data = { result = nil } --- Config for module -- @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 = { approval_module = false, token_slug = "token", token_variables = { treasury = 0, negative_spend = true, balances = {} } } --- Initiate function: creates a token in an org. -- Set up the token data structure. -- Create an org treasury -- @function tokenomics:initiate -- @param result (optional) Callback if this module is embedded in other modules 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 self:call_module( self.config.approval_module, self.initiator, { prompt = "Create token " .. self.config.token_slug .. "?" }, function() modpol.interactions.message_org( self.initiator, self.org.id, "Creating token " .. self.config.token_slug) self:create_token() end ) end end --- Create token -- @function tokenomics:create_token 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 -- @function tokenomics.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 --- Change balance -- @function tokenomics.change_balance -- @param org Org name (string) or id (number) -- @param token Token slug (string) -- @param user -- @param amount 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 --- Transfer tokens from a sender to recipient -- @function tokenomics.transfer -- @param org -- @param token -- @param sender -- @param recipient -- @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 --- Transfer from treasury -- @function tokenomics.treasury_transfer -- @param org -- @param token -- @param recipient -- @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 -- @param org -- @param token -- @param amount 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 ------------------------------------------ modpol.modules.tokenomics = tokenomics