pda-mint-authority.sol 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import "../libraries/spl_token.sol";
  2. import "solana";
  3. @program_id("J2eUKE878XKXJZaP7vXwxgWnWnNQMqHSkMPoRFQwa86b")
  4. contract pda_mint_authority {
  5. bytes1 bump; // stores the bump for the pda address
  6. @payer(payer)
  7. @seed("mint_authority") // hard-coded seed
  8. constructor(
  9. @bump bytes1 _bump // bump for the pda address
  10. ) {
  11. // Independently derive the PDA address from the seeds, bump, and programId
  12. (address pda, bytes1 pdaBump) = try_find_program_address(["mint_authority"], type(pda_mint_authority).program_id);
  13. // Verify that the bump passed to the constructor matches the bump derived from the seeds and programId
  14. // This ensures that only the canonical pda address can be used to create the account (first bump that generates a valid pda address)
  15. require(pdaBump == _bump, 'INVALID_BUMP');
  16. bump = _bump;
  17. }
  18. @mutableSigner(payer) // payer account
  19. @mutableSigner(mint) // mint account to be created
  20. @mutableAccount(metadata) // metadata account to be created
  21. @account(mintAuthority) // mint authority for the mint account
  22. @account(rentAddress)
  23. @account(metaplexId)
  24. function createTokenMint(
  25. address freezeAuthority, // freeze authority for the mint account
  26. uint8 decimals, // decimals for the mint account
  27. string name, // name for the metadata account
  28. string symbol, // symbol for the metadata account
  29. string uri // uri for the metadata account
  30. ) external {
  31. // Invoke System Program to create a new account for the mint account and,
  32. // Invoke Token Program to initialize the mint account
  33. // Set mint authority, freeze authority, and decimals for the mint account
  34. SplToken.create_mint(
  35. tx.accounts.payer.key, // payer account
  36. tx.accounts.mint.key, // mint account
  37. tx.accounts.mintAuthority.key, // mint authority
  38. freezeAuthority, // freeze authority
  39. decimals // decimals
  40. );
  41. // Invoke Metadata Program to create a new account for the metadata account
  42. _createMetadataAccount(
  43. tx.accounts.metadata.key, // metadata account
  44. tx.accounts.mint.key, // mint account
  45. tx.accounts.mintAuthority.key, // mint authority
  46. tx.accounts.payer.key, // payer
  47. tx.accounts.payer.key, // update authority (of the metadata account)
  48. name, // name
  49. symbol, // symbol
  50. uri, // uri (off-chain metadata json)
  51. tx.accounts.rentAddress.key,
  52. tx.accounts.metaplexId.key
  53. );
  54. }
  55. // Create metadata account, must reimplement manually to sign with PDA, which is the mint authority
  56. function _createMetadataAccount(
  57. address metadata, // metadata account address
  58. address mint, // mint account address
  59. address mintAuthority, // mint authority
  60. address payer, // payer
  61. address updateAuthority, // update authority for the metadata account
  62. string name, // token name
  63. string symbol, // token symbol
  64. string uri, // token uri
  65. address rentAddress,
  66. address metaplexId
  67. ) private {
  68. // // Independently derive the PDA address from the seeds, bump, and programId
  69. (address pda, bytes1 _bump) = try_find_program_address(["mint_authority"], type(pda_mint_authority).program_id);
  70. require(mintAuthority == pda, 'INVALID_PDA');
  71. DataV2 data = DataV2({
  72. name: name,
  73. symbol: symbol,
  74. uri: uri,
  75. sellerFeeBasisPoints: 0,
  76. creatorsPresent: false,
  77. collectionPresent: false,
  78. usesPresent: false
  79. });
  80. CreateMetadataAccountArgsV3 args = CreateMetadataAccountArgsV3({
  81. data: data,
  82. isMutable: true,
  83. collectionDetailsPresent: false
  84. });
  85. AccountMeta[7] metas = [
  86. AccountMeta({pubkey: metadata, is_writable: true, is_signer: false}),
  87. AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
  88. AccountMeta({pubkey: mintAuthority, is_writable: false, is_signer: true}),
  89. AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
  90. AccountMeta({pubkey: updateAuthority, is_writable: false, is_signer: false}),
  91. AccountMeta({pubkey: address"11111111111111111111111111111111", is_writable: false, is_signer: false}),
  92. AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
  93. ];
  94. bytes1 discriminator = 33;
  95. bytes instructionData = abi.encode(discriminator, args);
  96. metaplexId.call{accounts: metas, seeds: [["mint_authority", abi.encode(_bump)]]}(instructionData);
  97. }
  98. struct CreateMetadataAccountArgsV3 {
  99. DataV2 data;
  100. bool isMutable;
  101. bool collectionDetailsPresent; // To handle Rust Option<> in Solidity
  102. }
  103. struct DataV2 {
  104. string name;
  105. string symbol;
  106. string uri;
  107. uint16 sellerFeeBasisPoints;
  108. bool creatorsPresent; // To handle Rust Option<> in Solidity
  109. bool collectionPresent; // To handle Rust Option<> in Solidity
  110. bool usesPresent; // To handle Rust Option<> in Solidity
  111. }
  112. @mutableSigner(payer)
  113. @mutableAccount(tokenAccount)
  114. @account(owner)
  115. @mutableAccount(mint)
  116. @mutableAccount(pdaAccount)
  117. function mintTo() external {
  118. // Create an associated token account for the owner to receive the minted token
  119. SplToken.create_associated_token_account(
  120. tx.accounts.payer.key, // payer account
  121. tx.accounts.tokenAccount.key, // associated token account address
  122. tx.accounts.mint.key, // mint account
  123. tx.accounts.owner.key // owner account
  124. );
  125. // Mint 1 token to the associated token account
  126. _mintTo(
  127. tx.accounts.mint.key, // mint account
  128. tx.accounts.tokenAccount.key, // token account
  129. 1, // amount
  130. tx.accounts.pdaAccount.key
  131. );
  132. // // Remove mint authority from mint account
  133. _removeMintAuthority(
  134. tx.accounts.mint.key, // mint
  135. tx.accounts.pdaAccount.key
  136. );
  137. }
  138. // Invoke the token program to mint tokens to a token account, using a PDA as the mint authority
  139. function _mintTo(address mint, address account, uint64 amount, address pdaAccount) private {
  140. // Independently derive the PDA address from the seeds, bump, and programId
  141. (address pda, bytes1 _bump) = try_find_program_address(["mint_authority"], type(pda_mint_authority).program_id);
  142. require(pdaAccount == pda, 'INVALID_PDA');
  143. // Prepare instruction data
  144. bytes instructionData = new bytes(9);
  145. instructionData[0] = uint8(7); // MintTo instruction index
  146. instructionData.writeUint64LE(amount, 1); // Amount to mint
  147. // Prepare accounts required by instruction
  148. AccountMeta[3] metas = [
  149. AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
  150. AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
  151. AccountMeta({pubkey: pda, is_writable: true, is_signer: true}) // mint authority
  152. ];
  153. // Invoke the token program with prepared accounts and instruction data
  154. SplToken.tokenProgramId.call{accounts: metas, seeds: [["mint_authority", abi.encode(_bump)]]}(instructionData);
  155. }
  156. function _removeMintAuthority(address mintAccount, address pdaAccount) private {
  157. // Independently derive the PDA address from the seeds, bump, and programId
  158. (address pda, bytes1 _bump) = try_find_program_address(["mint_authority"], type(pda_mint_authority).program_id);
  159. require(pdaAccount == pda, 'INVALID_PDA');
  160. AccountMeta[2] metas = [
  161. AccountMeta({pubkey: mintAccount, is_signer: false, is_writable: true}),
  162. AccountMeta({pubkey: pda, is_signer: true, is_writable: false}) // mint authority
  163. ];
  164. bytes instructionData = new bytes(9);
  165. instructionData[0] = uint8(6); // SetAuthority instruction index
  166. instructionData[1] = uint8(0); // AuthorityType::MintTokens
  167. instructionData[3] = 0;
  168. // Invoke the token program with prepared accounts and instruction data
  169. SplToken.tokenProgramId.call{accounts: metas, seeds: [["mint_authority", abi.encode(_bump)]]}(instructionData);
  170. }
  171. }