tokenomics.lua 7.8 KB

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