tokenomics.lua 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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. tokenomics.data = {
  10. result = nil
  11. }
  12. --- Config for module
  13. -- @field token_variables the data that goes into the token
  14. -- @field token_slug A no-spaces slug for the token
  15. -- @field initial_treasury Quantity in org treasury
  16. -- @field negative_spend Boolean: can users spend negative tokens? (for mutual credit)
  17. -- @field balances Table of user balances
  18. tokenomics.config = {
  19. approval_module = false,
  20. token_slug = "token",
  21. token_variables = {
  22. treasury = 0,
  23. negative_spend = true,
  24. balances = {}
  25. }
  26. }
  27. --- Initiate function: creates a token in an org.
  28. -- Set up the token data structure.
  29. -- Create an org treasury
  30. -- @function tokenomics:initiate
  31. -- @param result (optional) Callback if this module is embedded in other modules
  32. function tokenomics:initiate(result)
  33. -- TODO need to create a series of interactions to get the info from users
  34. self.data.result = result
  35. if self.org.tokens and self.org.tokens[slug] then
  36. modpol.interactions.message(
  37. self.initiator, "Token slug taken, aborting")
  38. self.org:delete_process(self.id)
  39. else
  40. self:call_module(
  41. self.config.approval_module,
  42. self.initiator,
  43. {
  44. prompt = "Create token " ..
  45. self.config.token_slug .. "?"
  46. },
  47. function()
  48. modpol.interactions.message_org(
  49. self.initiator, self.org.id,
  50. "Creating token " ..
  51. self.config.token_slug)
  52. self:create_token()
  53. end
  54. )
  55. end
  56. end
  57. --- Create token
  58. -- @function tokenomics:create_token
  59. function tokenomics:create_token()
  60. if not self.org.tokens then self.org.tokens = {} end
  61. self.org.tokens[self.config.token_slug] =
  62. self.config.token_variables
  63. self.org:record("Created token "..self.config.token_slug,
  64. self.slug)
  65. modpol.interactions.message_org(
  66. self.initiator, self.org.id,
  67. "Created token "..self.config.token_slug)
  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. -- @function tokenomics.balance
  80. -- @param org Name (string) or id (num)
  81. -- @param token Slug (string)
  82. -- @param user Name (string)
  83. function tokenomics.balance(org, token, user)
  84. local this_org = modpol.orgs.get_org(org)
  85. if not this_org[token] then return nil, "Token not found" end
  86. if not user then
  87. return this_org[token].treasury
  88. end
  89. if not this_org[user] then
  90. return nil, "User not found"
  91. else
  92. local user_balance = this_org[token].balances[user]
  93. if user_balance then
  94. return user_balance
  95. else
  96. return 0
  97. end
  98. end
  99. end
  100. --- Change balance
  101. -- @function tokenomics.change_balance
  102. -- @param org Org name (string) or id (number)
  103. -- @param token Token slug (string)
  104. -- @param user
  105. -- @param amount
  106. function tokenomics.change_balance(org, token, user, amount)
  107. local this_org = modpol.orgs.get_org(org)
  108. if not this_org then
  109. return nil, "Cannot change balance: Org not found"
  110. elseif not this_org.tokens then
  111. return nil, "Cannot change balance: no tokens found"
  112. elseif not this_org.tokens[token] then
  113. return nil, "Cannot change balance: token not found"
  114. elseif not this_org.members[user] then
  115. return nil, "Cannot change balance: user not in org"
  116. elseif not tonumber(amount) then
  117. return nil, "Cannot change balance: invalid amount"
  118. else
  119. local old_balance = this_org.tokens[token].balances[user]
  120. if not old_balance then old_balance = 0 end
  121. local new_balance = old_balance + amount
  122. this_org.tokens[token].balances[user] = new_balance
  123. local msg = "Your balance of token "..token..
  124. " changed from "..old_balance.." to "..new_balance
  125. modpol.interactions.message(user, msg)
  126. self.org:record(
  127. "Changed token balance for "..user,self.slug)
  128. end
  129. end
  130. --- Transfer tokens from a sender to recipient
  131. -- @function tokenomics.transfer
  132. -- @param org
  133. -- @param token
  134. -- @param sender
  135. -- @param recipient
  136. -- @param amount Positive number
  137. function tokenomics.transfer(org, token, sender, recipient, amount)
  138. local sender_balance = tokenomics.balance(org, token, sender)
  139. local recipient_balance = tokenomics.balance(org, token, recipient)
  140. if not sender_balance or recipient_balance then
  141. return nil, "Transfer failed, user not found"
  142. else
  143. sender_balance = sender_balance - amount
  144. recipient_balance = recipient_balance + amount
  145. local neg_spend = modpol.orgs.get_org(org).tokens[token].negative_spend
  146. if sender_balance < 0 and not neg_spend then
  147. return nil, "Transfer failed, negative spend not allowed"
  148. else
  149. tokenomics.change_balance(
  150. org, token, sender, sender_balance)
  151. tokenomics.change_balance(
  152. org, token, recipient, recipient_balance)
  153. return "Transfer complete"
  154. end
  155. end
  156. end
  157. --- Transfer from treasury
  158. -- @function tokenomics.treasury_transfer
  159. -- @param org
  160. -- @param token
  161. -- @param recipient
  162. -- @param amount Can be positive or negative, assumes flow from treasury to recipient
  163. function tokenomics.treasury_transfer(org, token, recipient, amount)
  164. local this_org = modpol.orgs.get_org(org)
  165. if not this_org then
  166. return nil, "Cannot transfer treasury: Org not found"
  167. elseif not this_org.tokens then
  168. return nil, "Cannot transfer treasury: no tokens found"
  169. elseif not this_org.tokens[token] then
  170. return nil, "Cannot transfer treasury: token not found"
  171. elseif not this_org.members[user] then
  172. return nil, "Cannot transfer treasury: user not in org"
  173. elseif not tonumber(amount) then
  174. return nil, "Cannot change balance: invalid amount"
  175. else
  176. local new_treasury = this_org.tokens[token].treasury - amount
  177. local new_recipient_balance = tokenomics.balance(org, token, recipient) + amount
  178. if new_treasury < 0 and not this_org.tokens[token].negative_spend then
  179. return nil, "Transfer failed, negative spend not allowed"
  180. elseif new_recipient_balance < 0 and not this_org.tokens[token].negative_spend then
  181. return nil, "Transfer failed, negative spend not allowed"
  182. else
  183. this_org.tokens[token].treasury = new_treasury
  184. self.org:record("Treasury payment",self.slug)
  185. tokenomics.change_balance(org, token, recipient, amount)
  186. end
  187. end
  188. end
  189. --- Creates new tokens in the org treasury
  190. -- @function tokenomics.issue
  191. -- @param org
  192. -- @param token
  193. -- @param amount
  194. function tokenomics.issue(org, token, amount)
  195. local this_org = modpol.orgs.get_org(org)
  196. if not this_org then
  197. return nil, "Cannot transfer treasury: Org not found"
  198. elseif not this_org.tokens then
  199. return nil, "Cannot transfer treasury: no tokens found"
  200. elseif not this_org.tokens[token] then
  201. return nil, "Cannot transfer treasury: token not found"
  202. elseif not tonumber(amount) then
  203. return nil, "Cannot change balance: invalid amount"
  204. else
  205. this_org.tokens[token].treasury =
  206. this_org.tokens[token].treasury + amount
  207. self.org:record("Issued tokes to treasury","tokenomics")
  208. end
  209. end
  210. ------------------------------------------
  211. modpol.modules.tokenomics = tokenomics