Skip to content

Commit c290346

Browse files
committed
solang pda-mint-authority
1 parent 7323844 commit c290346

File tree

9 files changed

+1190
-0
lines changed

9 files changed

+1190
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
.anchor
3+
.DS_Store
4+
target
5+
**/*.rs.bk
6+
node_modules
7+
test-ledger
8+
.yarn
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[features]
2+
seeds = false
3+
skip-lint = false
4+
[programs.localnet]
5+
pda_mint_authority = "J2eUKE878XKXJZaP7vXwxgWnWnNQMqHSkMPoRFQwa86b"
6+
7+
[registry]
8+
url = "https://api.apr.dev"
9+
10+
[provider]
11+
cluster = "localnet"
12+
wallet = "~/.config/solana/id.json"
13+
14+
[scripts]
15+
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
16+
17+
[test.validator]
18+
url = "https://api.mainnet-beta.solana.com"
19+
20+
[[test.validator.clone]]
21+
address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"scripts": {
3+
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
4+
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
5+
},
6+
"dependencies": {
7+
"@coral-xyz/anchor": "^0.28.0",
8+
"@metaplex-foundation/js": "^0.19.4",
9+
"@solana/spl-token": "^0.3.8"
10+
},
11+
"devDependencies": {
12+
"@types/bn.js": "^5.1.0",
13+
"@types/chai": "^4.3.0",
14+
"@types/mocha": "^9.0.0",
15+
"chai": "^4.3.4",
16+
"mocha": "^9.0.3",
17+
"prettier": "^2.6.2",
18+
"ts-mocha": "^10.0.0",
19+
"typescript": "^4.3.5"
20+
}
21+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import 'solana';
2+
3+
// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L449
4+
// Solidity does not support Rust Option<> type, so we need to handle it manually
5+
// Requires creating a struct for each combination of Option<> types
6+
// If bool for Option<> type is false, comment out the corresponding struct field otherwise instruction fails with "invalid account data"
7+
// TODO: figure out better way to handle Option<> types
8+
library MplMetadata {
9+
address constant metadataProgramId = address"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
10+
address constant systemAddress = address"11111111111111111111111111111111";
11+
address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
12+
13+
// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L31
14+
struct CreateMetadataAccountArgsV3 {
15+
DataV2 data;
16+
bool isMutable;
17+
bool collectionDetailsPresent; // To handle Rust Option<> in Solidity
18+
// CollectionDetails collectionDetails;
19+
}
20+
21+
// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/data.rs#L22
22+
struct DataV2 {
23+
string name;
24+
string symbol;
25+
string uri;
26+
uint16 sellerFeeBasisPoints;
27+
bool creatorsPresent; // To handle Rust Option<> in Solidity
28+
// Creator[] creators;
29+
bool collectionPresent; // To handle Rust Option<> in Solidity
30+
// Collection collection;
31+
bool usesPresent; // To handle Rust Option<> in Solidity
32+
// Uses uses;
33+
}
34+
35+
// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L10
36+
struct Creator {
37+
address creatorAddress;
38+
bool verified;
39+
uint8 share;
40+
}
41+
42+
// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L66
43+
struct Collection {
44+
bool verified;
45+
address key;
46+
}
47+
48+
// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/collection.rs#L57
49+
struct CollectionDetails {
50+
CollectionDetailsType detailType;
51+
uint64 size;
52+
}
53+
enum CollectionDetailsType {
54+
V1
55+
}
56+
57+
// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L43
58+
struct Uses {
59+
UseMethod useMethod;
60+
uint64 remaining;
61+
uint64 total;
62+
}
63+
64+
// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L35
65+
enum UseMethod {
66+
Burn,
67+
Multiple,
68+
Single
69+
}
70+
71+
function create_metadata_account(
72+
address metadata,
73+
address mint,
74+
address mintAuthority,
75+
address payer,
76+
address updateAuthority,
77+
string name,
78+
string symbol,
79+
string uri
80+
) public view {
81+
// // Example of how to add a Creator[] array to the DataV2 struct
82+
// Creator[] memory creators = new Creator[](1);
83+
// creators[0] = Creator({
84+
// creatorAddress: payer,
85+
// verified: false,
86+
// share: 100
87+
// });
88+
89+
DataV2 data = DataV2({
90+
name: name,
91+
symbol: symbol,
92+
uri: uri,
93+
sellerFeeBasisPoints: 0,
94+
creatorsPresent: false,
95+
// creators: creators,
96+
collectionPresent: false,
97+
// collection: Collection({
98+
// verified: false,
99+
// key: address(0)
100+
// }),
101+
usesPresent: false
102+
// uses: Uses({
103+
// useMethod: UseMethod.Burn,
104+
// remaining: 0,
105+
// total: 0
106+
// })
107+
});
108+
109+
CreateMetadataAccountArgsV3 args = CreateMetadataAccountArgsV3({
110+
data: data,
111+
isMutable: true,
112+
collectionDetailsPresent: false
113+
// collectionDetails: CollectionDetails({
114+
// detailType: CollectionDetailsType.V1,
115+
// size: 0
116+
// })
117+
});
118+
119+
AccountMeta[7] metas = [
120+
AccountMeta({pubkey: metadata, is_writable: true, is_signer: false}),
121+
AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
122+
AccountMeta({pubkey: mintAuthority, is_writable: false, is_signer: true}),
123+
AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
124+
AccountMeta({pubkey: updateAuthority, is_writable: false, is_signer: false}),
125+
AccountMeta({pubkey: systemAddress, is_writable: false, is_signer: false}),
126+
AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
127+
];
128+
129+
bytes1 discriminator = 33;
130+
bytes instructionData = abi.encode(discriminator, args);
131+
132+
metadataProgramId.call{accounts: metas}(instructionData);
133+
}
134+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
2+
import "./spl_token.sol";
3+
import "solana";
4+
5+
@program_id("J2eUKE878XKXJZaP7vXwxgWnWnNQMqHSkMPoRFQwa86b")
6+
contract pda_mint_authority {
7+
bytes1 bump; // stores the bump for the pda address
8+
9+
@payer(payer)
10+
@seed("mint_authority") // hard-coded seed
11+
@bump(_bump) // bump for the pda address
12+
constructor(address payer, bytes1 _bump) {
13+
// Independently derive the PDA address from the seeds, bump, and programId
14+
(address pda, bytes1 pdaBump) = try_find_program_address(["mint_authority"], type(pda_mint_authority).program_id);
15+
16+
// Verify that the bump passed to the constructor matches the bump derived from the seeds and programId
17+
// This ensures that only the canonical pda address can be used to create the account (first bump that generates a valid pda address)
18+
require(pdaBump == _bump, 'INVALID_BUMP');
19+
20+
bump = _bump;
21+
}
22+
23+
function createTokenMint(
24+
address payer, // payer account
25+
address mint, // mint account to be created
26+
address mintAuthority, // mint authority for the mint account
27+
address freezeAuthority, // freeze authority for the mint account
28+
address metadata, // metadata account to be created
29+
uint8 decimals, // decimals for the mint account
30+
string name, // name for the metadata account
31+
string symbol, // symbol for the metadata account
32+
string uri // uri for the metadata account
33+
) public view {
34+
// Invoke System Program to create a new account for the mint account and,
35+
// Invoke Token Program to initialize the mint account
36+
// Set mint authority, freeze authority, and decimals for the mint account
37+
SplToken.create_mint(
38+
payer, // payer account
39+
mint, // mint account
40+
mintAuthority, // mint authority
41+
freezeAuthority, // freeze authority
42+
decimals // decimals
43+
);
44+
45+
// Invoke Metadata Program to create a new account for the metadata account
46+
_createMetadataAccount(
47+
metadata, // metadata account
48+
mint, // mint account
49+
mintAuthority, // mint authority
50+
payer, // payer
51+
payer, // update authority (of the metadata account)
52+
name, // name
53+
symbol, // symbol
54+
uri // uri (off-chain metadata json)
55+
);
56+
}
57+
58+
// Create metadata account, must reimplement manually to sign with PDA, which is the mint authority
59+
function _createMetadataAccount(
60+
address metadata, // metadata account address
61+
address mint, // mint account address
62+
address mintAuthority, // mint authority
63+
address payer, // payer
64+
address updateAuthority, // update authority for the metadata account
65+
string name, // token name
66+
string symbol, // token symbol
67+
string uri // token uri
68+
) private view {
69+
// // Independently derive the PDA address from the seeds, bump, and programId
70+
(address pda, bytes1 _bump) = try_find_program_address(["mint_authority"], type(pda_mint_authority).program_id);
71+
72+
require(address(this) == pda, 'INVALID_PDA');
73+
74+
DataV2 data = DataV2({
75+
name: name,
76+
symbol: symbol,
77+
uri: uri,
78+
sellerFeeBasisPoints: 0,
79+
creatorsPresent: false,
80+
collectionPresent: false,
81+
usesPresent: false
82+
});
83+
84+
CreateMetadataAccountArgsV3 args = CreateMetadataAccountArgsV3({
85+
data: data,
86+
isMutable: true,
87+
collectionDetailsPresent: false
88+
});
89+
90+
AccountMeta[7] metas = [
91+
AccountMeta({pubkey: metadata, is_writable: true, is_signer: false}),
92+
AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
93+
AccountMeta({pubkey: mintAuthority, is_writable: false, is_signer: true}),
94+
AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
95+
AccountMeta({pubkey: updateAuthority, is_writable: false, is_signer: false}),
96+
AccountMeta({pubkey: address"11111111111111111111111111111111", is_writable: false, is_signer: false}),
97+
AccountMeta({pubkey: address"SysvarRent111111111111111111111111111111111", is_writable: false, is_signer: false})
98+
];
99+
100+
bytes1 discriminator = 33;
101+
bytes instructionData = abi.encode(discriminator, args);
102+
103+
address"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s".call{accounts: metas, seeds: [["mint_authority", abi.encode(_bump)]]}(instructionData);
104+
}
105+
106+
struct CreateMetadataAccountArgsV3 {
107+
DataV2 data;
108+
bool isMutable;
109+
bool collectionDetailsPresent; // To handle Rust Option<> in Solidity
110+
}
111+
112+
struct DataV2 {
113+
string name;
114+
string symbol;
115+
string uri;
116+
uint16 sellerFeeBasisPoints;
117+
bool creatorsPresent; // To handle Rust Option<> in Solidity
118+
bool collectionPresent; // To handle Rust Option<> in Solidity
119+
bool usesPresent; // To handle Rust Option<> in Solidity
120+
}
121+
122+
function mintTo(address payer, address tokenAccount, address mint, address owner) public view {
123+
// Create an associated token account for the owner to receive the minted token
124+
SplToken.create_associated_token_account(
125+
payer, // payer account
126+
tokenAccount, // associated token account address
127+
mint, // mint account
128+
owner // owner account
129+
);
130+
131+
// Mint 1 token to the associated token account
132+
_mintTo(
133+
mint, // mint account
134+
tokenAccount, // token account
135+
1 // amount
136+
);
137+
138+
// Remove mint authority from mint account
139+
_removeMintAuthority(
140+
mint // mint
141+
);
142+
}
143+
144+
// Invoke the token program to mint tokens to a token account, using a PDA as the mint authority
145+
function _mintTo(address mint, address account, uint64 amount) private view {
146+
// Independently derive the PDA address from the seeds, bump, and programId
147+
(address pda, bytes1 _bump) = try_find_program_address(["mint_authority"], type(pda_mint_authority).program_id);
148+
require(address(this) == pda, 'INVALID_PDA');
149+
150+
// Prepare instruction data
151+
bytes instructionData = new bytes(9);
152+
instructionData[0] = uint8(7); // MintTo instruction index
153+
instructionData.writeUint64LE(1, 1); // Amount to mint
154+
155+
// Prepare accounts required by instruction
156+
AccountMeta[3] metas = [
157+
AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
158+
AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
159+
AccountMeta({pubkey: pda, is_writable: true, is_signer: true}) // mint authority
160+
];
161+
162+
// Invoke the token program with prepared accounts and instruction data
163+
SplToken.tokenProgramId.call{accounts: metas, seeds: [["mint_authority", abi.encode(_bump)]]}(instructionData);
164+
}
165+
166+
function _removeMintAuthority(address mintAccount) private view {
167+
// Independently derive the PDA address from the seeds, bump, and programId
168+
(address pda, bytes1 _bump) = try_find_program_address(["mint_authority"], type(pda_mint_authority).program_id);
169+
require(address(this) == pda, 'INVALID_PDA');
170+
171+
AccountMeta[2] metas = [
172+
AccountMeta({pubkey: mintAccount, is_signer: false, is_writable: true}),
173+
AccountMeta({pubkey: pda, is_signer: true, is_writable: false}) // mint authority
174+
];
175+
176+
bytes instructionData = new bytes(9);
177+
instructionData[0] = uint8(6); // SetAuthority instruction index
178+
instructionData[1] = uint8(0); // AuthorityType::MintTokens
179+
instructionData[3] = 0;
180+
181+
// Invoke the token program with prepared accounts and instruction data
182+
SplToken.tokenProgramId.call{accounts: metas, seeds: [["mint_authority", abi.encode(_bump)]]}(instructionData);
183+
}
184+
}

0 commit comments

Comments
 (0)