tokenomics.lua 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. --- tokenomics
  2. -- @module tokenomics
  3. local tokenomics = {
  4. name = "Tokenomics",
  5. slug = "tokenomics",
  6. desc = "A simple library for token economics",
  7. hide = true;
  8. }
  9. --- (Required) Data for module
  10. -- Variables that module uses during the course of a process
  11. -- Can be blank
  12. tokenomics.data = {
  13. result = nil
  14. }
  15. --- (Required): config for module
  16. -- @field consent Require consent to create?
  17. -- @field token_variables the data that goes into the token
  18. -- @field token_slug A no-spaces slug for the token
  19. -- @field initial_treasury Quantity in org treasury
  20. -- @field negative_spend Boolean: can users spend negative tokens? (for mutual credit)
  21. -- @field balances Table of user balances
  22. tokenomics.config = {
  23. consent = false,
  24. token_slug = "token",
  25. token_variables = {
  26. treasury = 0,
  27. negative_spend = true,
  28. balances = {}
  29. }
  30. }
  31. --- (Required): initiate function: creates a token in an org
  32. -- set up the token data structure
  33. -- create an org treasury
  34. -- @param result (optional) Callback if this module is embedded in other modules
  35. -- @function initiate
  36. function tokenomics:initiate(result)
  37. -- TODO need to create a series of interactions to get the info from users
  38. self.data.result = result
  39. if self.org.tokens and self.org.tokens[slug] then
  40. modpol.interactions.message(
  41. self.initiator, "Token slug taken, aborting")
  42. self.org:delete_process(self.id)
  43. else
  44. if self.config.consent then
  45. self.org:call_module(
  46. "consent",
  47. self.initiator,
  48. {
  49. prompt = "Create token "..self.token_slug.."?",
  50. votes_required = #self.org.members
  51. },
  52. function()
  53. self:create_token()
  54. end
  55. )
  56. else
  57. self:create_token()
  58. end
  59. end
  60. end
  61. function tokenomics:create_token()
  62. if not self.org.tokens then self.org.tokens = {} end
  63. self.org.tokens[slug] = self.config.token_variables
  64. modpol.interactions.message_org(
  65. self.initiator, self.org.id,
  66. "Token "..self.config.token_slug.." created")
  67. -- TODO need to add to persistent storage?
  68. if self.data.result then self.data.result() end
  69. -- call this wherever process might end:
  70. self.org:delete_process(self.id)
  71. end
  72. -----------------------------------------
  73. -- Utility functions
  74. -- all need to account for the fact that some users may not yet have balances
  75. -- all need to write to persistent data
  76. -- amount can be positive or negative (except transfer)
  77. -- returns balance
  78. -- if no user, get treasury balance
  79. -- @param org Name (string) or id (num)
  80. -- @param token Slug (string)
  81. -- @param user Name (string)
  82. function tokenomics.balance(org, token, user)
  83. local this_org = modpol.orgs.get_org(org)
  84. if not this_org[token] then return nil, "Token not found" end
  85. if not user then
  86. return this_org[token].treasury
  87. end
  88. if not this_org[user] then
  89. return nil, "User not found"
  90. else
  91. local user_balance = this_org[token].balances[user]
  92. if user_balance then
  93. return user_balance
  94. else
  95. return 0
  96. end
  97. end
  98. end
  99. -- @param org Org name (string) or id (number)
  100. -- @param token Token slug (string)
  101. function tokenomics.change_balance(org, token, user, amount)
  102. local this_org = modpol.orgs.get_org(org)
  103. if not this_org then
  104. return nil, "Cannot change balance: Org not found"
  105. elseif not this_org.tokens then
  106. return nil, "Cannot change balance: no tokens found"
  107. elseif not this_org.tokens[token] then
  108. return nil, "Cannot change balance: token not found"
  109. elseif not this_org.members[user] then
  110. return nil, "Cannot change balance: user not in org"
  111. elseif not tonumber(amount) then
  112. return nil, "Cannot change balance: invalid amount"
  113. else
  114. local old_balance = this_org.tokens[token].balances[user]
  115. if not old_balance then old_balance = 0 end
  116. local new_balance = old_balance + amount
  117. this_org.tokens[token].balances[user] = new_balance
  118. local msg = "Your balance of token "..token..
  119. " changed from "..old_balance.." to "..new_balance
  120. modpol.interactions.message(user, msg)
  121. self.org:record(
  122. "Changed token balance for "..user,self.slug)
  123. end
  124. end
  125. -- @param amount Positive number
  126. function tokenomics.transfer(org, token, sender, recipient, amount)
  127. local sender_balance = tokenomics.balance(org, token, sender)
  128. local recipient_balance = tokenomics.balance(org, token, recipient)
  129. if not sender_balance or recipient balance then
  130. return nil, "Transfer failed, user not found"
  131. else
  132. sender_balance = sender_balance - amount
  133. recipient_balance = recipient_balance + amount
  134. local neg_spend = modpol.orgs.get_org(org).tokens[token].negative_spend
  135. if sender_balance < 0 and not neg_spend then
  136. return nil, "Transfer failed, negative spend not allowed"
  137. else
  138. tokenomics.change_balance(
  139. org, token, sender, sender_balance)
  140. tokenomics.change_balance(
  141. org, token, recipient, recipient_balance)
  142. return "Transfer complete"
  143. end
  144. end
  145. end
  146. -- @param amount Can be positive or negative, assumes flow from treasury to recipient
  147. function tokenomics.treasury_transfer(org, token, recipient, amount)
  148. local this_org = modpol.orgs.get_org(org)
  149. if not this_org then
  150. return nil, "Cannot transfer treasury: Org not found"
  151. elseif not this_org.tokens then
  152. return nil, "Cannot transfer treasury: no tokens found"
  153. elseif not this_org.tokens[token] then
  154. return nil, "Cannot transfer treasury: token not found"
  155. elseif not this_org.members[user] then
  156. return nil, "Cannot transfer treasury: user not in org"
  157. elseif not tonumber(amount) then
  158. return nil, "Cannot change balance: invalid amount"
  159. else
  160. local new_treasury = this_org.tokens[token].treasury - amount
  161. local new_recipient_balance = tokenomics.balance(org, token, recipient) + amount
  162. if new_treasury < 0 and not this_org.tokens[token].negative_spend then
  163. return nil, "Transfer failed, negative spend not allowed"
  164. elseif new_recipient_balance < 0 and not this_org.tokens[token].negative_spend then
  165. return nil, "Transfer failed, negative spend not allowed"
  166. else
  167. this_org.tokens[token].treasury = new_treasury
  168. self.org:record("Treasury payment",self.slug)
  169. tokenomics.change_balance(org, token, recipient, amount)
  170. end
  171. end
  172. end
  173. -- creates new tokens in the org treasury
  174. function tokenomics.issue(org, token, amount)
  175. local this_org = modpol.orgs.get_org(org)
  176. if not this_org then
  177. return nil, "Cannot transfer treasury: Org not found"
  178. elseif not this_org.tokens then
  179. return nil, "Cannot transfer treasury: no tokens found"
  180. elseif not this_org.tokens[token] then
  181. return nil, "Cannot transfer treasury: token not found"
  182. elseif not tonumber(amount) then
  183. return nil, "Cannot change balance: invalid amount"
  184. else
  185. this_org.tokens[token].treasury =
  186. this_org.tokens[token].treasury + amount
  187. self.org:record("Issued tokes to treasury","tokenomics")
  188. end
  189. end
  190. ------------------------------------------
  191. --- (Required) Add to module table
  192. modpol.modules.tokenomics = tokenomics