@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.
- package/README.md +353 -0
- package/artifacts/contracts/Mailer.sol/Mailer.d.ts +1146 -0
- package/artifacts/contracts/Mailer.sol/Mailer.dbg.json +4 -0
- package/artifacts/contracts/Mailer.sol/Mailer.json +1096 -0
- package/artifacts/contracts/Mailer.sol/artifacts.d.ts +21 -0
- package/artifacts/contracts/MockUSDC.sol/MockUSDC.d.ts +284 -0
- package/artifacts/contracts/MockUSDC.sol/MockUSDC.dbg.json +4 -0
- package/artifacts/contracts/MockUSDC.sol/MockUSDC.json +234 -0
- package/artifacts/contracts/MockUSDC.sol/artifacts.d.ts +21 -0
- package/artifacts/contracts/interfaces/IERC20.sol/IERC20.d.ts +157 -0
- package/artifacts/contracts/interfaces/IERC20.sol/IERC20.dbg.json +4 -0
- package/artifacts/contracts/interfaces/IERC20.sol/IERC20.json +107 -0
- package/artifacts/contracts/interfaces/IERC20.sol/artifacts.d.ts +21 -0
- package/dist/evm/src/evm/index.d.ts +4 -0
- package/dist/evm/src/evm/index.d.ts.map +1 -0
- package/dist/evm/src/evm/index.js +10 -0
- package/dist/evm/src/evm/index.js.map +1 -0
- package/dist/evm/src/evm/mailer-client.d.ts +966 -0
- package/dist/evm/src/evm/mailer-client.d.ts.map +1 -0
- package/dist/evm/src/evm/mailer-client.js +619 -0
- package/dist/evm/src/evm/mailer-client.js.map +1 -0
- package/dist/evm/typechain-types/Mailer.d.ts +698 -0
- package/dist/evm/typechain-types/Mailer.d.ts.map +1 -0
- package/dist/evm/typechain-types/Mailer.js +3 -0
- package/dist/evm/typechain-types/Mailer.js.map +1 -0
- package/dist/evm/typechain-types/MockUSDC.d.ts +118 -0
- package/dist/evm/typechain-types/MockUSDC.d.ts.map +1 -0
- package/dist/evm/typechain-types/MockUSDC.js +3 -0
- package/dist/evm/typechain-types/MockUSDC.js.map +1 -0
- package/dist/evm/typechain-types/common.d.ts +51 -0
- package/dist/evm/typechain-types/common.d.ts.map +1 -0
- package/dist/evm/typechain-types/common.js +3 -0
- package/dist/evm/typechain-types/common.js.map +1 -0
- package/dist/evm/typechain-types/factories/Mailer__factory.d.ts +875 -0
- package/dist/evm/typechain-types/factories/Mailer__factory.d.ts.map +1 -0
- package/dist/evm/typechain-types/factories/Mailer__factory.js +1125 -0
- package/dist/evm/typechain-types/factories/Mailer__factory.js.map +1 -0
- package/dist/evm/typechain-types/factories/MockUSDC__factory.d.ts +193 -0
- package/dist/evm/typechain-types/factories/MockUSDC__factory.d.ts.map +1 -0
- package/dist/evm/typechain-types/factories/MockUSDC__factory.js +263 -0
- package/dist/evm/typechain-types/factories/MockUSDC__factory.js.map +1 -0
- package/dist/evm/typechain-types/factories/index.d.ts +4 -0
- package/dist/evm/typechain-types/factories/index.d.ts.map +1 -0
- package/dist/evm/typechain-types/factories/index.js +45 -0
- package/dist/evm/typechain-types/factories/index.js.map +1 -0
- package/dist/evm/typechain-types/factories/interfaces/IERC20__factory.d.ts +80 -0
- package/dist/evm/typechain-types/factories/interfaces/IERC20__factory.d.ts.map +1 -0
- package/dist/evm/typechain-types/factories/interfaces/IERC20__factory.js +116 -0
- package/dist/evm/typechain-types/factories/interfaces/IERC20__factory.js.map +1 -0
- package/dist/evm/typechain-types/factories/interfaces/index.d.ts +2 -0
- package/dist/evm/typechain-types/factories/interfaces/index.d.ts.map +1 -0
- package/dist/evm/typechain-types/factories/interfaces/index.js +9 -0
- package/dist/evm/typechain-types/factories/interfaces/index.js.map +1 -0
- package/dist/evm/typechain-types/index.d.ts +10 -0
- package/dist/evm/typechain-types/index.d.ts.map +1 -0
- package/dist/evm/typechain-types/index.js +44 -0
- package/dist/evm/typechain-types/index.js.map +1 -0
- package/dist/evm/typechain-types/interfaces/IERC20.d.ts +70 -0
- package/dist/evm/typechain-types/interfaces/IERC20.d.ts.map +1 -0
- package/dist/evm/typechain-types/interfaces/IERC20.js +3 -0
- package/dist/evm/typechain-types/interfaces/IERC20.js.map +1 -0
- package/dist/evm/typechain-types/interfaces/index.d.ts +2 -0
- package/dist/evm/typechain-types/interfaces/index.d.ts.map +1 -0
- package/dist/evm/typechain-types/interfaces/index.js +3 -0
- package/dist/evm/typechain-types/interfaces/index.js.map +1 -0
- package/dist/solana/solana/index.d.ts +3 -0
- package/dist/solana/solana/index.d.ts.map +1 -0
- package/dist/solana/solana/index.js +21 -0
- package/dist/solana/solana/index.js.map +1 -0
- package/dist/solana/solana/mailer-client.d.ts +282 -0
- package/dist/solana/solana/mailer-client.d.ts.map +1 -0
- package/dist/solana/solana/mailer-client.js +989 -0
- package/dist/solana/solana/mailer-client.js.map +1 -0
- package/dist/solana/solana/types.d.ts +28 -0
- package/dist/solana/solana/types.d.ts.map +1 -0
- package/dist/solana/solana/types.js +23 -0
- package/dist/solana/solana/types.js.map +1 -0
- package/dist/solana/utils/currency.d.ts +26 -0
- package/dist/solana/utils/currency.d.ts.map +1 -0
- package/dist/solana/utils/currency.js +36 -0
- package/dist/solana/utils/currency.js.map +1 -0
- package/dist/unified/package.json +3 -0
- package/dist/unified/src/evm/index.d.ts +4 -0
- package/dist/unified/src/evm/index.d.ts.map +1 -0
- package/dist/unified/src/evm/index.js +10 -0
- package/dist/unified/src/evm/index.js.map +1 -0
- package/dist/unified/src/evm/mailer-client.d.ts +966 -0
- package/dist/unified/src/evm/mailer-client.d.ts.map +1 -0
- package/dist/unified/src/evm/mailer-client.js +619 -0
- package/dist/unified/src/evm/mailer-client.js.map +1 -0
- package/dist/unified/src/react/context/MailerProvider.d.ts +102 -0
- package/dist/unified/src/react/context/MailerProvider.d.ts.map +1 -0
- package/dist/unified/src/react/context/MailerProvider.js +160 -0
- package/dist/unified/src/react/context/MailerProvider.js.map +1 -0
- package/dist/unified/src/react/hooks/useMailerMutations.d.ts +301 -0
- package/dist/unified/src/react/hooks/useMailerMutations.d.ts.map +1 -0
- package/dist/unified/src/react/hooks/useMailerMutations.js +417 -0
- package/dist/unified/src/react/hooks/useMailerMutations.js.map +1 -0
- package/dist/unified/src/react/hooks/useMailerQueries.d.ts +130 -0
- package/dist/unified/src/react/hooks/useMailerQueries.d.ts.map +1 -0
- package/dist/unified/src/react/hooks/useMailerQueries.js +197 -0
- package/dist/unified/src/react/hooks/useMailerQueries.js.map +1 -0
- package/dist/unified/src/react/index.d.ts +37 -0
- package/dist/unified/src/react/index.d.ts.map +1 -0
- package/dist/unified/src/react/index.js +70 -0
- package/dist/unified/src/react/index.js.map +1 -0
- package/dist/unified/src/solana/index.d.ts +3 -0
- package/dist/unified/src/solana/index.d.ts.map +1 -0
- package/dist/unified/src/solana/index.js +21 -0
- package/dist/unified/src/solana/index.js.map +1 -0
- package/dist/unified/src/solana/mailer-client.d.ts +282 -0
- package/dist/unified/src/solana/mailer-client.d.ts.map +1 -0
- package/dist/unified/src/solana/mailer-client.js +989 -0
- package/dist/unified/src/solana/mailer-client.js.map +1 -0
- package/dist/unified/src/solana/types.d.ts +28 -0
- package/dist/unified/src/solana/types.d.ts.map +1 -0
- package/dist/unified/src/solana/types.js +23 -0
- package/dist/unified/src/solana/types.js.map +1 -0
- package/dist/unified/src/unified/index.d.ts +4 -0
- package/dist/unified/src/unified/index.d.ts.map +1 -0
- package/dist/unified/src/unified/index.js +9 -0
- package/dist/unified/src/unified/index.js.map +1 -0
- package/dist/unified/src/unified/onchain-mailer-client.d.ts +173 -0
- package/dist/unified/src/unified/onchain-mailer-client.d.ts.map +1 -0
- package/dist/unified/src/unified/onchain-mailer-client.js +1048 -0
- package/dist/unified/src/unified/onchain-mailer-client.js.map +1 -0
- package/dist/unified/src/unified/types.d.ts +47 -0
- package/dist/unified/src/unified/types.d.ts.map +1 -0
- package/dist/unified/src/unified/types.js +3 -0
- package/dist/unified/src/unified/types.js.map +1 -0
- package/dist/unified/src/unified/wallet-detector.d.ts +28 -0
- package/dist/unified/src/unified/wallet-detector.d.ts.map +1 -0
- package/dist/unified/src/unified/wallet-detector.js +63 -0
- package/dist/unified/src/unified/wallet-detector.js.map +1 -0
- package/dist/unified/src/utils/chain-config.d.ts +75 -0
- package/dist/unified/src/utils/chain-config.d.ts.map +1 -0
- package/dist/unified/src/utils/chain-config.js +203 -0
- package/dist/unified/src/utils/chain-config.js.map +1 -0
- package/dist/unified/src/utils/currency.d.ts +26 -0
- package/dist/unified/src/utils/currency.d.ts.map +1 -0
- package/dist/unified/src/utils/currency.js +36 -0
- package/dist/unified/src/utils/currency.js.map +1 -0
- package/dist/unified/src/utils/index.d.ts +4 -0
- package/dist/unified/src/utils/index.d.ts.map +1 -0
- package/dist/unified/src/utils/index.js +20 -0
- package/dist/unified/src/utils/index.js.map +1 -0
- package/dist/unified/src/utils/validation.d.ts +10 -0
- package/dist/unified/src/utils/validation.d.ts.map +1 -0
- package/dist/unified/src/utils/validation.js +102 -0
- package/dist/unified/src/utils/validation.js.map +1 -0
- package/dist/unified/typechain-types/Mailer.d.ts +698 -0
- package/dist/unified/typechain-types/Mailer.d.ts.map +1 -0
- package/dist/unified/typechain-types/Mailer.js +3 -0
- package/dist/unified/typechain-types/Mailer.js.map +1 -0
- package/dist/unified/typechain-types/MockUSDC.d.ts +118 -0
- package/dist/unified/typechain-types/MockUSDC.d.ts.map +1 -0
- package/dist/unified/typechain-types/MockUSDC.js +3 -0
- package/dist/unified/typechain-types/MockUSDC.js.map +1 -0
- package/dist/unified/typechain-types/common.d.ts +51 -0
- package/dist/unified/typechain-types/common.d.ts.map +1 -0
- package/dist/unified/typechain-types/common.js +3 -0
- package/dist/unified/typechain-types/common.js.map +1 -0
- package/dist/unified/typechain-types/factories/Mailer__factory.d.ts +875 -0
- package/dist/unified/typechain-types/factories/Mailer__factory.d.ts.map +1 -0
- package/dist/unified/typechain-types/factories/Mailer__factory.js +1125 -0
- package/dist/unified/typechain-types/factories/Mailer__factory.js.map +1 -0
- package/dist/unified/typechain-types/factories/MockUSDC__factory.d.ts +193 -0
- package/dist/unified/typechain-types/factories/MockUSDC__factory.d.ts.map +1 -0
- package/dist/unified/typechain-types/factories/MockUSDC__factory.js +263 -0
- package/dist/unified/typechain-types/factories/MockUSDC__factory.js.map +1 -0
- package/dist/unified/typechain-types/factories/index.d.ts +4 -0
- package/dist/unified/typechain-types/factories/index.d.ts.map +1 -0
- package/dist/unified/typechain-types/factories/index.js +45 -0
- package/dist/unified/typechain-types/factories/index.js.map +1 -0
- package/dist/unified/typechain-types/factories/interfaces/IERC20__factory.d.ts +80 -0
- package/dist/unified/typechain-types/factories/interfaces/IERC20__factory.d.ts.map +1 -0
- package/dist/unified/typechain-types/factories/interfaces/IERC20__factory.js +116 -0
- package/dist/unified/typechain-types/factories/interfaces/IERC20__factory.js.map +1 -0
- package/dist/unified/typechain-types/factories/interfaces/index.d.ts +2 -0
- package/dist/unified/typechain-types/factories/interfaces/index.d.ts.map +1 -0
- package/dist/unified/typechain-types/factories/interfaces/index.js +9 -0
- package/dist/unified/typechain-types/factories/interfaces/index.js.map +1 -0
- package/dist/unified/typechain-types/index.d.ts +10 -0
- package/dist/unified/typechain-types/index.d.ts.map +1 -0
- package/dist/unified/typechain-types/index.js +44 -0
- package/dist/unified/typechain-types/index.js.map +1 -0
- package/dist/unified/typechain-types/interfaces/IERC20.d.ts +70 -0
- package/dist/unified/typechain-types/interfaces/IERC20.d.ts.map +1 -0
- package/dist/unified/typechain-types/interfaces/IERC20.js +3 -0
- package/dist/unified/typechain-types/interfaces/IERC20.js.map +1 -0
- package/dist/unified/typechain-types/interfaces/index.d.ts +2 -0
- package/dist/unified/typechain-types/interfaces/index.d.ts.map +1 -0
- package/dist/unified/typechain-types/interfaces/index.js +3 -0
- package/dist/unified/typechain-types/interfaces/index.js.map +1 -0
- package/dist/unified-esm/src/evm/index.d.ts +4 -0
- package/dist/unified-esm/src/evm/index.d.ts.map +1 -0
- package/dist/unified-esm/src/evm/index.js +5 -0
- package/dist/unified-esm/src/evm/index.js.map +1 -0
- package/dist/unified-esm/src/evm/mailer-client.d.ts +966 -0
- package/dist/unified-esm/src/evm/mailer-client.d.ts.map +1 -0
- package/dist/unified-esm/src/evm/mailer-client.js +615 -0
- package/dist/unified-esm/src/evm/mailer-client.js.map +1 -0
- package/dist/unified-esm/src/react/context/MailerProvider.d.ts +102 -0
- package/dist/unified-esm/src/react/context/MailerProvider.d.ts.map +1 -0
- package/dist/unified-esm/src/react/context/MailerProvider.js +120 -0
- package/dist/unified-esm/src/react/context/MailerProvider.js.map +1 -0
- package/dist/unified-esm/src/react/hooks/useMailerMutations.d.ts +301 -0
- package/dist/unified-esm/src/react/hooks/useMailerMutations.d.ts.map +1 -0
- package/dist/unified-esm/src/react/hooks/useMailerMutations.js +400 -0
- package/dist/unified-esm/src/react/hooks/useMailerMutations.js.map +1 -0
- package/dist/unified-esm/src/react/hooks/useMailerQueries.d.ts +130 -0
- package/dist/unified-esm/src/react/hooks/useMailerQueries.d.ts.map +1 -0
- package/dist/unified-esm/src/react/hooks/useMailerQueries.js +186 -0
- package/dist/unified-esm/src/react/hooks/useMailerQueries.js.map +1 -0
- package/dist/unified-esm/src/react/index.d.ts +37 -0
- package/dist/unified-esm/src/react/index.d.ts.map +1 -0
- package/dist/unified-esm/src/react/index.js +39 -0
- package/dist/unified-esm/src/react/index.js.map +1 -0
- package/dist/unified-esm/src/solana/index.d.ts +3 -0
- package/dist/unified-esm/src/solana/index.d.ts.map +1 -0
- package/dist/unified-esm/src/solana/index.js +3 -0
- package/dist/unified-esm/src/solana/index.js.map +1 -0
- package/dist/unified-esm/src/solana/mailer-client.d.ts +282 -0
- package/dist/unified-esm/src/solana/mailer-client.d.ts.map +1 -0
- package/dist/unified-esm/src/solana/mailer-client.js +985 -0
- package/dist/unified-esm/src/solana/mailer-client.js.map +1 -0
- package/dist/unified-esm/src/solana/types.d.ts +28 -0
- package/dist/unified-esm/src/solana/types.d.ts.map +1 -0
- package/dist/unified-esm/src/solana/types.js +16 -0
- package/dist/unified-esm/src/solana/types.js.map +1 -0
- package/dist/unified-esm/src/unified/index.d.ts +4 -0
- package/dist/unified-esm/src/unified/index.d.ts.map +1 -0
- package/dist/unified-esm/src/unified/index.js +4 -0
- package/dist/unified-esm/src/unified/index.js.map +1 -0
- package/dist/unified-esm/src/unified/onchain-mailer-client.d.ts +173 -0
- package/dist/unified-esm/src/unified/onchain-mailer-client.d.ts.map +1 -0
- package/dist/unified-esm/src/unified/onchain-mailer-client.js +1011 -0
- package/dist/unified-esm/src/unified/onchain-mailer-client.js.map +1 -0
- package/dist/unified-esm/src/unified/types.d.ts +47 -0
- package/dist/unified-esm/src/unified/types.d.ts.map +1 -0
- package/dist/unified-esm/src/unified/types.js +2 -0
- package/dist/unified-esm/src/unified/types.js.map +1 -0
- package/dist/unified-esm/src/unified/wallet-detector.d.ts +28 -0
- package/dist/unified-esm/src/unified/wallet-detector.d.ts.map +1 -0
- package/dist/unified-esm/src/unified/wallet-detector.js +59 -0
- package/dist/unified-esm/src/unified/wallet-detector.js.map +1 -0
- package/dist/unified-esm/src/utils/chain-config.d.ts +75 -0
- package/dist/unified-esm/src/utils/chain-config.d.ts.map +1 -0
- package/dist/unified-esm/src/utils/chain-config.js +199 -0
- package/dist/unified-esm/src/utils/chain-config.js.map +1 -0
- package/dist/unified-esm/src/utils/currency.d.ts +26 -0
- package/dist/unified-esm/src/utils/currency.d.ts.map +1 -0
- package/dist/unified-esm/src/utils/currency.js +31 -0
- package/dist/unified-esm/src/utils/currency.js.map +1 -0
- package/dist/unified-esm/src/utils/index.d.ts +4 -0
- package/dist/unified-esm/src/utils/index.d.ts.map +1 -0
- package/dist/unified-esm/src/utils/index.js +4 -0
- package/dist/unified-esm/src/utils/index.js.map +1 -0
- package/dist/unified-esm/src/utils/validation.d.ts +10 -0
- package/dist/unified-esm/src/utils/validation.d.ts.map +1 -0
- package/dist/unified-esm/src/utils/validation.js +96 -0
- package/dist/unified-esm/src/utils/validation.js.map +1 -0
- package/dist/unified-esm/typechain-types/Mailer.d.ts +698 -0
- package/dist/unified-esm/typechain-types/Mailer.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/Mailer.js +2 -0
- package/dist/unified-esm/typechain-types/Mailer.js.map +1 -0
- package/dist/unified-esm/typechain-types/MockUSDC.d.ts +118 -0
- package/dist/unified-esm/typechain-types/MockUSDC.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/MockUSDC.js +2 -0
- package/dist/unified-esm/typechain-types/MockUSDC.js.map +1 -0
- package/dist/unified-esm/typechain-types/common.d.ts +51 -0
- package/dist/unified-esm/typechain-types/common.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/common.js +2 -0
- package/dist/unified-esm/typechain-types/common.js.map +1 -0
- package/dist/unified-esm/typechain-types/factories/Mailer__factory.d.ts +875 -0
- package/dist/unified-esm/typechain-types/factories/Mailer__factory.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/factories/Mailer__factory.js +1121 -0
- package/dist/unified-esm/typechain-types/factories/Mailer__factory.js.map +1 -0
- package/dist/unified-esm/typechain-types/factories/MockUSDC__factory.d.ts +193 -0
- package/dist/unified-esm/typechain-types/factories/MockUSDC__factory.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/factories/MockUSDC__factory.js +259 -0
- package/dist/unified-esm/typechain-types/factories/MockUSDC__factory.js.map +1 -0
- package/dist/unified-esm/typechain-types/factories/index.d.ts +4 -0
- package/dist/unified-esm/typechain-types/factories/index.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/factories/index.js +7 -0
- package/dist/unified-esm/typechain-types/factories/index.js.map +1 -0
- package/dist/unified-esm/typechain-types/factories/interfaces/IERC20__factory.d.ts +80 -0
- package/dist/unified-esm/typechain-types/factories/interfaces/IERC20__factory.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/factories/interfaces/IERC20__factory.js +112 -0
- package/dist/unified-esm/typechain-types/factories/interfaces/IERC20__factory.js.map +1 -0
- package/dist/unified-esm/typechain-types/factories/interfaces/index.d.ts +2 -0
- package/dist/unified-esm/typechain-types/factories/interfaces/index.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/factories/interfaces/index.js +5 -0
- package/dist/unified-esm/typechain-types/factories/interfaces/index.js.map +1 -0
- package/dist/unified-esm/typechain-types/index.d.ts +10 -0
- package/dist/unified-esm/typechain-types/index.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/index.js +5 -0
- package/dist/unified-esm/typechain-types/index.js.map +1 -0
- package/dist/unified-esm/typechain-types/interfaces/IERC20.d.ts +70 -0
- package/dist/unified-esm/typechain-types/interfaces/IERC20.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/interfaces/IERC20.js +2 -0
- package/dist/unified-esm/typechain-types/interfaces/IERC20.js.map +1 -0
- package/dist/unified-esm/typechain-types/interfaces/index.d.ts +2 -0
- package/dist/unified-esm/typechain-types/interfaces/index.d.ts.map +1 -0
- package/dist/unified-esm/typechain-types/interfaces/index.js +2 -0
- package/dist/unified-esm/typechain-types/interfaces/index.js.map +1 -0
- package/package.json +250 -0
- package/programs/mailer/Cargo.toml +29 -0
- package/programs/mailer/src/lib.rs +2034 -0
- package/programs/mailer/tests/integration_tests.rs +1236 -0
- package/typechain-types/Mailer.ts +1393 -0
- package/typechain-types/MockUSDC.ts +236 -0
- package/typechain-types/common.ts +131 -0
- package/typechain-types/factories/Mailer__factory.ts +1157 -0
- package/typechain-types/factories/MockUSDC__factory.ts +284 -0
- package/typechain-types/factories/index.ts +6 -0
- package/typechain-types/factories/interfaces/IERC20__factory.ts +115 -0
- package/typechain-types/factories/interfaces/index.ts +4 -0
- package/typechain-types/hardhat.d.ts +99 -0
- package/typechain-types/index.ts +12 -0
- package/typechain-types/interfaces/IERC20.ts +148 -0
- 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
|
+
}
|