@sudobility/contracts 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/README.md +353 -0
  2. package/artifacts/contracts/Mailer.sol/Mailer.d.ts +1146 -0
  3. package/artifacts/contracts/Mailer.sol/Mailer.dbg.json +4 -0
  4. package/artifacts/contracts/Mailer.sol/Mailer.json +1096 -0
  5. package/artifacts/contracts/Mailer.sol/artifacts.d.ts +21 -0
  6. package/artifacts/contracts/MockUSDC.sol/MockUSDC.d.ts +284 -0
  7. package/artifacts/contracts/MockUSDC.sol/MockUSDC.dbg.json +4 -0
  8. package/artifacts/contracts/MockUSDC.sol/MockUSDC.json +234 -0
  9. package/artifacts/contracts/MockUSDC.sol/artifacts.d.ts +21 -0
  10. package/artifacts/contracts/interfaces/IERC20.sol/IERC20.d.ts +157 -0
  11. package/artifacts/contracts/interfaces/IERC20.sol/IERC20.dbg.json +4 -0
  12. package/artifacts/contracts/interfaces/IERC20.sol/IERC20.json +107 -0
  13. package/artifacts/contracts/interfaces/IERC20.sol/artifacts.d.ts +21 -0
  14. package/dist/evm/src/evm/index.d.ts +4 -0
  15. package/dist/evm/src/evm/index.d.ts.map +1 -0
  16. package/dist/evm/src/evm/index.js +10 -0
  17. package/dist/evm/src/evm/index.js.map +1 -0
  18. package/dist/evm/src/evm/mailer-client.d.ts +966 -0
  19. package/dist/evm/src/evm/mailer-client.d.ts.map +1 -0
  20. package/dist/evm/src/evm/mailer-client.js +619 -0
  21. package/dist/evm/src/evm/mailer-client.js.map +1 -0
  22. package/dist/evm/typechain-types/Mailer.d.ts +698 -0
  23. package/dist/evm/typechain-types/Mailer.d.ts.map +1 -0
  24. package/dist/evm/typechain-types/Mailer.js +3 -0
  25. package/dist/evm/typechain-types/Mailer.js.map +1 -0
  26. package/dist/evm/typechain-types/MockUSDC.d.ts +118 -0
  27. package/dist/evm/typechain-types/MockUSDC.d.ts.map +1 -0
  28. package/dist/evm/typechain-types/MockUSDC.js +3 -0
  29. package/dist/evm/typechain-types/MockUSDC.js.map +1 -0
  30. package/dist/evm/typechain-types/common.d.ts +51 -0
  31. package/dist/evm/typechain-types/common.d.ts.map +1 -0
  32. package/dist/evm/typechain-types/common.js +3 -0
  33. package/dist/evm/typechain-types/common.js.map +1 -0
  34. package/dist/evm/typechain-types/factories/Mailer__factory.d.ts +875 -0
  35. package/dist/evm/typechain-types/factories/Mailer__factory.d.ts.map +1 -0
  36. package/dist/evm/typechain-types/factories/Mailer__factory.js +1125 -0
  37. package/dist/evm/typechain-types/factories/Mailer__factory.js.map +1 -0
  38. package/dist/evm/typechain-types/factories/MockUSDC__factory.d.ts +193 -0
  39. package/dist/evm/typechain-types/factories/MockUSDC__factory.d.ts.map +1 -0
  40. package/dist/evm/typechain-types/factories/MockUSDC__factory.js +263 -0
  41. package/dist/evm/typechain-types/factories/MockUSDC__factory.js.map +1 -0
  42. package/dist/evm/typechain-types/factories/index.d.ts +4 -0
  43. package/dist/evm/typechain-types/factories/index.d.ts.map +1 -0
  44. package/dist/evm/typechain-types/factories/index.js +45 -0
  45. package/dist/evm/typechain-types/factories/index.js.map +1 -0
  46. package/dist/evm/typechain-types/factories/interfaces/IERC20__factory.d.ts +80 -0
  47. package/dist/evm/typechain-types/factories/interfaces/IERC20__factory.d.ts.map +1 -0
  48. package/dist/evm/typechain-types/factories/interfaces/IERC20__factory.js +116 -0
  49. package/dist/evm/typechain-types/factories/interfaces/IERC20__factory.js.map +1 -0
  50. package/dist/evm/typechain-types/factories/interfaces/index.d.ts +2 -0
  51. package/dist/evm/typechain-types/factories/interfaces/index.d.ts.map +1 -0
  52. package/dist/evm/typechain-types/factories/interfaces/index.js +9 -0
  53. package/dist/evm/typechain-types/factories/interfaces/index.js.map +1 -0
  54. package/dist/evm/typechain-types/index.d.ts +10 -0
  55. package/dist/evm/typechain-types/index.d.ts.map +1 -0
  56. package/dist/evm/typechain-types/index.js +44 -0
  57. package/dist/evm/typechain-types/index.js.map +1 -0
  58. package/dist/evm/typechain-types/interfaces/IERC20.d.ts +70 -0
  59. package/dist/evm/typechain-types/interfaces/IERC20.d.ts.map +1 -0
  60. package/dist/evm/typechain-types/interfaces/IERC20.js +3 -0
  61. package/dist/evm/typechain-types/interfaces/IERC20.js.map +1 -0
  62. package/dist/evm/typechain-types/interfaces/index.d.ts +2 -0
  63. package/dist/evm/typechain-types/interfaces/index.d.ts.map +1 -0
  64. package/dist/evm/typechain-types/interfaces/index.js +3 -0
  65. package/dist/evm/typechain-types/interfaces/index.js.map +1 -0
  66. package/dist/solana/solana/index.d.ts +3 -0
  67. package/dist/solana/solana/index.d.ts.map +1 -0
  68. package/dist/solana/solana/index.js +21 -0
  69. package/dist/solana/solana/index.js.map +1 -0
  70. package/dist/solana/solana/mailer-client.d.ts +282 -0
  71. package/dist/solana/solana/mailer-client.d.ts.map +1 -0
  72. package/dist/solana/solana/mailer-client.js +989 -0
  73. package/dist/solana/solana/mailer-client.js.map +1 -0
  74. package/dist/solana/solana/types.d.ts +28 -0
  75. package/dist/solana/solana/types.d.ts.map +1 -0
  76. package/dist/solana/solana/types.js +23 -0
  77. package/dist/solana/solana/types.js.map +1 -0
  78. package/dist/solana/utils/currency.d.ts +26 -0
  79. package/dist/solana/utils/currency.d.ts.map +1 -0
  80. package/dist/solana/utils/currency.js +36 -0
  81. package/dist/solana/utils/currency.js.map +1 -0
  82. package/dist/unified/package.json +3 -0
  83. package/dist/unified/src/evm/index.d.ts +4 -0
  84. package/dist/unified/src/evm/index.d.ts.map +1 -0
  85. package/dist/unified/src/evm/index.js +10 -0
  86. package/dist/unified/src/evm/index.js.map +1 -0
  87. package/dist/unified/src/evm/mailer-client.d.ts +966 -0
  88. package/dist/unified/src/evm/mailer-client.d.ts.map +1 -0
  89. package/dist/unified/src/evm/mailer-client.js +619 -0
  90. package/dist/unified/src/evm/mailer-client.js.map +1 -0
  91. package/dist/unified/src/react/context/MailerProvider.d.ts +102 -0
  92. package/dist/unified/src/react/context/MailerProvider.d.ts.map +1 -0
  93. package/dist/unified/src/react/context/MailerProvider.js +160 -0
  94. package/dist/unified/src/react/context/MailerProvider.js.map +1 -0
  95. package/dist/unified/src/react/hooks/useMailerMutations.d.ts +301 -0
  96. package/dist/unified/src/react/hooks/useMailerMutations.d.ts.map +1 -0
  97. package/dist/unified/src/react/hooks/useMailerMutations.js +417 -0
  98. package/dist/unified/src/react/hooks/useMailerMutations.js.map +1 -0
  99. package/dist/unified/src/react/hooks/useMailerQueries.d.ts +130 -0
  100. package/dist/unified/src/react/hooks/useMailerQueries.d.ts.map +1 -0
  101. package/dist/unified/src/react/hooks/useMailerQueries.js +197 -0
  102. package/dist/unified/src/react/hooks/useMailerQueries.js.map +1 -0
  103. package/dist/unified/src/react/index.d.ts +37 -0
  104. package/dist/unified/src/react/index.d.ts.map +1 -0
  105. package/dist/unified/src/react/index.js +70 -0
  106. package/dist/unified/src/react/index.js.map +1 -0
  107. package/dist/unified/src/solana/index.d.ts +3 -0
  108. package/dist/unified/src/solana/index.d.ts.map +1 -0
  109. package/dist/unified/src/solana/index.js +21 -0
  110. package/dist/unified/src/solana/index.js.map +1 -0
  111. package/dist/unified/src/solana/mailer-client.d.ts +282 -0
  112. package/dist/unified/src/solana/mailer-client.d.ts.map +1 -0
  113. package/dist/unified/src/solana/mailer-client.js +989 -0
  114. package/dist/unified/src/solana/mailer-client.js.map +1 -0
  115. package/dist/unified/src/solana/types.d.ts +28 -0
  116. package/dist/unified/src/solana/types.d.ts.map +1 -0
  117. package/dist/unified/src/solana/types.js +23 -0
  118. package/dist/unified/src/solana/types.js.map +1 -0
  119. package/dist/unified/src/unified/index.d.ts +4 -0
  120. package/dist/unified/src/unified/index.d.ts.map +1 -0
  121. package/dist/unified/src/unified/index.js +9 -0
  122. package/dist/unified/src/unified/index.js.map +1 -0
  123. package/dist/unified/src/unified/onchain-mailer-client.d.ts +173 -0
  124. package/dist/unified/src/unified/onchain-mailer-client.d.ts.map +1 -0
  125. package/dist/unified/src/unified/onchain-mailer-client.js +1048 -0
  126. package/dist/unified/src/unified/onchain-mailer-client.js.map +1 -0
  127. package/dist/unified/src/unified/types.d.ts +47 -0
  128. package/dist/unified/src/unified/types.d.ts.map +1 -0
  129. package/dist/unified/src/unified/types.js +3 -0
  130. package/dist/unified/src/unified/types.js.map +1 -0
  131. package/dist/unified/src/unified/wallet-detector.d.ts +28 -0
  132. package/dist/unified/src/unified/wallet-detector.d.ts.map +1 -0
  133. package/dist/unified/src/unified/wallet-detector.js +63 -0
  134. package/dist/unified/src/unified/wallet-detector.js.map +1 -0
  135. package/dist/unified/src/utils/chain-config.d.ts +75 -0
  136. package/dist/unified/src/utils/chain-config.d.ts.map +1 -0
  137. package/dist/unified/src/utils/chain-config.js +203 -0
  138. package/dist/unified/src/utils/chain-config.js.map +1 -0
  139. package/dist/unified/src/utils/currency.d.ts +26 -0
  140. package/dist/unified/src/utils/currency.d.ts.map +1 -0
  141. package/dist/unified/src/utils/currency.js +36 -0
  142. package/dist/unified/src/utils/currency.js.map +1 -0
  143. package/dist/unified/src/utils/index.d.ts +4 -0
  144. package/dist/unified/src/utils/index.d.ts.map +1 -0
  145. package/dist/unified/src/utils/index.js +20 -0
  146. package/dist/unified/src/utils/index.js.map +1 -0
  147. package/dist/unified/src/utils/validation.d.ts +10 -0
  148. package/dist/unified/src/utils/validation.d.ts.map +1 -0
  149. package/dist/unified/src/utils/validation.js +102 -0
  150. package/dist/unified/src/utils/validation.js.map +1 -0
  151. package/dist/unified/typechain-types/Mailer.d.ts +698 -0
  152. package/dist/unified/typechain-types/Mailer.d.ts.map +1 -0
  153. package/dist/unified/typechain-types/Mailer.js +3 -0
  154. package/dist/unified/typechain-types/Mailer.js.map +1 -0
  155. package/dist/unified/typechain-types/MockUSDC.d.ts +118 -0
  156. package/dist/unified/typechain-types/MockUSDC.d.ts.map +1 -0
  157. package/dist/unified/typechain-types/MockUSDC.js +3 -0
  158. package/dist/unified/typechain-types/MockUSDC.js.map +1 -0
  159. package/dist/unified/typechain-types/common.d.ts +51 -0
  160. package/dist/unified/typechain-types/common.d.ts.map +1 -0
  161. package/dist/unified/typechain-types/common.js +3 -0
  162. package/dist/unified/typechain-types/common.js.map +1 -0
  163. package/dist/unified/typechain-types/factories/Mailer__factory.d.ts +875 -0
  164. package/dist/unified/typechain-types/factories/Mailer__factory.d.ts.map +1 -0
  165. package/dist/unified/typechain-types/factories/Mailer__factory.js +1125 -0
  166. package/dist/unified/typechain-types/factories/Mailer__factory.js.map +1 -0
  167. package/dist/unified/typechain-types/factories/MockUSDC__factory.d.ts +193 -0
  168. package/dist/unified/typechain-types/factories/MockUSDC__factory.d.ts.map +1 -0
  169. package/dist/unified/typechain-types/factories/MockUSDC__factory.js +263 -0
  170. package/dist/unified/typechain-types/factories/MockUSDC__factory.js.map +1 -0
  171. package/dist/unified/typechain-types/factories/index.d.ts +4 -0
  172. package/dist/unified/typechain-types/factories/index.d.ts.map +1 -0
  173. package/dist/unified/typechain-types/factories/index.js +45 -0
  174. package/dist/unified/typechain-types/factories/index.js.map +1 -0
  175. package/dist/unified/typechain-types/factories/interfaces/IERC20__factory.d.ts +80 -0
  176. package/dist/unified/typechain-types/factories/interfaces/IERC20__factory.d.ts.map +1 -0
  177. package/dist/unified/typechain-types/factories/interfaces/IERC20__factory.js +116 -0
  178. package/dist/unified/typechain-types/factories/interfaces/IERC20__factory.js.map +1 -0
  179. package/dist/unified/typechain-types/factories/interfaces/index.d.ts +2 -0
  180. package/dist/unified/typechain-types/factories/interfaces/index.d.ts.map +1 -0
  181. package/dist/unified/typechain-types/factories/interfaces/index.js +9 -0
  182. package/dist/unified/typechain-types/factories/interfaces/index.js.map +1 -0
  183. package/dist/unified/typechain-types/index.d.ts +10 -0
  184. package/dist/unified/typechain-types/index.d.ts.map +1 -0
  185. package/dist/unified/typechain-types/index.js +44 -0
  186. package/dist/unified/typechain-types/index.js.map +1 -0
  187. package/dist/unified/typechain-types/interfaces/IERC20.d.ts +70 -0
  188. package/dist/unified/typechain-types/interfaces/IERC20.d.ts.map +1 -0
  189. package/dist/unified/typechain-types/interfaces/IERC20.js +3 -0
  190. package/dist/unified/typechain-types/interfaces/IERC20.js.map +1 -0
  191. package/dist/unified/typechain-types/interfaces/index.d.ts +2 -0
  192. package/dist/unified/typechain-types/interfaces/index.d.ts.map +1 -0
  193. package/dist/unified/typechain-types/interfaces/index.js +3 -0
  194. package/dist/unified/typechain-types/interfaces/index.js.map +1 -0
  195. package/dist/unified-esm/src/evm/index.d.ts +4 -0
  196. package/dist/unified-esm/src/evm/index.d.ts.map +1 -0
  197. package/dist/unified-esm/src/evm/index.js +5 -0
  198. package/dist/unified-esm/src/evm/index.js.map +1 -0
  199. package/dist/unified-esm/src/evm/mailer-client.d.ts +966 -0
  200. package/dist/unified-esm/src/evm/mailer-client.d.ts.map +1 -0
  201. package/dist/unified-esm/src/evm/mailer-client.js +615 -0
  202. package/dist/unified-esm/src/evm/mailer-client.js.map +1 -0
  203. package/dist/unified-esm/src/react/context/MailerProvider.d.ts +102 -0
  204. package/dist/unified-esm/src/react/context/MailerProvider.d.ts.map +1 -0
  205. package/dist/unified-esm/src/react/context/MailerProvider.js +120 -0
  206. package/dist/unified-esm/src/react/context/MailerProvider.js.map +1 -0
  207. package/dist/unified-esm/src/react/hooks/useMailerMutations.d.ts +301 -0
  208. package/dist/unified-esm/src/react/hooks/useMailerMutations.d.ts.map +1 -0
  209. package/dist/unified-esm/src/react/hooks/useMailerMutations.js +400 -0
  210. package/dist/unified-esm/src/react/hooks/useMailerMutations.js.map +1 -0
  211. package/dist/unified-esm/src/react/hooks/useMailerQueries.d.ts +130 -0
  212. package/dist/unified-esm/src/react/hooks/useMailerQueries.d.ts.map +1 -0
  213. package/dist/unified-esm/src/react/hooks/useMailerQueries.js +186 -0
  214. package/dist/unified-esm/src/react/hooks/useMailerQueries.js.map +1 -0
  215. package/dist/unified-esm/src/react/index.d.ts +37 -0
  216. package/dist/unified-esm/src/react/index.d.ts.map +1 -0
  217. package/dist/unified-esm/src/react/index.js +39 -0
  218. package/dist/unified-esm/src/react/index.js.map +1 -0
  219. package/dist/unified-esm/src/solana/index.d.ts +3 -0
  220. package/dist/unified-esm/src/solana/index.d.ts.map +1 -0
  221. package/dist/unified-esm/src/solana/index.js +3 -0
  222. package/dist/unified-esm/src/solana/index.js.map +1 -0
  223. package/dist/unified-esm/src/solana/mailer-client.d.ts +282 -0
  224. package/dist/unified-esm/src/solana/mailer-client.d.ts.map +1 -0
  225. package/dist/unified-esm/src/solana/mailer-client.js +985 -0
  226. package/dist/unified-esm/src/solana/mailer-client.js.map +1 -0
  227. package/dist/unified-esm/src/solana/types.d.ts +28 -0
  228. package/dist/unified-esm/src/solana/types.d.ts.map +1 -0
  229. package/dist/unified-esm/src/solana/types.js +16 -0
  230. package/dist/unified-esm/src/solana/types.js.map +1 -0
  231. package/dist/unified-esm/src/unified/index.d.ts +4 -0
  232. package/dist/unified-esm/src/unified/index.d.ts.map +1 -0
  233. package/dist/unified-esm/src/unified/index.js +4 -0
  234. package/dist/unified-esm/src/unified/index.js.map +1 -0
  235. package/dist/unified-esm/src/unified/onchain-mailer-client.d.ts +173 -0
  236. package/dist/unified-esm/src/unified/onchain-mailer-client.d.ts.map +1 -0
  237. package/dist/unified-esm/src/unified/onchain-mailer-client.js +1011 -0
  238. package/dist/unified-esm/src/unified/onchain-mailer-client.js.map +1 -0
  239. package/dist/unified-esm/src/unified/types.d.ts +47 -0
  240. package/dist/unified-esm/src/unified/types.d.ts.map +1 -0
  241. package/dist/unified-esm/src/unified/types.js +2 -0
  242. package/dist/unified-esm/src/unified/types.js.map +1 -0
  243. package/dist/unified-esm/src/unified/wallet-detector.d.ts +28 -0
  244. package/dist/unified-esm/src/unified/wallet-detector.d.ts.map +1 -0
  245. package/dist/unified-esm/src/unified/wallet-detector.js +59 -0
  246. package/dist/unified-esm/src/unified/wallet-detector.js.map +1 -0
  247. package/dist/unified-esm/src/utils/chain-config.d.ts +75 -0
  248. package/dist/unified-esm/src/utils/chain-config.d.ts.map +1 -0
  249. package/dist/unified-esm/src/utils/chain-config.js +199 -0
  250. package/dist/unified-esm/src/utils/chain-config.js.map +1 -0
  251. package/dist/unified-esm/src/utils/currency.d.ts +26 -0
  252. package/dist/unified-esm/src/utils/currency.d.ts.map +1 -0
  253. package/dist/unified-esm/src/utils/currency.js +31 -0
  254. package/dist/unified-esm/src/utils/currency.js.map +1 -0
  255. package/dist/unified-esm/src/utils/index.d.ts +4 -0
  256. package/dist/unified-esm/src/utils/index.d.ts.map +1 -0
  257. package/dist/unified-esm/src/utils/index.js +4 -0
  258. package/dist/unified-esm/src/utils/index.js.map +1 -0
  259. package/dist/unified-esm/src/utils/validation.d.ts +10 -0
  260. package/dist/unified-esm/src/utils/validation.d.ts.map +1 -0
  261. package/dist/unified-esm/src/utils/validation.js +96 -0
  262. package/dist/unified-esm/src/utils/validation.js.map +1 -0
  263. package/dist/unified-esm/typechain-types/Mailer.d.ts +698 -0
  264. package/dist/unified-esm/typechain-types/Mailer.d.ts.map +1 -0
  265. package/dist/unified-esm/typechain-types/Mailer.js +2 -0
  266. package/dist/unified-esm/typechain-types/Mailer.js.map +1 -0
  267. package/dist/unified-esm/typechain-types/MockUSDC.d.ts +118 -0
  268. package/dist/unified-esm/typechain-types/MockUSDC.d.ts.map +1 -0
  269. package/dist/unified-esm/typechain-types/MockUSDC.js +2 -0
  270. package/dist/unified-esm/typechain-types/MockUSDC.js.map +1 -0
  271. package/dist/unified-esm/typechain-types/common.d.ts +51 -0
  272. package/dist/unified-esm/typechain-types/common.d.ts.map +1 -0
  273. package/dist/unified-esm/typechain-types/common.js +2 -0
  274. package/dist/unified-esm/typechain-types/common.js.map +1 -0
  275. package/dist/unified-esm/typechain-types/factories/Mailer__factory.d.ts +875 -0
  276. package/dist/unified-esm/typechain-types/factories/Mailer__factory.d.ts.map +1 -0
  277. package/dist/unified-esm/typechain-types/factories/Mailer__factory.js +1121 -0
  278. package/dist/unified-esm/typechain-types/factories/Mailer__factory.js.map +1 -0
  279. package/dist/unified-esm/typechain-types/factories/MockUSDC__factory.d.ts +193 -0
  280. package/dist/unified-esm/typechain-types/factories/MockUSDC__factory.d.ts.map +1 -0
  281. package/dist/unified-esm/typechain-types/factories/MockUSDC__factory.js +259 -0
  282. package/dist/unified-esm/typechain-types/factories/MockUSDC__factory.js.map +1 -0
  283. package/dist/unified-esm/typechain-types/factories/index.d.ts +4 -0
  284. package/dist/unified-esm/typechain-types/factories/index.d.ts.map +1 -0
  285. package/dist/unified-esm/typechain-types/factories/index.js +7 -0
  286. package/dist/unified-esm/typechain-types/factories/index.js.map +1 -0
  287. package/dist/unified-esm/typechain-types/factories/interfaces/IERC20__factory.d.ts +80 -0
  288. package/dist/unified-esm/typechain-types/factories/interfaces/IERC20__factory.d.ts.map +1 -0
  289. package/dist/unified-esm/typechain-types/factories/interfaces/IERC20__factory.js +112 -0
  290. package/dist/unified-esm/typechain-types/factories/interfaces/IERC20__factory.js.map +1 -0
  291. package/dist/unified-esm/typechain-types/factories/interfaces/index.d.ts +2 -0
  292. package/dist/unified-esm/typechain-types/factories/interfaces/index.d.ts.map +1 -0
  293. package/dist/unified-esm/typechain-types/factories/interfaces/index.js +5 -0
  294. package/dist/unified-esm/typechain-types/factories/interfaces/index.js.map +1 -0
  295. package/dist/unified-esm/typechain-types/index.d.ts +10 -0
  296. package/dist/unified-esm/typechain-types/index.d.ts.map +1 -0
  297. package/dist/unified-esm/typechain-types/index.js +5 -0
  298. package/dist/unified-esm/typechain-types/index.js.map +1 -0
  299. package/dist/unified-esm/typechain-types/interfaces/IERC20.d.ts +70 -0
  300. package/dist/unified-esm/typechain-types/interfaces/IERC20.d.ts.map +1 -0
  301. package/dist/unified-esm/typechain-types/interfaces/IERC20.js +2 -0
  302. package/dist/unified-esm/typechain-types/interfaces/IERC20.js.map +1 -0
  303. package/dist/unified-esm/typechain-types/interfaces/index.d.ts +2 -0
  304. package/dist/unified-esm/typechain-types/interfaces/index.d.ts.map +1 -0
  305. package/dist/unified-esm/typechain-types/interfaces/index.js +2 -0
  306. package/dist/unified-esm/typechain-types/interfaces/index.js.map +1 -0
  307. package/package.json +250 -0
  308. package/programs/mailer/Cargo.toml +29 -0
  309. package/programs/mailer/src/lib.rs +2034 -0
  310. package/programs/mailer/tests/integration_tests.rs +1236 -0
  311. package/typechain-types/Mailer.ts +1393 -0
  312. package/typechain-types/MockUSDC.ts +236 -0
  313. package/typechain-types/common.ts +131 -0
  314. package/typechain-types/factories/Mailer__factory.ts +1157 -0
  315. package/typechain-types/factories/MockUSDC__factory.ts +284 -0
  316. package/typechain-types/factories/index.ts +6 -0
  317. package/typechain-types/factories/interfaces/IERC20__factory.ts +115 -0
  318. package/typechain-types/factories/interfaces/index.ts +4 -0
  319. package/typechain-types/hardhat.d.ts +99 -0
  320. package/typechain-types/index.ts +12 -0
  321. package/typechain-types/interfaces/IERC20.ts +148 -0
  322. package/typechain-types/interfaces/index.ts +4 -0
@@ -0,0 +1,2034 @@
1
+ //! # Native Solana Mailer Program
2
+ //!
3
+ //! A native Solana program for decentralized messaging with delegation management,
4
+ //! USDC fees and revenue sharing - no Anchor dependencies.
5
+ //!
6
+ //! ## Key Features
7
+ //!
8
+ //! - **Delegation Management**: Delegate mail handling with rejection capability
9
+ //! - **Priority Messages**: Full fee (0.1 USDC) with 90% revenue share back to sender
10
+ //! - **Standard Messages**: 10% fee only (0.01 USDC) with no revenue share
11
+ //! - **Revenue Claims**: 60-day claim period for priority message revenue shares
12
+ //!
13
+ //! ## Program Architecture
14
+ //!
15
+ //! The program uses Program Derived Addresses (PDAs) for:
16
+ //! - Mailer state: `[b"mailer"]`
17
+ //! - Recipient claims: `[b"claim", recipient.key()]`
18
+ //! - Delegations: `[b"delegation", delegator.key()]`
19
+ //!
20
+ //! ## Fee Structure
21
+ //!
22
+ //! - Send Fee: 0.1 USDC (100,000 with 6 decimals)
23
+ //! - Delegation Fee: 10 USDC (10,000,000 with 6 decimals)
24
+ //! - Priority: Sender pays full fee, gets 90% back as claimable
25
+ //! - Standard: Sender pays 10% fee only
26
+ //! - Owner gets 10% of all fees
27
+
28
+ use borsh::{BorshDeserialize, BorshSerialize};
29
+ use solana_program::{
30
+ account_info::{next_account_info, AccountInfo},
31
+ clock::Clock,
32
+ entrypoint::ProgramResult,
33
+ msg,
34
+ program::{invoke, invoke_signed},
35
+ program_error::ProgramError,
36
+ program_pack::Pack,
37
+ pubkey::Pubkey,
38
+ rent::Rent,
39
+ system_instruction,
40
+ sysvar::Sysvar,
41
+ };
42
+ use spl_token::state::Account as TokenAccount;
43
+ use thiserror::Error;
44
+
45
+ // Program ID for the Native Mailer program
46
+ solana_program::declare_id!("9FLkBDGpZBcR8LMsQ7MwwV6X9P4TDFgN3DeRh5qYyHJF");
47
+
48
+ /// Base sending fee in USDC (with 6 decimals): 0.1 USDC
49
+ const SEND_FEE: u64 = 100_000;
50
+
51
+ /// Delegation fee in USDC (with 6 decimals): 10 USDC
52
+ const DELEGATION_FEE: u64 = 10_000_000;
53
+
54
+ /// Claim period for revenue shares: 60 days in seconds
55
+ const CLAIM_PERIOD: i64 = 60 * 24 * 60 * 60;
56
+
57
+ #[cfg(not(feature = "no-entrypoint"))]
58
+ solana_program::entrypoint!(process_instruction);
59
+
60
+ /// Program state account
61
+ #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
62
+ pub struct MailerState {
63
+ pub owner: Pubkey,
64
+ pub usdc_mint: Pubkey,
65
+ pub send_fee: u64,
66
+ pub delegation_fee: u64,
67
+ pub owner_claimable: u64,
68
+ pub paused: bool,
69
+ pub bump: u8,
70
+ }
71
+
72
+ impl MailerState {
73
+ pub const LEN: usize = 32 + 32 + 8 + 8 + 8 + 1 + 1; // 90 bytes
74
+ }
75
+
76
+ /// Recipient claim account
77
+ #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
78
+ pub struct RecipientClaim {
79
+ pub recipient: Pubkey,
80
+ pub amount: u64,
81
+ pub timestamp: i64,
82
+ pub bump: u8,
83
+ }
84
+
85
+ impl RecipientClaim {
86
+ pub const LEN: usize = 32 + 8 + 8 + 1; // 49 bytes
87
+ }
88
+
89
+ /// Delegation account
90
+ #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
91
+ pub struct Delegation {
92
+ pub delegator: Pubkey,
93
+ pub delegate: Option<Pubkey>,
94
+ pub bump: u8,
95
+ }
96
+
97
+ impl Delegation {
98
+ pub const LEN: usize = 32 + 1 + 32 + 1; // 66 bytes (max with Some(Pubkey))
99
+ }
100
+
101
+ /// Fee discount account for custom fee percentages
102
+ /// Stores discount (0-100) instead of percentage for cleaner default behavior
103
+ /// 0 = no discount (100% fee), 100 = full discount (0% fee, free)
104
+ #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
105
+ pub struct FeeDiscount {
106
+ pub account: Pubkey,
107
+ pub discount: u8, // 0-100: 0 = no discount (full fee), 100 = full discount (free)
108
+ pub bump: u8,
109
+ }
110
+
111
+ impl FeeDiscount {
112
+ pub const LEN: usize = 32 + 1 + 1; // 34 bytes
113
+ }
114
+
115
+ /// Instructions
116
+ #[derive(BorshSerialize, BorshDeserialize, Debug)]
117
+ pub enum MailerInstruction {
118
+ /// Initialize the program
119
+ /// Accounts:
120
+ /// 0. `[writable, signer]` Owner account
121
+ /// 1. `[writable]` Mailer state account (PDA)
122
+ /// 2. `[]` System program
123
+ Initialize { usdc_mint: Pubkey },
124
+
125
+ /// Send message with optional revenue sharing
126
+ /// Accounts:
127
+ /// 0. `[signer]` Sender
128
+ /// 1. `[writable]` Recipient claim account (PDA)
129
+ /// 2. `[]` Mailer state account (PDA)
130
+ /// 3. `[writable]` Sender USDC account
131
+ /// 4. `[writable]` Mailer USDC account
132
+ /// 5. `[]` Token program
133
+ /// 6. `[]` System program
134
+ Send {
135
+ to: Pubkey,
136
+ subject: String,
137
+ _body: String,
138
+ revenue_share_to_receiver: bool,
139
+ resolve_sender_to_name: bool,
140
+ },
141
+
142
+ /// Send prepared message with optional revenue sharing (references off-chain content via mailId)
143
+ /// Accounts:
144
+ /// 0. `[signer]` Sender
145
+ /// 1. `[writable]` Recipient claim account (PDA)
146
+ /// 2. `[]` Mailer state account (PDA)
147
+ /// 3. `[writable]` Sender USDC account
148
+ /// 4. `[writable]` Mailer USDC account
149
+ /// 5. `[]` Token program
150
+ /// 6. `[]` System program
151
+ SendPrepared {
152
+ to: Pubkey,
153
+ mail_id: String,
154
+ revenue_share_to_receiver: bool,
155
+ resolve_sender_to_name: bool,
156
+ },
157
+
158
+ /// Send message to email address (no wallet address known)
159
+ /// Charges only 10% owner fee since recipient wallet is unknown
160
+ /// Accounts:
161
+ /// 0. `[signer]` Sender
162
+ /// 1. `[]` Mailer state account (PDA)
163
+ /// 2. `[writable]` Sender USDC account
164
+ /// 3. `[writable]` Mailer USDC account
165
+ /// 4. `[]` Token program
166
+ SendToEmail {
167
+ to_email: String,
168
+ subject: String,
169
+ _body: String,
170
+ },
171
+
172
+ /// Send prepared message to email address (no wallet address known)
173
+ /// Charges only 10% owner fee since recipient wallet is unknown
174
+ /// Accounts:
175
+ /// 0. `[signer]` Sender
176
+ /// 1. `[]` Mailer state account (PDA)
177
+ /// 2. `[writable]` Sender USDC account
178
+ /// 3. `[writable]` Mailer USDC account
179
+ /// 4. `[]` Token program
180
+ SendPreparedToEmail { to_email: String, mail_id: String },
181
+
182
+ /// Send message through webhook (referenced by webhookId)
183
+ /// Accounts:
184
+ /// 0. `[signer]` Sender
185
+ /// 1. `[writable]` Recipient claim account (PDA)
186
+ /// 2. `[]` Mailer state account (PDA)
187
+ /// 3. `[writable]` Sender USDC account
188
+ /// 4. `[writable]` Mailer USDC account
189
+ /// 5. `[]` Token program
190
+ /// 6. `[]` System program
191
+ SendThroughWebhook {
192
+ to: Pubkey,
193
+ webhook_id: String,
194
+ revenue_share_to_receiver: bool,
195
+ resolve_sender_to_name: bool,
196
+ },
197
+
198
+ /// Claim recipient share
199
+ /// Accounts:
200
+ /// 0. `[signer]` Recipient
201
+ /// 1. `[writable]` Recipient claim account (PDA)
202
+ /// 2. `[]` Mailer state account (PDA)
203
+ /// 3. `[writable]` Recipient USDC account
204
+ /// 4. `[writable]` Mailer USDC account
205
+ /// 5. `[]` Token program
206
+ ClaimRecipientShare,
207
+
208
+ /// Claim owner share
209
+ /// Accounts:
210
+ /// 0. `[signer]` Owner
211
+ /// 1. `[writable]` Mailer state account (PDA)
212
+ /// 2. `[writable]` Owner USDC account
213
+ /// 3. `[writable]` Mailer USDC account
214
+ /// 4. `[]` Token program
215
+ ClaimOwnerShare,
216
+
217
+ /// Set send fee (owner only)
218
+ /// Accounts:
219
+ /// 0. `[signer]` Owner
220
+ /// 1. `[writable]` Mailer state account (PDA)
221
+ SetFee { new_fee: u64 },
222
+
223
+ /// Delegate to another address
224
+ /// Accounts:
225
+ /// 0. `[signer]` Delegator
226
+ /// 1. `[writable]` Delegation account (PDA)
227
+ /// 2. `[]` Mailer state account (PDA)
228
+ /// 3. `[writable]` Delegator USDC account
229
+ /// 4. `[writable]` Mailer USDC account
230
+ /// 5. `[]` Token program
231
+ /// 6. `[]` System program
232
+ DelegateTo { delegate: Option<Pubkey> },
233
+
234
+ /// Reject delegation
235
+ /// Accounts:
236
+ /// 0. `[signer]` Rejector
237
+ /// 1. `[writable]` Delegation account (PDA)
238
+ /// 2. `[]` Mailer state account (PDA)
239
+ RejectDelegation,
240
+
241
+ /// Set delegation fee (owner only)
242
+ /// Accounts:
243
+ /// 0. `[signer]` Owner
244
+ /// 1. `[writable]` Mailer state account (PDA)
245
+ SetDelegationFee { new_fee: u64 },
246
+
247
+ /// Set custom fee percentage for a specific address (owner only)
248
+ /// Accounts:
249
+ /// 0. `[signer]` Owner
250
+ /// 1. `[]` Mailer state account (PDA)
251
+ /// 2. `[writable]` Fee discount account (PDA)
252
+ /// 3. `[]` Account to set custom fee for
253
+ /// 4. `[signer]` Payer for account creation
254
+ /// 5. `[]` System program
255
+ SetCustomFeePercentage {
256
+ account: Pubkey,
257
+ percentage: u8, // 0-100: 0 = free, 100 = full fee
258
+ },
259
+
260
+ /// Clear custom fee percentage for a specific address (owner only)
261
+ /// Accounts:
262
+ /// 0. `[signer]` Owner
263
+ /// 1. `[]` Mailer state account (PDA)
264
+ /// 2. `[writable]` Fee discount account (PDA)
265
+ ClearCustomFeePercentage { account: Pubkey },
266
+
267
+ /// Pause the contract (owner only)
268
+ /// Accounts:
269
+ /// 0. `[signer]` Owner
270
+ /// 1. `[writable]` Mailer state account (PDA)
271
+ /// 2. `[writable]` Owner USDC account
272
+ /// 3. `[writable]` Mailer USDC account
273
+ /// 4. `[]` Token program
274
+ Pause,
275
+
276
+ /// Unpause the contract (owner only)
277
+ /// Accounts:
278
+ /// 0. `[signer]` Owner
279
+ /// 1. `[writable]` Mailer state account (PDA)
280
+ Unpause,
281
+
282
+ /// Distribute claimable funds (when paused)
283
+ /// Accounts:
284
+ /// 0. `[signer]` Anyone can call
285
+ /// 1. `[]` Mailer state account (PDA)
286
+ /// 2. `[writable]` Recipient claim account (PDA)
287
+ /// 3. `[writable]` Recipient USDC account
288
+ /// 4. `[writable]` Mailer USDC account
289
+ /// 5. `[]` Token program
290
+ DistributeClaimableFunds { recipient: Pubkey },
291
+
292
+ /// Claim expired recipient shares (owner only)
293
+ /// Accounts:
294
+ /// 0. `[signer]` Owner
295
+ /// 1. `[writable]` Mailer state account (PDA)
296
+ /// 2. `[writable]` Recipient claim account (PDA)
297
+ ClaimExpiredShares { recipient: Pubkey },
298
+
299
+ /// Emergency unpause without fund distribution (owner only)
300
+ /// Accounts:
301
+ /// 0. `[signer]` Owner
302
+ /// 1. `[writable]` Mailer state account (PDA)
303
+ EmergencyUnpause,
304
+ }
305
+
306
+ /// Custom program errors
307
+ #[derive(Error, Debug, Copy, Clone)]
308
+ pub enum MailerError {
309
+ #[error("Only the owner can perform this action")]
310
+ OnlyOwner,
311
+ #[error("No claimable amount available")]
312
+ NoClaimableAmount,
313
+ #[error("Claim period has expired")]
314
+ ClaimPeriodExpired,
315
+ #[error("Claim period has not expired yet")]
316
+ ClaimPeriodNotExpired,
317
+ #[error("Invalid recipient")]
318
+ InvalidRecipient,
319
+ #[error("No delegation to reject")]
320
+ NoDelegationToReject,
321
+ #[error("Invalid delegator")]
322
+ InvalidDelegator,
323
+ #[error("Account already initialized")]
324
+ AlreadyInitialized,
325
+ #[error("Account not initialized")]
326
+ NotInitialized,
327
+ #[error("Invalid PDA")]
328
+ InvalidPDA,
329
+ #[error("Invalid account owner")]
330
+ InvalidAccountOwner,
331
+ #[error("Invalid token mint")]
332
+ InvalidMint,
333
+ #[error("Invalid token program")]
334
+ InvalidTokenProgram,
335
+ #[error("Contract is paused")]
336
+ ContractPaused,
337
+ #[error("Contract is not paused")]
338
+ ContractNotPaused,
339
+ #[error("Invalid percentage (must be 0-100)")]
340
+ InvalidPercentage,
341
+ }
342
+
343
+ impl From<MailerError> for ProgramError {
344
+ fn from(e: MailerError) -> Self {
345
+ ProgramError::Custom(e as u32)
346
+ }
347
+ }
348
+
349
+ /// Main instruction processor
350
+ pub fn process_instruction(
351
+ program_id: &Pubkey,
352
+ accounts: &[AccountInfo],
353
+ instruction_data: &[u8],
354
+ ) -> ProgramResult {
355
+ let instruction = MailerInstruction::try_from_slice(instruction_data)?;
356
+
357
+ match instruction {
358
+ MailerInstruction::Initialize { usdc_mint } => {
359
+ process_initialize(program_id, accounts, usdc_mint)
360
+ }
361
+ MailerInstruction::Send {
362
+ to,
363
+ subject,
364
+ _body,
365
+ revenue_share_to_receiver,
366
+ resolve_sender_to_name,
367
+ } => process_send(
368
+ program_id,
369
+ accounts,
370
+ to,
371
+ subject,
372
+ _body,
373
+ revenue_share_to_receiver,
374
+ resolve_sender_to_name,
375
+ ),
376
+ MailerInstruction::SendPrepared {
377
+ to,
378
+ mail_id,
379
+ revenue_share_to_receiver,
380
+ resolve_sender_to_name,
381
+ } => process_send_prepared(
382
+ program_id,
383
+ accounts,
384
+ to,
385
+ mail_id,
386
+ revenue_share_to_receiver,
387
+ resolve_sender_to_name,
388
+ ),
389
+ MailerInstruction::SendToEmail {
390
+ to_email,
391
+ subject,
392
+ _body,
393
+ } => process_send_to_email(program_id, accounts, to_email, subject, _body),
394
+ MailerInstruction::SendPreparedToEmail { to_email, mail_id } => {
395
+ process_send_prepared_to_email(program_id, accounts, to_email, mail_id)
396
+ }
397
+ MailerInstruction::SendThroughWebhook {
398
+ to,
399
+ webhook_id,
400
+ revenue_share_to_receiver,
401
+ resolve_sender_to_name,
402
+ } => process_send_through_webhook(
403
+ program_id,
404
+ accounts,
405
+ to,
406
+ webhook_id,
407
+ revenue_share_to_receiver,
408
+ resolve_sender_to_name,
409
+ ),
410
+ MailerInstruction::ClaimRecipientShare => {
411
+ process_claim_recipient_share(program_id, accounts)
412
+ }
413
+ MailerInstruction::ClaimOwnerShare => process_claim_owner_share(program_id, accounts),
414
+ MailerInstruction::SetFee { new_fee } => process_set_fee(program_id, accounts, new_fee),
415
+ MailerInstruction::DelegateTo { delegate } => {
416
+ process_delegate_to(program_id, accounts, delegate)
417
+ }
418
+ MailerInstruction::RejectDelegation => process_reject_delegation(program_id, accounts),
419
+ MailerInstruction::SetDelegationFee { new_fee } => {
420
+ process_set_delegation_fee(program_id, accounts, new_fee)
421
+ }
422
+ MailerInstruction::SetCustomFeePercentage {
423
+ account,
424
+ percentage,
425
+ } => process_set_custom_fee_percentage(program_id, accounts, account, percentage),
426
+ MailerInstruction::ClearCustomFeePercentage { account } => {
427
+ process_clear_custom_fee_percentage(program_id, accounts, account)
428
+ }
429
+ MailerInstruction::Pause => process_pause(program_id, accounts),
430
+ MailerInstruction::Unpause => process_unpause(program_id, accounts),
431
+ MailerInstruction::DistributeClaimableFunds { recipient } => {
432
+ process_distribute_claimable_funds(program_id, accounts, recipient)
433
+ }
434
+ MailerInstruction::ClaimExpiredShares { recipient } => {
435
+ process_claim_expired_shares(program_id, accounts, recipient)
436
+ }
437
+ MailerInstruction::EmergencyUnpause => process_emergency_unpause(program_id, accounts),
438
+ }
439
+ }
440
+
441
+ /// Initialize the program
442
+ fn process_initialize(
443
+ program_id: &Pubkey,
444
+ accounts: &[AccountInfo],
445
+ usdc_mint: Pubkey,
446
+ ) -> ProgramResult {
447
+ let account_iter = &mut accounts.iter();
448
+ let owner = next_account_info(account_iter)?;
449
+ let mailer_account = next_account_info(account_iter)?;
450
+ let system_program = next_account_info(account_iter)?;
451
+
452
+ if !owner.is_signer {
453
+ return Err(ProgramError::MissingRequiredSignature);
454
+ }
455
+
456
+ // Verify mailer account PDA
457
+ let (mailer_pda, bump) = Pubkey::find_program_address(&[b"mailer"], program_id);
458
+ if mailer_account.key != &mailer_pda {
459
+ return Err(MailerError::InvalidPDA.into());
460
+ }
461
+
462
+ // Create mailer account
463
+ let rent = Rent::get()?;
464
+ let space = 8 + MailerState::LEN; // 8 bytes for discriminator
465
+ let lamports = rent.minimum_balance(space);
466
+
467
+ invoke_signed(
468
+ &system_instruction::create_account(
469
+ owner.key,
470
+ mailer_account.key,
471
+ lamports,
472
+ space as u64,
473
+ program_id,
474
+ ),
475
+ &[
476
+ owner.clone(),
477
+ mailer_account.clone(),
478
+ system_program.clone(),
479
+ ],
480
+ &[&[b"mailer", &[bump]]],
481
+ )?;
482
+
483
+ // Initialize state
484
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
485
+ mailer_data[0..8].copy_from_slice(&hash_discriminator("account:MailerState").to_le_bytes());
486
+
487
+ let mailer_state = MailerState {
488
+ owner: *owner.key,
489
+ usdc_mint,
490
+ send_fee: SEND_FEE,
491
+ delegation_fee: DELEGATION_FEE,
492
+ owner_claimable: 0,
493
+ paused: false,
494
+ bump,
495
+ };
496
+
497
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
498
+
499
+ msg!("Mailer initialized with owner: {}", owner.key);
500
+ Ok(())
501
+ }
502
+
503
+ /// Send message with optional revenue sharing
504
+ fn process_send(
505
+ program_id: &Pubkey,
506
+ accounts: &[AccountInfo],
507
+ to: Pubkey,
508
+ subject: String,
509
+ _body: String,
510
+ revenue_share_to_receiver: bool,
511
+ _resolve_sender_to_name: bool,
512
+ ) -> ProgramResult {
513
+ let account_iter = &mut accounts.iter();
514
+ let sender = next_account_info(account_iter)?;
515
+ let recipient_claim = next_account_info(account_iter)?;
516
+ let mailer_account = next_account_info(account_iter)?;
517
+ let sender_usdc = next_account_info(account_iter)?;
518
+ let mailer_usdc = next_account_info(account_iter)?;
519
+ let token_program = next_account_info(account_iter)?;
520
+ let system_program = next_account_info(account_iter)?;
521
+
522
+ if !sender.is_signer {
523
+ return Err(ProgramError::MissingRequiredSignature);
524
+ }
525
+
526
+ // Load mailer state
527
+ let (mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
528
+ let mailer_data = mailer_account.try_borrow_data()?;
529
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
530
+ drop(mailer_data);
531
+
532
+ assert_token_program(token_program)?;
533
+ assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
534
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
535
+
536
+ // Check if contract is paused
537
+ if mailer_state.paused {
538
+ return Err(MailerError::ContractPaused.into());
539
+ }
540
+
541
+ // Calculate effective fee based on custom discount (if any)
542
+ let effective_fee =
543
+ calculate_fee_with_discount(program_id, sender.key, accounts, mailer_state.send_fee)?;
544
+
545
+ if revenue_share_to_receiver {
546
+ // Priority mode: full fee with revenue sharing
547
+
548
+ // Create or load recipient claim account
549
+ let (claim_pda, claim_bump) =
550
+ Pubkey::find_program_address(&[b"claim", to.as_ref()], program_id);
551
+
552
+ if recipient_claim.key != &claim_pda {
553
+ return Err(MailerError::InvalidPDA.into());
554
+ }
555
+
556
+ // Create claim account if needed
557
+ if recipient_claim.lamports() == 0 {
558
+ let rent = Rent::get()?;
559
+ let space = 8 + RecipientClaim::LEN;
560
+ let lamports = rent.minimum_balance(space);
561
+
562
+ invoke_signed(
563
+ &system_instruction::create_account(
564
+ sender.key,
565
+ recipient_claim.key,
566
+ lamports,
567
+ space as u64,
568
+ program_id,
569
+ ),
570
+ &[
571
+ sender.clone(),
572
+ recipient_claim.clone(),
573
+ system_program.clone(),
574
+ ],
575
+ &[&[b"claim", to.as_ref(), &[claim_bump]]],
576
+ )?;
577
+
578
+ // Initialize claim account
579
+ let mut claim_data = recipient_claim.try_borrow_mut_data()?;
580
+ claim_data[0..8]
581
+ .copy_from_slice(&hash_discriminator("account:RecipientClaim").to_le_bytes());
582
+
583
+ let claim_state = RecipientClaim {
584
+ recipient: to,
585
+ amount: 0,
586
+ timestamp: 0,
587
+ bump: claim_bump,
588
+ };
589
+
590
+ claim_state.serialize(&mut &mut claim_data[8..])?;
591
+ drop(claim_data);
592
+ }
593
+
594
+ // Transfer effective fee (may be discounted)
595
+ if effective_fee > 0 {
596
+ invoke(
597
+ &spl_token::instruction::transfer(
598
+ token_program.key,
599
+ sender_usdc.key,
600
+ mailer_usdc.key,
601
+ sender.key,
602
+ &[],
603
+ effective_fee,
604
+ )?,
605
+ &[
606
+ sender_usdc.clone(),
607
+ mailer_usdc.clone(),
608
+ sender.clone(),
609
+ token_program.clone(),
610
+ ],
611
+ )?;
612
+
613
+ // Record revenue shares (only if fee > 0)
614
+ record_shares(recipient_claim, mailer_account, to, effective_fee)?;
615
+ }
616
+
617
+ msg!("Priority mail sent from {} to {}: {} (revenue share enabled, resolve sender: {}, effective fee: {})", sender.key, to, subject, _resolve_sender_to_name, effective_fee);
618
+ } else {
619
+ // Standard mode: 10% fee only, no revenue sharing
620
+ let owner_fee = (effective_fee * 10) / 100; // 10% of effective fee
621
+
622
+ // Transfer only owner fee (10%)
623
+ if owner_fee > 0 {
624
+ invoke(
625
+ &spl_token::instruction::transfer(
626
+ token_program.key,
627
+ sender_usdc.key,
628
+ mailer_usdc.key,
629
+ sender.key,
630
+ &[],
631
+ owner_fee,
632
+ )?,
633
+ &[
634
+ sender_usdc.clone(),
635
+ mailer_usdc.clone(),
636
+ sender.clone(),
637
+ token_program.clone(),
638
+ ],
639
+ )?;
640
+ }
641
+
642
+ // Update owner claimable
643
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
644
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
645
+ mailer_state.owner_claimable += owner_fee;
646
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
647
+
648
+ msg!(
649
+ "Standard mail sent from {} to {}: {} (resolve sender: {}, effective fee: {})",
650
+ sender.key,
651
+ to,
652
+ subject,
653
+ _resolve_sender_to_name,
654
+ effective_fee
655
+ );
656
+ }
657
+
658
+ Ok(())
659
+ }
660
+
661
+ /// Send prepared message with optional revenue sharing (references off-chain content via mailId)
662
+ fn process_send_prepared(
663
+ program_id: &Pubkey,
664
+ accounts: &[AccountInfo],
665
+ to: Pubkey,
666
+ mail_id: String,
667
+ revenue_share_to_receiver: bool,
668
+ _resolve_sender_to_name: bool,
669
+ ) -> ProgramResult {
670
+ let account_iter = &mut accounts.iter();
671
+ let sender = next_account_info(account_iter)?;
672
+ let recipient_claim = next_account_info(account_iter)?;
673
+ let mailer_account = next_account_info(account_iter)?;
674
+ let sender_usdc = next_account_info(account_iter)?;
675
+ let mailer_usdc = next_account_info(account_iter)?;
676
+ let token_program = next_account_info(account_iter)?;
677
+ let system_program = next_account_info(account_iter)?;
678
+
679
+ if !sender.is_signer {
680
+ return Err(ProgramError::MissingRequiredSignature);
681
+ }
682
+
683
+ // Load mailer state
684
+ let (mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
685
+ let mailer_data = mailer_account.try_borrow_data()?;
686
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
687
+ drop(mailer_data);
688
+
689
+ assert_token_program(token_program)?;
690
+ assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
691
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
692
+
693
+ // Check if contract is paused
694
+ if mailer_state.paused {
695
+ return Err(MailerError::ContractPaused.into());
696
+ }
697
+
698
+ // Calculate effective fee based on custom discount (if any)
699
+ let effective_fee =
700
+ calculate_fee_with_discount(program_id, sender.key, accounts, mailer_state.send_fee)?;
701
+
702
+ if revenue_share_to_receiver {
703
+ // Priority mode: full fee with revenue sharing
704
+
705
+ // Create or load recipient claim account
706
+ let (claim_pda, claim_bump) =
707
+ Pubkey::find_program_address(&[b"claim", to.as_ref()], program_id);
708
+
709
+ if recipient_claim.key != &claim_pda {
710
+ return Err(MailerError::InvalidPDA.into());
711
+ }
712
+
713
+ // Create claim account if needed
714
+ if recipient_claim.lamports() == 0 {
715
+ let rent = Rent::get()?;
716
+ let space = 8 + RecipientClaim::LEN;
717
+ let lamports = rent.minimum_balance(space);
718
+
719
+ invoke_signed(
720
+ &system_instruction::create_account(
721
+ sender.key,
722
+ recipient_claim.key,
723
+ lamports,
724
+ space as u64,
725
+ program_id,
726
+ ),
727
+ &[
728
+ sender.clone(),
729
+ recipient_claim.clone(),
730
+ system_program.clone(),
731
+ ],
732
+ &[&[b"claim", to.as_ref(), &[claim_bump]]],
733
+ )?;
734
+
735
+ // Initialize claim account
736
+ let mut claim_data = recipient_claim.try_borrow_mut_data()?;
737
+ claim_data[0..8]
738
+ .copy_from_slice(&hash_discriminator("account:RecipientClaim").to_le_bytes());
739
+
740
+ let claim_state = RecipientClaim {
741
+ recipient: to,
742
+ amount: 0,
743
+ timestamp: 0,
744
+ bump: claim_bump,
745
+ };
746
+
747
+ claim_state.serialize(&mut &mut claim_data[8..])?;
748
+ drop(claim_data);
749
+ }
750
+
751
+ // Transfer effective fee (may be discounted)
752
+ if effective_fee > 0 {
753
+ invoke(
754
+ &spl_token::instruction::transfer(
755
+ token_program.key,
756
+ sender_usdc.key,
757
+ mailer_usdc.key,
758
+ sender.key,
759
+ &[],
760
+ effective_fee,
761
+ )?,
762
+ &[
763
+ sender_usdc.clone(),
764
+ mailer_usdc.clone(),
765
+ sender.clone(),
766
+ token_program.clone(),
767
+ ],
768
+ )?;
769
+
770
+ // Record revenue shares (only if fee > 0)
771
+ record_shares(recipient_claim, mailer_account, to, effective_fee)?;
772
+ }
773
+
774
+ msg!("Priority prepared mail sent from {} to {} (mailId: {}, revenue share enabled, resolve sender: {}, effective fee: {})", sender.key, to, mail_id, _resolve_sender_to_name, effective_fee);
775
+ } else {
776
+ // Standard mode: 10% fee only, no revenue sharing
777
+ let owner_fee = (effective_fee * 10) / 100; // 10% of effective fee
778
+
779
+ // Transfer only owner fee (10%)
780
+ if owner_fee > 0 {
781
+ invoke(
782
+ &spl_token::instruction::transfer(
783
+ token_program.key,
784
+ sender_usdc.key,
785
+ mailer_usdc.key,
786
+ sender.key,
787
+ &[],
788
+ owner_fee,
789
+ )?,
790
+ &[
791
+ sender_usdc.clone(),
792
+ mailer_usdc.clone(),
793
+ sender.clone(),
794
+ token_program.clone(),
795
+ ],
796
+ )?;
797
+ }
798
+
799
+ // Update owner claimable
800
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
801
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
802
+ mailer_state.owner_claimable += owner_fee;
803
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
804
+
805
+ msg!(
806
+ "Standard prepared mail sent from {} to {} (mailId: {}, resolve sender: {}, effective fee: {})",
807
+ sender.key,
808
+ to,
809
+ mail_id,
810
+ _resolve_sender_to_name,
811
+ effective_fee
812
+ );
813
+ }
814
+
815
+ Ok(())
816
+ }
817
+
818
+ /// Process send to email address (no wallet known, only owner fee)
819
+ fn process_send_to_email(
820
+ _program_id: &Pubkey,
821
+ accounts: &[AccountInfo],
822
+ to_email: String,
823
+ subject: String,
824
+ _body: String,
825
+ ) -> ProgramResult {
826
+ let account_iter = &mut accounts.iter();
827
+ let sender = next_account_info(account_iter)?;
828
+ let mailer_account = next_account_info(account_iter)?;
829
+ let sender_usdc = next_account_info(account_iter)?;
830
+ let mailer_usdc = next_account_info(account_iter)?;
831
+ let token_program = next_account_info(account_iter)?;
832
+
833
+ if !sender.is_signer {
834
+ return Err(ProgramError::MissingRequiredSignature);
835
+ }
836
+
837
+ // Load mailer state
838
+ let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
839
+ let mailer_data = mailer_account.try_borrow_data()?;
840
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
841
+ drop(mailer_data);
842
+
843
+ assert_token_program(token_program)?;
844
+ assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
845
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
846
+
847
+ // Check if contract is paused
848
+ if mailer_state.paused {
849
+ return Err(MailerError::ContractPaused.into());
850
+ }
851
+
852
+ // Calculate effective fee based on custom discount (if any)
853
+ let effective_fee =
854
+ calculate_fee_with_discount(_program_id, sender.key, accounts, mailer_state.send_fee)?;
855
+
856
+ // Calculate 10% owner fee (no revenue share since no wallet address)
857
+ let owner_fee = (effective_fee * 10) / 100;
858
+
859
+ // Transfer fee from sender to mailer
860
+ if owner_fee > 0 {
861
+ let transfer_ix = spl_token::instruction::transfer(
862
+ token_program.key,
863
+ sender_usdc.key,
864
+ mailer_usdc.key,
865
+ sender.key,
866
+ &[],
867
+ owner_fee,
868
+ )?;
869
+
870
+ invoke(
871
+ &transfer_ix,
872
+ &[
873
+ sender_usdc.clone(),
874
+ mailer_usdc.clone(),
875
+ sender.clone(),
876
+ token_program.clone(),
877
+ ],
878
+ )?;
879
+ }
880
+
881
+ // Update owner claimable
882
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
883
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
884
+ mailer_state.owner_claimable += owner_fee;
885
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
886
+
887
+ msg!(
888
+ "Mail sent from {} to email {}: {} (effective fee: {})",
889
+ sender.key,
890
+ to_email,
891
+ subject,
892
+ effective_fee
893
+ );
894
+
895
+ Ok(())
896
+ }
897
+
898
+ /// Process send prepared to email address (no wallet known, only owner fee)
899
+ fn process_send_prepared_to_email(
900
+ _program_id: &Pubkey,
901
+ accounts: &[AccountInfo],
902
+ to_email: String,
903
+ mail_id: String,
904
+ ) -> ProgramResult {
905
+ let account_iter = &mut accounts.iter();
906
+ let sender = next_account_info(account_iter)?;
907
+ let mailer_account = next_account_info(account_iter)?;
908
+ let sender_usdc = next_account_info(account_iter)?;
909
+ let mailer_usdc = next_account_info(account_iter)?;
910
+ let token_program = next_account_info(account_iter)?;
911
+
912
+ if !sender.is_signer {
913
+ return Err(ProgramError::MissingRequiredSignature);
914
+ }
915
+
916
+ // Load mailer state
917
+ let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
918
+ let mailer_data = mailer_account.try_borrow_data()?;
919
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
920
+ drop(mailer_data);
921
+
922
+ assert_token_program(token_program)?;
923
+ assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
924
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
925
+
926
+ // Check if contract is paused
927
+ if mailer_state.paused {
928
+ return Err(MailerError::ContractPaused.into());
929
+ }
930
+
931
+ // Calculate effective fee based on custom discount (if any)
932
+ let effective_fee =
933
+ calculate_fee_with_discount(_program_id, sender.key, accounts, mailer_state.send_fee)?;
934
+
935
+ // Calculate 10% owner fee (no revenue share since no wallet address)
936
+ let owner_fee = (effective_fee * 10) / 100;
937
+
938
+ // Transfer fee from sender to mailer
939
+ if owner_fee > 0 {
940
+ let transfer_ix = spl_token::instruction::transfer(
941
+ token_program.key,
942
+ sender_usdc.key,
943
+ mailer_usdc.key,
944
+ sender.key,
945
+ &[],
946
+ owner_fee,
947
+ )?;
948
+
949
+ invoke(
950
+ &transfer_ix,
951
+ &[
952
+ sender_usdc.clone(),
953
+ mailer_usdc.clone(),
954
+ sender.clone(),
955
+ token_program.clone(),
956
+ ],
957
+ )?;
958
+ }
959
+
960
+ // Update owner claimable
961
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
962
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
963
+ mailer_state.owner_claimable += owner_fee;
964
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
965
+
966
+ msg!(
967
+ "Prepared mail sent from {} to email {} (mailId: {}, effective fee: {})",
968
+ sender.key,
969
+ to_email,
970
+ mail_id,
971
+ effective_fee
972
+ );
973
+
974
+ Ok(())
975
+ }
976
+
977
+ /// Send message through webhook (references webhook by webhookId)
978
+ fn process_send_through_webhook(
979
+ program_id: &Pubkey,
980
+ accounts: &[AccountInfo],
981
+ to: Pubkey,
982
+ webhook_id: String,
983
+ revenue_share_to_receiver: bool,
984
+ _resolve_sender_to_name: bool,
985
+ ) -> ProgramResult {
986
+ let account_iter = &mut accounts.iter();
987
+ let sender = next_account_info(account_iter)?;
988
+ let recipient_claim = next_account_info(account_iter)?;
989
+ let mailer_account = next_account_info(account_iter)?;
990
+ let sender_usdc = next_account_info(account_iter)?;
991
+ let mailer_usdc = next_account_info(account_iter)?;
992
+ let token_program = next_account_info(account_iter)?;
993
+ let system_program = next_account_info(account_iter)?;
994
+
995
+ if !sender.is_signer {
996
+ return Err(ProgramError::MissingRequiredSignature);
997
+ }
998
+
999
+ // Load mailer state
1000
+ let (mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
1001
+ let mailer_data = mailer_account.try_borrow_data()?;
1002
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1003
+ drop(mailer_data);
1004
+
1005
+ assert_token_program(token_program)?;
1006
+ assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
1007
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1008
+
1009
+ // Check if contract is paused
1010
+ if mailer_state.paused {
1011
+ return Err(MailerError::ContractPaused.into());
1012
+ }
1013
+
1014
+ // Calculate effective fee based on custom discount (if any)
1015
+ let effective_fee =
1016
+ calculate_fee_with_discount(program_id, sender.key, accounts, mailer_state.send_fee)?;
1017
+
1018
+ if revenue_share_to_receiver {
1019
+ // Priority mode: full fee with revenue sharing
1020
+
1021
+ // Create or load recipient claim account
1022
+ let (claim_pda, claim_bump) =
1023
+ Pubkey::find_program_address(&[b"claim", to.as_ref()], program_id);
1024
+
1025
+ if recipient_claim.key != &claim_pda {
1026
+ return Err(MailerError::InvalidPDA.into());
1027
+ }
1028
+
1029
+ // Create claim account if needed
1030
+ if recipient_claim.lamports() == 0 {
1031
+ let rent = Rent::get()?;
1032
+ let space = 8 + RecipientClaim::LEN;
1033
+ let lamports = rent.minimum_balance(space);
1034
+
1035
+ invoke_signed(
1036
+ &system_instruction::create_account(
1037
+ sender.key,
1038
+ recipient_claim.key,
1039
+ lamports,
1040
+ space as u64,
1041
+ program_id,
1042
+ ),
1043
+ &[
1044
+ sender.clone(),
1045
+ recipient_claim.clone(),
1046
+ system_program.clone(),
1047
+ ],
1048
+ &[&[b"claim", to.as_ref(), &[claim_bump]]],
1049
+ )?;
1050
+
1051
+ // Initialize claim account
1052
+ let mut claim_data = recipient_claim.try_borrow_mut_data()?;
1053
+ claim_data[0..8]
1054
+ .copy_from_slice(&hash_discriminator("account:RecipientClaim").to_le_bytes());
1055
+
1056
+ let claim_state = RecipientClaim {
1057
+ recipient: to,
1058
+ amount: 0,
1059
+ timestamp: 0,
1060
+ bump: claim_bump,
1061
+ };
1062
+
1063
+ claim_state.serialize(&mut &mut claim_data[8..])?;
1064
+ drop(claim_data);
1065
+ }
1066
+
1067
+ // Transfer effective fee (may be discounted)
1068
+ if effective_fee > 0 {
1069
+ invoke(
1070
+ &spl_token::instruction::transfer(
1071
+ token_program.key,
1072
+ sender_usdc.key,
1073
+ mailer_usdc.key,
1074
+ sender.key,
1075
+ &[],
1076
+ effective_fee,
1077
+ )?,
1078
+ &[
1079
+ sender_usdc.clone(),
1080
+ mailer_usdc.clone(),
1081
+ sender.clone(),
1082
+ token_program.clone(),
1083
+ ],
1084
+ )?;
1085
+
1086
+ // Record revenue shares (only if fee > 0)
1087
+ record_shares(recipient_claim, mailer_account, to, effective_fee)?;
1088
+ }
1089
+
1090
+ msg!("Webhook mail sent from {} to {} (webhookId: {}, revenue share enabled, resolve sender: {}, effective fee: {})", sender.key, to, webhook_id, _resolve_sender_to_name, effective_fee);
1091
+ } else {
1092
+ // Standard mode: 10% fee only, no revenue sharing
1093
+ let owner_fee = (effective_fee * 10) / 100; // 10% of effective fee
1094
+
1095
+ // Transfer only owner fee (10%)
1096
+ if owner_fee > 0 {
1097
+ invoke(
1098
+ &spl_token::instruction::transfer(
1099
+ token_program.key,
1100
+ sender_usdc.key,
1101
+ mailer_usdc.key,
1102
+ sender.key,
1103
+ &[],
1104
+ owner_fee,
1105
+ )?,
1106
+ &[
1107
+ sender_usdc.clone(),
1108
+ mailer_usdc.clone(),
1109
+ sender.clone(),
1110
+ token_program.clone(),
1111
+ ],
1112
+ )?;
1113
+ }
1114
+
1115
+ // Update owner claimable
1116
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1117
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1118
+ mailer_state.owner_claimable += owner_fee;
1119
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
1120
+
1121
+ msg!(
1122
+ "Webhook mail sent from {} to {} (webhookId: {}, resolve sender: {}, effective fee: {})",
1123
+ sender.key,
1124
+ to,
1125
+ webhook_id,
1126
+ _resolve_sender_to_name,
1127
+ effective_fee
1128
+ );
1129
+ }
1130
+
1131
+ Ok(())
1132
+ }
1133
+
1134
+ /// Process claim recipient share
1135
+ fn process_claim_recipient_share(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1136
+ let account_iter = &mut accounts.iter();
1137
+ let recipient = next_account_info(account_iter)?;
1138
+ let recipient_claim = next_account_info(account_iter)?;
1139
+ let mailer_account = next_account_info(account_iter)?;
1140
+ let recipient_usdc = next_account_info(account_iter)?;
1141
+ let mailer_usdc = next_account_info(account_iter)?;
1142
+ let token_program = next_account_info(account_iter)?;
1143
+
1144
+ if !recipient.is_signer {
1145
+ return Err(ProgramError::MissingRequiredSignature);
1146
+ }
1147
+
1148
+ let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
1149
+ let (claim_pda, _) =
1150
+ Pubkey::find_program_address(&[b"claim", recipient.key.as_ref()], _program_id);
1151
+ if recipient_claim.key != &claim_pda {
1152
+ return Err(MailerError::InvalidPDA.into());
1153
+ }
1154
+
1155
+ // Load claim state
1156
+ let mut claim_data = recipient_claim.try_borrow_mut_data()?;
1157
+ let mut claim_state: RecipientClaim = BorshDeserialize::deserialize(&mut &claim_data[8..])?;
1158
+
1159
+ if claim_state.recipient != *recipient.key {
1160
+ return Err(MailerError::InvalidRecipient.into());
1161
+ }
1162
+
1163
+ if claim_state.amount == 0 {
1164
+ return Err(MailerError::NoClaimableAmount.into());
1165
+ }
1166
+
1167
+ // Check if claim period has expired
1168
+ let current_time = Clock::get()?.unix_timestamp;
1169
+ if current_time > claim_state.timestamp + CLAIM_PERIOD {
1170
+ return Err(MailerError::ClaimPeriodExpired.into());
1171
+ }
1172
+
1173
+ let amount = claim_state.amount;
1174
+ claim_state.amount = 0;
1175
+ claim_state.timestamp = 0;
1176
+ claim_state.serialize(&mut &mut claim_data[8..])?;
1177
+
1178
+ // Load mailer state for PDA signing
1179
+ let mailer_data = mailer_account.try_borrow_data()?;
1180
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1181
+ drop(mailer_data);
1182
+
1183
+ assert_token_program(token_program)?;
1184
+ assert_token_account(recipient_usdc, recipient.key, &mailer_state.usdc_mint)?;
1185
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1186
+
1187
+ // Transfer USDC from mailer to recipient
1188
+ invoke_signed(
1189
+ &spl_token::instruction::transfer(
1190
+ token_program.key,
1191
+ mailer_usdc.key,
1192
+ recipient_usdc.key,
1193
+ mailer_account.key,
1194
+ &[],
1195
+ amount,
1196
+ )?,
1197
+ &[
1198
+ mailer_usdc.clone(),
1199
+ recipient_usdc.clone(),
1200
+ mailer_account.clone(),
1201
+ token_program.clone(),
1202
+ ],
1203
+ &[&[b"mailer", &[mailer_state.bump]]],
1204
+ )?;
1205
+
1206
+ msg!("Recipient {} claimed {}", recipient.key, amount);
1207
+ Ok(())
1208
+ }
1209
+
1210
+ /// Process claim owner share
1211
+ fn process_claim_owner_share(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1212
+ let account_iter = &mut accounts.iter();
1213
+ let owner = next_account_info(account_iter)?;
1214
+ let mailer_account = next_account_info(account_iter)?;
1215
+ let owner_usdc = next_account_info(account_iter)?;
1216
+ let mailer_usdc = next_account_info(account_iter)?;
1217
+ let token_program = next_account_info(account_iter)?;
1218
+
1219
+ if !owner.is_signer {
1220
+ return Err(ProgramError::MissingRequiredSignature);
1221
+ }
1222
+
1223
+ let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
1224
+
1225
+ // Load and update mailer state
1226
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1227
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1228
+
1229
+ if mailer_state.owner != *owner.key {
1230
+ return Err(MailerError::OnlyOwner.into());
1231
+ }
1232
+
1233
+ if mailer_state.owner_claimable == 0 {
1234
+ return Err(MailerError::NoClaimableAmount.into());
1235
+ }
1236
+
1237
+ let amount = mailer_state.owner_claimable;
1238
+ mailer_state.owner_claimable = 0;
1239
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
1240
+ drop(mailer_data);
1241
+
1242
+ assert_token_program(token_program)?;
1243
+ assert_token_account(owner_usdc, owner.key, &mailer_state.usdc_mint)?;
1244
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1245
+
1246
+ // Transfer USDC from mailer to owner
1247
+ invoke_signed(
1248
+ &spl_token::instruction::transfer(
1249
+ token_program.key,
1250
+ mailer_usdc.key,
1251
+ owner_usdc.key,
1252
+ mailer_account.key,
1253
+ &[],
1254
+ amount,
1255
+ )?,
1256
+ &[
1257
+ mailer_usdc.clone(),
1258
+ owner_usdc.clone(),
1259
+ mailer_account.clone(),
1260
+ token_program.clone(),
1261
+ ],
1262
+ &[&[b"mailer", &[mailer_state.bump]]],
1263
+ )?;
1264
+
1265
+ msg!("Owner {} claimed {}", owner.key, amount);
1266
+ Ok(())
1267
+ }
1268
+
1269
+ /// Set send fee (owner only)
1270
+ fn process_set_fee(_program_id: &Pubkey, accounts: &[AccountInfo], new_fee: u64) -> ProgramResult {
1271
+ let account_iter = &mut accounts.iter();
1272
+ let owner = next_account_info(account_iter)?;
1273
+ let mailer_account = next_account_info(account_iter)?;
1274
+
1275
+ if !owner.is_signer {
1276
+ return Err(ProgramError::MissingRequiredSignature);
1277
+ }
1278
+
1279
+ assert_mailer_account(_program_id, mailer_account)?;
1280
+
1281
+ // Load and update mailer state
1282
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1283
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1284
+
1285
+ if mailer_state.owner != *owner.key {
1286
+ return Err(MailerError::OnlyOwner.into());
1287
+ }
1288
+
1289
+ // Check if contract is paused
1290
+ if mailer_state.paused {
1291
+ return Err(MailerError::ContractPaused.into());
1292
+ }
1293
+
1294
+ let old_fee = mailer_state.send_fee;
1295
+ mailer_state.send_fee = new_fee;
1296
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
1297
+
1298
+ msg!("Fee updated from {} to {}", old_fee, new_fee);
1299
+ Ok(())
1300
+ }
1301
+
1302
+ /// Delegate to another address
1303
+ fn process_delegate_to(
1304
+ program_id: &Pubkey,
1305
+ accounts: &[AccountInfo],
1306
+ delegate: Option<Pubkey>,
1307
+ ) -> ProgramResult {
1308
+ let account_iter = &mut accounts.iter();
1309
+ let delegator = next_account_info(account_iter)?;
1310
+ let delegation_account = next_account_info(account_iter)?;
1311
+ let mailer_account = next_account_info(account_iter)?;
1312
+ let delegator_usdc = next_account_info(account_iter)?;
1313
+ let mailer_usdc = next_account_info(account_iter)?;
1314
+ let token_program = next_account_info(account_iter)?;
1315
+ let system_program = next_account_info(account_iter)?;
1316
+
1317
+ if !delegator.is_signer {
1318
+ return Err(ProgramError::MissingRequiredSignature);
1319
+ }
1320
+
1321
+ let (mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
1322
+
1323
+ // Load mailer state
1324
+ let mailer_data = mailer_account.try_borrow_data()?;
1325
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1326
+ drop(mailer_data);
1327
+
1328
+ assert_token_program(token_program)?;
1329
+ assert_token_account(delegator_usdc, delegator.key, &mailer_state.usdc_mint)?;
1330
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1331
+
1332
+ // Check if contract is paused
1333
+ if mailer_state.paused {
1334
+ return Err(MailerError::ContractPaused.into());
1335
+ }
1336
+
1337
+ // Verify delegation account PDA
1338
+ let (delegation_pda, delegation_bump) =
1339
+ Pubkey::find_program_address(&[b"delegation", delegator.key.as_ref()], program_id);
1340
+
1341
+ if delegation_account.key != &delegation_pda {
1342
+ return Err(MailerError::InvalidPDA.into());
1343
+ }
1344
+
1345
+ // Create delegation account if needed
1346
+ if delegation_account.lamports() == 0 {
1347
+ let rent = Rent::get()?;
1348
+ let space = 8 + Delegation::LEN;
1349
+ let lamports = rent.minimum_balance(space);
1350
+
1351
+ invoke_signed(
1352
+ &system_instruction::create_account(
1353
+ delegator.key,
1354
+ delegation_account.key,
1355
+ lamports,
1356
+ space as u64,
1357
+ program_id,
1358
+ ),
1359
+ &[
1360
+ delegator.clone(),
1361
+ delegation_account.clone(),
1362
+ system_program.clone(),
1363
+ ],
1364
+ &[&[b"delegation", delegator.key.as_ref(), &[delegation_bump]]],
1365
+ )?;
1366
+
1367
+ // Initialize delegation account
1368
+ let mut delegation_data = delegation_account.try_borrow_mut_data()?;
1369
+ delegation_data[0..8]
1370
+ .copy_from_slice(&hash_discriminator("account:Delegation").to_le_bytes());
1371
+
1372
+ let delegation_state = Delegation {
1373
+ delegator: *delegator.key,
1374
+ delegate: None,
1375
+ bump: delegation_bump,
1376
+ };
1377
+
1378
+ delegation_state.serialize(&mut &mut delegation_data[8..])?;
1379
+ drop(delegation_data);
1380
+ }
1381
+
1382
+ // If setting delegation (not clearing), charge fee
1383
+ if let Some(delegate_key) = delegate {
1384
+ if delegate_key != Pubkey::default() {
1385
+ invoke(
1386
+ &spl_token::instruction::transfer(
1387
+ token_program.key,
1388
+ delegator_usdc.key,
1389
+ mailer_usdc.key,
1390
+ delegator.key,
1391
+ &[],
1392
+ mailer_state.delegation_fee,
1393
+ )?,
1394
+ &[
1395
+ delegator_usdc.clone(),
1396
+ mailer_usdc.clone(),
1397
+ delegator.clone(),
1398
+ token_program.clone(),
1399
+ ],
1400
+ )?;
1401
+ }
1402
+ }
1403
+
1404
+ // Update delegation
1405
+ let mut delegation_data = delegation_account.try_borrow_mut_data()?;
1406
+ let mut delegation_state: Delegation =
1407
+ BorshDeserialize::deserialize(&mut &delegation_data[8..])?;
1408
+ delegation_state.delegate = delegate;
1409
+ delegation_state.serialize(&mut &mut delegation_data[8..])?;
1410
+
1411
+ msg!("Delegation set from {} to {:?}", delegator.key, delegate);
1412
+ Ok(())
1413
+ }
1414
+
1415
+ /// Reject delegation
1416
+ fn process_reject_delegation(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1417
+ let account_iter = &mut accounts.iter();
1418
+ let rejector = next_account_info(account_iter)?;
1419
+ let delegation_account = next_account_info(account_iter)?;
1420
+ let mailer_account = next_account_info(account_iter)?;
1421
+
1422
+ if !rejector.is_signer {
1423
+ return Err(ProgramError::MissingRequiredSignature);
1424
+ }
1425
+
1426
+ // Verify mailer state PDA and ensure contract is not paused
1427
+ let (_mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
1428
+
1429
+ let mailer_data = mailer_account.try_borrow_data()?;
1430
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1431
+ drop(mailer_data);
1432
+
1433
+ if mailer_state.paused {
1434
+ return Err(MailerError::ContractPaused.into());
1435
+ }
1436
+
1437
+ // Load and update delegation state
1438
+ let mut delegation_data = delegation_account.try_borrow_mut_data()?;
1439
+ let mut delegation_state: Delegation =
1440
+ BorshDeserialize::deserialize(&mut &delegation_data[8..])?;
1441
+
1442
+ // Verify the rejector is the current delegate
1443
+ if delegation_state.delegate != Some(*rejector.key) {
1444
+ return Err(MailerError::NoDelegationToReject.into());
1445
+ }
1446
+
1447
+ delegation_state.delegate = None;
1448
+ delegation_state.serialize(&mut &mut delegation_data[8..])?;
1449
+
1450
+ msg!("Delegation rejected by {}", rejector.key);
1451
+ Ok(())
1452
+ }
1453
+
1454
+ /// Set delegation fee (owner only)
1455
+ fn process_set_delegation_fee(
1456
+ _program_id: &Pubkey,
1457
+ accounts: &[AccountInfo],
1458
+ new_fee: u64,
1459
+ ) -> ProgramResult {
1460
+ let account_iter = &mut accounts.iter();
1461
+ let owner = next_account_info(account_iter)?;
1462
+ let mailer_account = next_account_info(account_iter)?;
1463
+
1464
+ if !owner.is_signer {
1465
+ return Err(ProgramError::MissingRequiredSignature);
1466
+ }
1467
+
1468
+ assert_mailer_account(_program_id, mailer_account)?;
1469
+
1470
+ // Load and update mailer state
1471
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1472
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1473
+
1474
+ if mailer_state.owner != *owner.key {
1475
+ return Err(MailerError::OnlyOwner.into());
1476
+ }
1477
+
1478
+ // Check if contract is paused
1479
+ if mailer_state.paused {
1480
+ return Err(MailerError::ContractPaused.into());
1481
+ }
1482
+
1483
+ let old_fee = mailer_state.delegation_fee;
1484
+ mailer_state.delegation_fee = new_fee;
1485
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
1486
+
1487
+ msg!("Delegation fee updated from {} to {}", old_fee, new_fee);
1488
+ Ok(())
1489
+ }
1490
+
1491
+ /// Set custom fee percentage for a specific address (owner only)
1492
+ fn process_set_custom_fee_percentage(
1493
+ program_id: &Pubkey,
1494
+ accounts: &[AccountInfo],
1495
+ account: Pubkey,
1496
+ percentage: u8,
1497
+ ) -> ProgramResult {
1498
+ let account_iter = &mut accounts.iter();
1499
+ let owner = next_account_info(account_iter)?;
1500
+ let mailer_account = next_account_info(account_iter)?;
1501
+ let fee_discount_account = next_account_info(account_iter)?;
1502
+ let _target_account = next_account_info(account_iter)?;
1503
+ let payer = next_account_info(account_iter)?;
1504
+ let system_program = next_account_info(account_iter)?;
1505
+
1506
+ if !owner.is_signer {
1507
+ return Err(ProgramError::MissingRequiredSignature);
1508
+ }
1509
+
1510
+ assert_mailer_account(program_id, mailer_account)?;
1511
+
1512
+ // Load mailer state and verify owner
1513
+ let mailer_data = mailer_account.try_borrow_data()?;
1514
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1515
+ drop(mailer_data);
1516
+
1517
+ if mailer_state.owner != *owner.key {
1518
+ return Err(MailerError::OnlyOwner.into());
1519
+ }
1520
+
1521
+ // Check if contract is paused
1522
+ if mailer_state.paused {
1523
+ return Err(MailerError::ContractPaused.into());
1524
+ }
1525
+
1526
+ // Validate percentage
1527
+ if percentage > 100 {
1528
+ return Err(MailerError::InvalidPercentage.into());
1529
+ }
1530
+
1531
+ // Verify fee discount account PDA
1532
+ let (discount_pda, bump) =
1533
+ Pubkey::find_program_address(&[b"discount", account.as_ref()], program_id);
1534
+
1535
+ if fee_discount_account.key != &discount_pda {
1536
+ return Err(MailerError::InvalidPDA.into());
1537
+ }
1538
+
1539
+ // Create or update fee discount account
1540
+ if fee_discount_account.lamports() == 0 {
1541
+ let rent = Rent::get()?;
1542
+ let space = 8 + FeeDiscount::LEN;
1543
+ let lamports = rent.minimum_balance(space);
1544
+
1545
+ invoke_signed(
1546
+ &system_instruction::create_account(
1547
+ payer.key,
1548
+ fee_discount_account.key,
1549
+ lamports,
1550
+ space as u64,
1551
+ program_id,
1552
+ ),
1553
+ &[
1554
+ payer.clone(),
1555
+ fee_discount_account.clone(),
1556
+ system_program.clone(),
1557
+ ],
1558
+ &[&[b"discount", account.as_ref(), &[bump]]],
1559
+ )?;
1560
+
1561
+ // Initialize discount account
1562
+ let mut discount_data = fee_discount_account.try_borrow_mut_data()?;
1563
+ discount_data[0..8]
1564
+ .copy_from_slice(&hash_discriminator("account:FeeDiscount").to_le_bytes());
1565
+
1566
+ let fee_discount = FeeDiscount {
1567
+ account,
1568
+ discount: 100 - percentage, // Store as discount: 0% fee = 100 discount, 100% fee = 0 discount
1569
+ bump,
1570
+ };
1571
+
1572
+ fee_discount.serialize(&mut &mut discount_data[8..])?;
1573
+ } else {
1574
+ // Update existing discount account
1575
+ let mut discount_data = fee_discount_account.try_borrow_mut_data()?;
1576
+ let mut fee_discount: FeeDiscount =
1577
+ BorshDeserialize::deserialize(&mut &discount_data[8..])?;
1578
+ fee_discount.discount = 100 - percentage; // Store as discount
1579
+ fee_discount.serialize(&mut &mut discount_data[8..])?;
1580
+ }
1581
+
1582
+ msg!("Custom fee percentage set for {}: {}%", account, percentage);
1583
+ Ok(())
1584
+ }
1585
+
1586
+ /// Clear custom fee percentage for a specific address (owner only)
1587
+ fn process_clear_custom_fee_percentage(
1588
+ program_id: &Pubkey,
1589
+ accounts: &[AccountInfo],
1590
+ account: Pubkey,
1591
+ ) -> ProgramResult {
1592
+ let account_iter = &mut accounts.iter();
1593
+ let owner = next_account_info(account_iter)?;
1594
+ let mailer_account = next_account_info(account_iter)?;
1595
+ let fee_discount_account = next_account_info(account_iter)?;
1596
+
1597
+ if !owner.is_signer {
1598
+ return Err(ProgramError::MissingRequiredSignature);
1599
+ }
1600
+
1601
+ assert_mailer_account(program_id, mailer_account)?;
1602
+
1603
+ // Load mailer state and verify owner
1604
+ let mailer_data = mailer_account.try_borrow_data()?;
1605
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1606
+ drop(mailer_data);
1607
+
1608
+ if mailer_state.owner != *owner.key {
1609
+ return Err(MailerError::OnlyOwner.into());
1610
+ }
1611
+
1612
+ // Check if contract is paused
1613
+ if mailer_state.paused {
1614
+ return Err(MailerError::ContractPaused.into());
1615
+ }
1616
+
1617
+ // Verify fee discount account PDA
1618
+ let (discount_pda, _) =
1619
+ Pubkey::find_program_address(&[b"discount", account.as_ref()], program_id);
1620
+
1621
+ if fee_discount_account.key != &discount_pda {
1622
+ return Err(MailerError::InvalidPDA.into());
1623
+ }
1624
+
1625
+ // Clear by setting discount to 0 (no discount = 100% fee = default behavior)
1626
+ if fee_discount_account.lamports() > 0 {
1627
+ let mut discount_data = fee_discount_account.try_borrow_mut_data()?;
1628
+ let mut fee_discount: FeeDiscount =
1629
+ BorshDeserialize::deserialize(&mut &discount_data[8..])?;
1630
+ fee_discount.discount = 0; // 0 discount = 100% fee = default
1631
+ fee_discount.serialize(&mut &mut discount_data[8..])?;
1632
+ }
1633
+
1634
+ msg!(
1635
+ "Custom fee percentage cleared for {} (reset to 100%)",
1636
+ account
1637
+ );
1638
+ Ok(())
1639
+ }
1640
+
1641
+ fn assert_token_program(token_program: &AccountInfo) -> Result<(), ProgramError> {
1642
+ if token_program.key != &spl_token::id() {
1643
+ return Err(MailerError::InvalidTokenProgram.into());
1644
+ }
1645
+ Ok(())
1646
+ }
1647
+
1648
+ fn assert_token_account(
1649
+ token_account_info: &AccountInfo,
1650
+ expected_owner: &Pubkey,
1651
+ expected_mint: &Pubkey,
1652
+ ) -> Result<(), ProgramError> {
1653
+ let data = token_account_info.try_borrow_data()?;
1654
+ let token_account = TokenAccount::unpack(&data)?;
1655
+ drop(data);
1656
+
1657
+ if token_account.owner != *expected_owner {
1658
+ return Err(MailerError::InvalidAccountOwner.into());
1659
+ }
1660
+
1661
+ if token_account.mint != *expected_mint {
1662
+ return Err(MailerError::InvalidMint.into());
1663
+ }
1664
+
1665
+ Ok(())
1666
+ }
1667
+
1668
+ fn assert_mailer_account(
1669
+ program_id: &Pubkey,
1670
+ mailer_account: &AccountInfo,
1671
+ ) -> Result<(Pubkey, u8), ProgramError> {
1672
+ let (mailer_pda, bump) = Pubkey::find_program_address(&[b"mailer"], program_id);
1673
+ if mailer_account.key != &mailer_pda {
1674
+ return Err(MailerError::InvalidPDA.into());
1675
+ }
1676
+ Ok((mailer_pda, bump))
1677
+ }
1678
+
1679
+ /// Record revenue shares for priority messages
1680
+ fn record_shares(
1681
+ recipient_claim: &AccountInfo,
1682
+ mailer_account: &AccountInfo,
1683
+ recipient: Pubkey,
1684
+ total_amount: u64,
1685
+ ) -> ProgramResult {
1686
+ let owner_amount = total_amount / 10; // 10% of total_amount
1687
+ let recipient_amount = total_amount - owner_amount;
1688
+
1689
+ // Update recipient's claimable amount and refresh the timestamp to extend the 60-day window
1690
+ let mut claim_data = recipient_claim.try_borrow_mut_data()?;
1691
+ let mut claim_state: RecipientClaim = BorshDeserialize::deserialize(&mut &claim_data[8..])?;
1692
+
1693
+ claim_state.recipient = recipient;
1694
+ claim_state.amount += recipient_amount;
1695
+ claim_state.timestamp = Clock::get()?.unix_timestamp;
1696
+ claim_state.serialize(&mut &mut claim_data[8..])?;
1697
+ drop(claim_data);
1698
+
1699
+ // Update owner's claimable amount
1700
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1701
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1702
+ mailer_state.owner_claimable += owner_amount;
1703
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
1704
+
1705
+ msg!(
1706
+ "Shares recorded: recipient {}, owner {}",
1707
+ recipient_amount,
1708
+ owner_amount
1709
+ );
1710
+ Ok(())
1711
+ }
1712
+
1713
+ /// Calculate the effective fee for an account based on custom discount
1714
+ /// If no discount account exists, returns full base_fee (default behavior)
1715
+ /// Otherwise applies discount: fee = base_fee * (100 - discount) / 100
1716
+ fn calculate_fee_with_discount(
1717
+ program_id: &Pubkey,
1718
+ account: &Pubkey,
1719
+ accounts: &[AccountInfo],
1720
+ base_fee: u64,
1721
+ ) -> Result<u64, ProgramError> {
1722
+ // Try to find fee discount account
1723
+ let (discount_pda, _) =
1724
+ Pubkey::find_program_address(&[b"discount", account.as_ref()], program_id);
1725
+
1726
+ // Check if any account in the accounts slice matches the discount PDA
1727
+ let discount_account = accounts.iter().find(|acc| acc.key == &discount_pda);
1728
+
1729
+ if let Some(discount_acc) = discount_account {
1730
+ // Account exists and has lamports - load the discount
1731
+ if discount_acc.lamports() > 0 {
1732
+ let discount_data = discount_acc.try_borrow_data()?;
1733
+ if discount_data.len() >= 8 + FeeDiscount::LEN {
1734
+ let fee_discount: FeeDiscount =
1735
+ BorshDeserialize::deserialize(&mut &discount_data[8..])?;
1736
+ let discount = fee_discount.discount as u64;
1737
+
1738
+ // Apply discount: fee = base_fee * (100 - discount) / 100
1739
+ // Examples: discount=0 → 100% fee, discount=50 → 50% fee, discount=100 → 0% fee (free)
1740
+ let effective_fee = (base_fee * (100 - discount)) / 100;
1741
+ return Ok(effective_fee);
1742
+ }
1743
+ }
1744
+ }
1745
+
1746
+ // No discount account or uninitialized - use full fee (default behavior)
1747
+ Ok(base_fee)
1748
+ }
1749
+
1750
+ /// Pause the contract and distribute owner claimable funds
1751
+ fn process_pause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1752
+ let account_iter = &mut accounts.iter();
1753
+ let owner = next_account_info(account_iter)?;
1754
+ let mailer_account = next_account_info(account_iter)?;
1755
+ let owner_usdc = next_account_info(account_iter)?;
1756
+ let mailer_usdc = next_account_info(account_iter)?;
1757
+ let token_program = next_account_info(account_iter)?;
1758
+
1759
+ if !owner.is_signer {
1760
+ return Err(ProgramError::MissingRequiredSignature);
1761
+ }
1762
+
1763
+ let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
1764
+
1765
+ // Load and update mailer state
1766
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1767
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1768
+
1769
+ // Verify owner
1770
+ if mailer_state.owner != *owner.key {
1771
+ return Err(MailerError::OnlyOwner.into());
1772
+ }
1773
+
1774
+ // Check if already paused
1775
+ if mailer_state.paused {
1776
+ return Err(MailerError::ContractPaused.into());
1777
+ }
1778
+
1779
+ // Set paused state
1780
+ mailer_state.paused = true;
1781
+
1782
+ assert_token_program(token_program)?;
1783
+
1784
+ // Distribute owner claimable funds if any
1785
+ if mailer_state.owner_claimable > 0 {
1786
+ let amount = mailer_state.owner_claimable;
1787
+ mailer_state.owner_claimable = 0;
1788
+
1789
+ assert_token_account(owner_usdc, owner.key, &mailer_state.usdc_mint)?;
1790
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1791
+
1792
+ // Transfer USDC from mailer to owner
1793
+ invoke_signed(
1794
+ &spl_token::instruction::transfer(
1795
+ token_program.key,
1796
+ mailer_usdc.key,
1797
+ owner_usdc.key,
1798
+ &mailer_pda,
1799
+ &[],
1800
+ amount,
1801
+ )?,
1802
+ &[
1803
+ mailer_usdc.clone(),
1804
+ owner_usdc.clone(),
1805
+ mailer_account.clone(),
1806
+ token_program.clone(),
1807
+ ],
1808
+ &[&[b"mailer", &[mailer_state.bump]]],
1809
+ )?;
1810
+
1811
+ msg!("Distributed owner funds during pause: {}", amount);
1812
+ }
1813
+
1814
+ // Save updated state
1815
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
1816
+
1817
+ msg!("Contract paused by owner: {}", owner.key);
1818
+ Ok(())
1819
+ }
1820
+
1821
+ /// Unpause the contract
1822
+ fn process_unpause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1823
+ let account_iter = &mut accounts.iter();
1824
+ let owner = next_account_info(account_iter)?;
1825
+ let mailer_account = next_account_info(account_iter)?;
1826
+
1827
+ if !owner.is_signer {
1828
+ return Err(ProgramError::MissingRequiredSignature);
1829
+ }
1830
+
1831
+ assert_mailer_account(_program_id, mailer_account)?;
1832
+
1833
+ // Load and update mailer state
1834
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1835
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1836
+
1837
+ // Verify owner
1838
+ if mailer_state.owner != *owner.key {
1839
+ return Err(MailerError::OnlyOwner.into());
1840
+ }
1841
+
1842
+ // Check if not paused
1843
+ if !mailer_state.paused {
1844
+ return Err(MailerError::ContractNotPaused.into());
1845
+ }
1846
+
1847
+ // Set unpaused state
1848
+ mailer_state.paused = false;
1849
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
1850
+
1851
+ msg!("Contract unpaused by owner: {}", owner.key);
1852
+ Ok(())
1853
+ }
1854
+
1855
+ /// Distribute claimable funds when contract is paused
1856
+ fn process_distribute_claimable_funds(
1857
+ _program_id: &Pubkey,
1858
+ accounts: &[AccountInfo],
1859
+ recipient: Pubkey,
1860
+ ) -> ProgramResult {
1861
+ let account_iter = &mut accounts.iter();
1862
+ let _caller = next_account_info(account_iter)?; // Anyone can call
1863
+ let mailer_account = next_account_info(account_iter)?;
1864
+ let recipient_claim_account = next_account_info(account_iter)?;
1865
+ let recipient_usdc = next_account_info(account_iter)?;
1866
+ let mailer_usdc = next_account_info(account_iter)?;
1867
+ let token_program = next_account_info(account_iter)?;
1868
+
1869
+ let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
1870
+
1871
+ // Load mailer state to check if paused
1872
+ let mailer_data = mailer_account.try_borrow_data()?;
1873
+ let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1874
+ drop(mailer_data);
1875
+
1876
+ // Check if contract is paused
1877
+ if !mailer_state.paused {
1878
+ return Err(MailerError::ContractNotPaused.into());
1879
+ }
1880
+
1881
+ // Verify recipient claim PDA
1882
+ let (claim_pda, _) = Pubkey::find_program_address(&[b"claim", recipient.as_ref()], _program_id);
1883
+ if recipient_claim_account.key != &claim_pda {
1884
+ return Err(MailerError::InvalidPDA.into());
1885
+ }
1886
+
1887
+ assert_token_program(token_program)?;
1888
+
1889
+ // Load and update recipient claim
1890
+ let mut claim_data = recipient_claim_account.try_borrow_mut_data()?;
1891
+ let mut claim_state: RecipientClaim = BorshDeserialize::deserialize(&mut &claim_data[8..])?;
1892
+
1893
+ if claim_state.amount == 0 {
1894
+ return Err(MailerError::NoClaimableAmount.into());
1895
+ }
1896
+
1897
+ let amount = claim_state.amount;
1898
+ claim_state.amount = 0;
1899
+ claim_state.timestamp = 0;
1900
+
1901
+ assert_token_account(recipient_usdc, &recipient, &mailer_state.usdc_mint)?;
1902
+ assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1903
+
1904
+ // Transfer USDC from mailer to recipient
1905
+ invoke_signed(
1906
+ &spl_token::instruction::transfer(
1907
+ token_program.key,
1908
+ mailer_usdc.key,
1909
+ recipient_usdc.key,
1910
+ &mailer_pda,
1911
+ &[],
1912
+ amount,
1913
+ )?,
1914
+ &[
1915
+ mailer_usdc.clone(),
1916
+ recipient_usdc.clone(),
1917
+ mailer_account.clone(),
1918
+ token_program.clone(),
1919
+ ],
1920
+ &[&[b"mailer", &[mailer_state.bump]]],
1921
+ )?;
1922
+
1923
+ claim_state.serialize(&mut &mut claim_data[8..])?;
1924
+
1925
+ msg!("Distributed claimable funds to {}: {}", recipient, amount);
1926
+ Ok(())
1927
+ }
1928
+
1929
+ /// Claim expired shares and move them under owner control (owner only)
1930
+ fn process_claim_expired_shares(
1931
+ program_id: &Pubkey,
1932
+ accounts: &[AccountInfo],
1933
+ recipient: Pubkey,
1934
+ ) -> ProgramResult {
1935
+ let account_iter = &mut accounts.iter();
1936
+ let owner = next_account_info(account_iter)?;
1937
+ let mailer_account = next_account_info(account_iter)?;
1938
+ let recipient_claim_account = next_account_info(account_iter)?;
1939
+
1940
+ if !owner.is_signer {
1941
+ return Err(ProgramError::MissingRequiredSignature);
1942
+ }
1943
+
1944
+ let (_mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
1945
+
1946
+ // Load and verify mailer state
1947
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1948
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1949
+
1950
+ if mailer_state.owner != *owner.key {
1951
+ return Err(MailerError::OnlyOwner.into());
1952
+ }
1953
+
1954
+ // Verify recipient claim PDA
1955
+ let (claim_pda, _) = Pubkey::find_program_address(&[b"claim", recipient.as_ref()], program_id);
1956
+ if recipient_claim_account.key != &claim_pda {
1957
+ return Err(MailerError::InvalidPDA.into());
1958
+ }
1959
+
1960
+ // Load and validate claim state
1961
+ let mut claim_data = recipient_claim_account.try_borrow_mut_data()?;
1962
+ let mut claim_state: RecipientClaim = BorshDeserialize::deserialize(&mut &claim_data[8..])?;
1963
+
1964
+ if claim_state.recipient != recipient {
1965
+ return Err(MailerError::InvalidRecipient.into());
1966
+ }
1967
+ if claim_state.amount == 0 {
1968
+ return Err(MailerError::NoClaimableAmount.into());
1969
+ }
1970
+
1971
+ let current_time = Clock::get()?.unix_timestamp;
1972
+ if current_time <= claim_state.timestamp + CLAIM_PERIOD {
1973
+ return Err(MailerError::ClaimPeriodNotExpired.into());
1974
+ }
1975
+
1976
+ let amount = claim_state.amount;
1977
+ claim_state.amount = 0;
1978
+ claim_state.timestamp = 0;
1979
+ claim_state.serialize(&mut &mut claim_data[8..])?;
1980
+ drop(claim_data);
1981
+
1982
+ mailer_state.owner_claimable += amount;
1983
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
1984
+
1985
+ msg!("Expired shares claimed for {}: {}", recipient, amount);
1986
+ Ok(())
1987
+ }
1988
+
1989
+ /// Emergency unpause without fund distribution (owner only)
1990
+ fn process_emergency_unpause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1991
+ let account_iter = &mut accounts.iter();
1992
+ let owner = next_account_info(account_iter)?;
1993
+ let mailer_account = next_account_info(account_iter)?;
1994
+
1995
+ if !owner.is_signer {
1996
+ return Err(ProgramError::MissingRequiredSignature);
1997
+ }
1998
+
1999
+ assert_mailer_account(_program_id, mailer_account)?;
2000
+
2001
+ // Load and update mailer state
2002
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
2003
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
2004
+
2005
+ // Verify owner
2006
+ if mailer_state.owner != *owner.key {
2007
+ return Err(MailerError::OnlyOwner.into());
2008
+ }
2009
+
2010
+ // Check if not paused
2011
+ if !mailer_state.paused {
2012
+ return Err(MailerError::ContractNotPaused.into());
2013
+ }
2014
+
2015
+ // Set unpaused state without fund distribution
2016
+ mailer_state.paused = false;
2017
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
2018
+
2019
+ msg!(
2020
+ "Contract emergency unpaused by owner: {} - funds can be claimed manually",
2021
+ owner.key
2022
+ );
2023
+ Ok(())
2024
+ }
2025
+
2026
+ /// Simple hash function for account discriminators
2027
+ fn hash_discriminator(name: &str) -> u64 {
2028
+ use std::collections::hash_map::DefaultHasher;
2029
+ use std::hash::{Hash, Hasher};
2030
+
2031
+ let mut hasher = DefaultHasher::new();
2032
+ name.hash(&mut hasher);
2033
+ hasher.finish()
2034
+ }