tokenomics.lua 7.4 KB

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