John 2 жил өмнө
parent
commit
7323844a97
38 өөрчлөгдсөн 4332 нэмэгдсэн , 0 устгасан
  1. 8 0
      tokens/create-token/solang/.gitignore
  2. 21 0
      tokens/create-token/solang/Anchor.toml
  3. 20 0
      tokens/create-token/solang/package.json
  4. 47 0
      tokens/create-token/solang/solidity/create-token.sol
  5. 134 0
      tokens/create-token/solang/solidity/mpl_metadata.sol
  6. 387 0
      tokens/create-token/solang/solidity/spl_token.sol
  7. 300 0
      tokens/create-token/solang/solidity/system_instruction.sol
  8. 122 0
      tokens/create-token/solang/tests/create-token.ts
  9. 11 0
      tokens/create-token/solang/tsconfig.json
  10. 8 0
      tokens/nft-minter/solang/.gitignore
  11. 21 0
      tokens/nft-minter/solang/Anchor.toml
  12. 21 0
      tokens/nft-minter/solang/package.json
  13. 134 0
      tokens/nft-minter/solang/solidity/mpl_metadata.sol
  14. 69 0
      tokens/nft-minter/solang/solidity/nft-minter.sol
  15. 387 0
      tokens/nft-minter/solang/solidity/spl_token.sol
  16. 300 0
      tokens/nft-minter/solang/solidity/system_instruction.sol
  17. 123 0
      tokens/nft-minter/solang/tests/nft-minter.ts
  18. 11 0
      tokens/nft-minter/solang/tsconfig.json
  19. 8 0
      tokens/spl-token-minter/solang/.gitignore
  20. 21 0
      tokens/spl-token-minter/solang/Anchor.toml
  21. 21 0
      tokens/spl-token-minter/solang/package.json
  22. 134 0
      tokens/spl-token-minter/solang/solidity/mpl_metadata.sol
  23. 54 0
      tokens/spl-token-minter/solang/solidity/spl-token-minter.sol
  24. 387 0
      tokens/spl-token-minter/solang/solidity/spl_token.sol
  25. 300 0
      tokens/spl-token-minter/solang/solidity/system_instruction.sol
  26. 126 0
      tokens/spl-token-minter/solang/tests/spl-token-minter.ts
  27. 11 0
      tokens/spl-token-minter/solang/tsconfig.json
  28. 8 0
      tokens/transfer-tokens/solang/.gitignore
  29. 8 0
      tokens/transfer-tokens/solang/.prettierignore
  30. 21 0
      tokens/transfer-tokens/solang/Anchor.toml
  31. 12 0
      tokens/transfer-tokens/solang/migrations/deploy.ts
  32. 21 0
      tokens/transfer-tokens/solang/package.json
  33. 134 0
      tokens/transfer-tokens/solang/solidity/mpl_metadata.sol
  34. 387 0
      tokens/transfer-tokens/solang/solidity/spl_token.sol
  35. 300 0
      tokens/transfer-tokens/solang/solidity/system_instruction.sol
  36. 71 0
      tokens/transfer-tokens/solang/solidity/transfer-tokens.sol
  37. 173 0
      tokens/transfer-tokens/solang/tests/transfer-tokens.ts
  38. 11 0
      tokens/transfer-tokens/solang/tsconfig.json

+ 8 - 0
tokens/create-token/solang/.gitignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+**/*.rs.bk
+node_modules
+test-ledger
+.yarn

+ 21 - 0
tokens/create-token/solang/Anchor.toml

@@ -0,0 +1,21 @@
+[features]
+seeds = false
+skip-lint = false
+[programs.localnet]
+create_token = "8eZPhSaXfHqbcrfskVThtCG68kq8MfVHqmtm6wYf4TLb"
+
+[registry]
+url = "https://api.apr.dev"
+
+[provider]
+cluster = "Localnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+
+[test.validator]
+url = "https://api.mainnet-beta.solana.com"
+
+[[test.validator.clone]]
+address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

+ 20 - 0
tokens/create-token/solang/package.json

@@ -0,0 +1,20 @@
+{
+    "scripts": {
+        "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
+        "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    },
+    "dependencies": {
+        "@coral-xyz/anchor": "^0.28.0",
+        "@metaplex-foundation/js": "^0.19.4"
+    },
+    "devDependencies": {
+        "@types/bn.js": "^5.1.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.0.0",
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "prettier": "^2.6.2",
+        "ts-mocha": "^10.0.0",
+        "typescript": "^4.3.5"
+    }
+}

+ 47 - 0
tokens/create-token/solang/solidity/create-token.sol

@@ -0,0 +1,47 @@
+
+import "./spl_token.sol";
+import "./mpl_metadata.sol";
+
+@program_id("8eZPhSaXfHqbcrfskVThtCG68kq8MfVHqmtm6wYf4TLb")
+contract create_token {
+
+    // Creating a dataAccount is required by Solang
+    // The account is unused in this example
+    @payer(payer) // payer account
+    constructor(address payer) {}
+
+    function createTokenMint(
+        address payer, // payer account
+        address mint, // mint account to be created
+        address mintAuthority, // mint authority for the mint account
+        address freezeAuthority, // freeze authority for the mint account
+        address metadata, // metadata account to be created
+        uint8 decimals, // decimals for the mint account
+        string name, // name for the metadata account
+        string symbol, // symbol for the metadata account
+        string uri // uri for the metadata account
+    ) public view {
+        // Invoke System Program to create a new account for the mint account and,
+        // Invoke Token Program to initialize the mint account
+        // Set mint authority, freeze authority, and decimals for the mint account
+        SplToken.create_mint(
+            payer,            // payer account
+            mint,            // mint account
+            mintAuthority,   // mint authority
+            freezeAuthority, // freeze authority
+            decimals         // decimals
+        );
+
+        // Invoke Metadata Program to create a new account for the metadata account
+        MplMetadata.create_metadata_account(
+            metadata, // metadata account
+            mint,  // mint account
+            mintAuthority, // mint authority
+            payer, // payer
+            payer, // update authority (of the metadata account)
+            name, // name
+            symbol, // symbol
+            uri // uri (off-chain metadata json)
+        );
+    }
+}

+ 134 - 0
tokens/create-token/solang/solidity/mpl_metadata.sol

@@ -0,0 +1,134 @@
+import 'solana';
+
+// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L449
+// Solidity does not support Rust Option<> type, so we need to handle it manually
+// Requires creating a struct for each combination of Option<> types
+// If bool for Option<> type is false, comment out the corresponding struct field otherwise instruction fails with "invalid account data"
+// TODO: figure out better way to handle Option<> types
+library MplMetadata {
+	address constant metadataProgramId = address"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
+	address constant systemAddress = address"11111111111111111111111111111111";
+    address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L31
+	struct CreateMetadataAccountArgsV3 {
+        DataV2 data;
+        bool isMutable;
+        bool collectionDetailsPresent; // To handle Rust Option<> in Solidity
+        // CollectionDetails collectionDetails;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/data.rs#L22
+    struct DataV2 {
+        string name;
+        string symbol;
+        string uri;
+        uint16 sellerFeeBasisPoints;
+        bool creatorsPresent; // To handle Rust Option<> in Solidity
+        // Creator[] creators;
+        bool collectionPresent; // To handle Rust Option<> in Solidity
+        // Collection collection;
+        bool usesPresent; // To handle Rust Option<> in Solidity
+        // Uses uses;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L10
+    struct Creator {
+        address creatorAddress;
+        bool verified;
+        uint8 share;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L66
+    struct Collection {
+        bool verified;
+        address key;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/collection.rs#L57
+    struct CollectionDetails {
+        CollectionDetailsType detailType;
+        uint64 size;
+    }
+    enum CollectionDetailsType {
+        V1
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L43
+    struct Uses {
+        UseMethod useMethod;
+        uint64 remaining;
+        uint64 total;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L35
+    enum UseMethod {
+        Burn,
+        Multiple,
+        Single
+    }
+
+	function create_metadata_account(
+		address metadata,
+		address mint,
+		address mintAuthority,
+		address payer,
+		address updateAuthority,
+		string name,
+		string symbol,
+		string uri
+	) public view {
+        // // Example of how to add a Creator[] array to the DataV2 struct
+		// Creator[] memory creators = new Creator[](1);
+        // creators[0] = Creator({
+        //     creatorAddress: payer,
+        //     verified: false,
+        //     share: 100
+        // });
+
+        DataV2 data = DataV2({
+            name: name,
+            symbol: symbol,
+            uri: uri,
+            sellerFeeBasisPoints: 0,
+            creatorsPresent: false,
+             // creators: creators,
+            collectionPresent: false,
+            // collection: Collection({
+            //     verified: false,
+            //     key: address(0)
+            // }),
+            usesPresent: false
+            // uses: Uses({
+            //     useMethod: UseMethod.Burn,
+            //     remaining: 0,
+            //     total: 0
+            // })
+        });
+
+        CreateMetadataAccountArgsV3 args = CreateMetadataAccountArgsV3({
+            data: data,
+            isMutable: true,
+            collectionDetailsPresent: false
+			// collectionDetails: CollectionDetails({
+            //     detailType: CollectionDetailsType.V1,
+            //     size: 0
+            // })
+        });
+
+        AccountMeta[7] metas = [
+            AccountMeta({pubkey: metadata, is_writable: true, is_signer: false}),
+            AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: mintAuthority, is_writable: false, is_signer: true}),
+            AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+            AccountMeta({pubkey: updateAuthority, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: systemAddress, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
+        ];
+
+        bytes1 discriminator = 33;
+        bytes instructionData = abi.encode(discriminator, args);
+
+        metadataProgramId.call{accounts: metas}(instructionData);
+    }
+}

+ 387 - 0
tokens/create-token/solang/solidity/spl_token.sol

@@ -0,0 +1,387 @@
+import 'solana';
+import 'system_instruction.sol';
+
+library SplToken {
+	address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
+	address constant associatedTokenProgramId = address"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
+	address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+	enum TokenInstruction {
+		InitializeMint, // 0
+		InitializeAccount, // 1
+		InitializeMultisig, // 2
+		Transfer, // 3
+		Approve, // 4
+		Revoke, // 5
+		SetAuthority, // 6
+		MintTo, // 7
+		Burn, // 8
+		CloseAccount, // 9
+		FreezeAccount, // 10
+		ThawAccount, // 11
+		TransferChecked, // 12
+		ApproveChecked, // 13
+		MintToChecked, // 14
+		BurnChecked, // 15
+		InitializeAccount2, // 16
+		SyncNative, // 17
+		InitializeAccount3, // 18
+		InitializeMultisig2, // 19
+		InitializeMint2, // 20
+		GetAccountDataSize, // 21
+		InitializeImmutableOwner, // 22
+		AmountToUiAmount, // 23
+		UiAmountToAmount, // 24
+		InitializeMintCloseAuthority, // 25
+		TransferFeeExtension, // 26
+		ConfidentialTransferExtension, // 27
+		DefaultAccountStateExtension, // 28
+		Reallocate, // 29
+		MemoTransferExtension, // 30
+		CreateNativeMint // 31
+	}
+
+	/// Initialize a new token account.
+	///
+	/// @param tokenAccount the public key of the token account to initialize
+	/// @param mint the public key of the mint account for this new token account
+	/// @param owner the public key of the owner of this new token account
+	function initialize_account(address tokenAccount, address mint, address owner) internal view{
+		bytes instr = new bytes(1);
+
+		instr[0] = uint8(TokenInstruction.InitializeAccount);
+		AccountMeta[4] metas = [
+			AccountMeta({pubkey: tokenAccount, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Initialize a new associated token account.
+	///
+	/// @param payer the public key of the payer to create the associated token account
+	/// @param tokenAccount the public key of the token account to initialize
+	/// @param mint the public key of the mint account for this new token account
+	/// @param owner the public key of the owner of this new token account
+    function create_associated_token_account(address payer, address tokenAccount, address mint, address owner) internal view {
+        AccountMeta[6] metas = [
+			AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+			AccountMeta({pubkey: tokenAccount, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: SystemInstruction.systemAddress, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: SplToken.tokenProgramId, is_writable: false, is_signer: false})
+		];
+
+        bytes instructionData = abi.encode((0));
+		associatedTokenProgramId.call{accounts: metas}(instructionData);
+    }
+
+	// Initialize mint instruction data
+	struct InitializeMintInstruction {
+        uint8 instruction;
+        uint8 decimals;
+        address mintAuthority;
+        uint8 freezeAuthorityOption;
+        address freezeAuthority;
+    }
+
+	/// Initialize a new mint account.
+	///
+	/// @param mint the public key of the mint account to initialize
+	/// @param mintAuthority the public key of the mint authority
+	/// @param freezeAuthority the public key of the freeze authority
+	/// @param decimals the decimals of the mint
+	function initialize_mint(address mint, address mintAuthority, address freezeAuthority, uint8 decimals) internal view {
+    	InitializeMintInstruction instr = InitializeMintInstruction({
+            instruction: 20,
+            decimals: decimals,
+            mintAuthority: mintAuthority,
+            freezeAuthorityOption: 1,
+            freezeAuthority: freezeAuthority
+        });
+
+		AccountMeta[1] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Create and initialize a new mint account in one instruction
+	///
+	/// @param payer the public key of the account paying to create the mint account
+	/// @param mint the public key of the mint account to initialize
+	/// @param mintAuthority the public key of the mint authority
+	/// @param freezeAuthority the public key of the freeze authority
+	/// @param decimals the decimals of the mint
+	function create_mint(address payer, address mint, address mintAuthority, address freezeAuthority, uint8 decimals) internal view {
+		// Invoke System Program to create a new account for the mint account
+        // Program owner is set to the Token program
+        SystemInstruction.create_account(
+            payer,   // lamports sent from this account (payer)
+            mint,    // lamports sent to this account (account to be created)
+            1461600, // lamport amount (minimum lamports for mint account)
+            82,      // space required for the account (mint account)
+            SplToken.tokenProgramId // new program owner
+        );
+
+		InitializeMintInstruction instr = InitializeMintInstruction({
+            instruction: 20,
+            decimals: decimals,
+            mintAuthority: mintAuthority,
+            freezeAuthorityOption: 1,
+            freezeAuthority: freezeAuthority
+        });
+
+		AccountMeta[1] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Mint new tokens. The transaction should be signed by the mint authority keypair
+	///
+	/// @param mint the account of the mint
+	/// @param account the token account where the minted tokens should go
+	/// @param authority the public key of the mint authority
+	/// @param amount the amount of tokens to mint
+	function mint_to(address mint, address account, address authority, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.MintTo);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: authority, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Transfer @amount token from @from to @to. The transaction should be signed by the owner
+	/// keypair of the from account.
+	///
+	/// @param from the account to transfer tokens from
+	/// @param to the account to transfer tokens to
+	/// @param owner the publickey of the from account owner keypair
+	/// @param amount the amount to transfer
+	function transfer(address from, address to, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Transfer);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: from, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: to, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Burn @amount tokens in account. This transaction should be signed by the owner.
+	///
+	/// @param account the acount for which tokens should be burned
+	/// @param mint the mint for this token
+	/// @param owner the publickey of the account owner keypair
+	/// @param amount the amount to transfer
+	function burn(address account, address mint, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Burn);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Approve an amount to a delegate. This transaction should be signed by the owner
+	///
+	/// @param account the account for which a delegate should be approved
+	/// @param delegate the delegate publickey
+	/// @param owner the publickey of the account owner keypair
+	/// @param amount the amount to approve
+	function approve(address account, address delegate, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Approve);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: delegate, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Revoke a previously approved delegate. This transaction should be signed by the owner. After
+	/// this transaction, no delgate is approved for any amount.
+	///
+	/// @param account the account for which a delegate should be approved
+	/// @param owner the publickey of the account owner keypair
+	function revoke(address account, address owner) internal view {
+		bytes instr = new bytes(1);
+
+		instr[0] = uint8(TokenInstruction.Revoke);
+
+		AccountMeta[2] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Get the total supply for the mint, i.e. the total amount in circulation
+	/// @param mint the mint for this token
+	function total_supply(address mint) internal view returns (uint64) {
+		AccountInfo account = get_account_info(mint);
+
+		return account.data.readUint64LE(36);
+	}
+
+	/// Get the balance for an account.
+	///
+	/// @param account the account for which we want to know a balance
+	function get_balance(address account) internal view returns (uint64) {
+		AccountInfo ai = get_account_info(account);
+
+		return ai.data.readUint64LE(64);
+	}
+
+	/// Get the account info for an account. This walks the transaction account infos
+	/// and find the account info, or the transaction fails.
+	///
+	/// @param account the account for which we want to have the acount info.
+	function get_account_info(address account) internal view returns (AccountInfo) {
+		for (uint64 i = 0; i < tx.accounts.length; i++) {
+			AccountInfo ai = tx.accounts[i];
+			if (ai.key == account) {
+				return ai;
+			}
+		}
+
+		revert("account missing");
+	}
+
+	/// This enum represents the state of a token account
+	enum AccountState {
+		Uninitialized,
+		Initialized,
+		Frozen
+	}
+
+	/// This struct is the return of 'get_token_account_data'
+	struct TokenAccountData {
+		address mintAccount;
+		address owner;
+		uint64 balance;
+		bool delegate_present;
+		address delegate;
+		AccountState state;
+		bool is_native_present;
+		uint64 is_native;
+		uint64 delegated_amount;
+		bool close_authority_present;
+		address close_authority;
+	}
+
+	/// Fetch the owner, mint account and balance for an associated token account.
+	///
+	/// @param tokenAccount The token account
+	/// @return struct TokenAccountData
+	function get_token_account_data(address tokenAccount) public view returns (TokenAccountData) {
+		AccountInfo ai = get_account_info(tokenAccount);
+
+		TokenAccountData data = TokenAccountData(
+			{
+				mintAccount: ai.data.readAddress(0),
+				owner: ai.data.readAddress(32),
+			 	balance: ai.data.readUint64LE(64),
+				delegate_present: ai.data.readUint32LE(72) > 0,
+				delegate: ai.data.readAddress(76),
+				state: AccountState(ai.data[108]),
+				is_native_present: ai.data.readUint32LE(109) > 0,
+				is_native: ai.data.readUint64LE(113),
+				delegated_amount: ai.data.readUint64LE(121),
+				close_authority_present: ai.data.readUint32LE(129) > 10,
+				close_authority: ai.data.readAddress(133)
+			}
+		);
+
+		return data;
+	}
+
+	// This struct is the return of 'get_mint_account_data'
+	struct MintAccountData {
+		bool authority_present;
+		address mint_authority;
+		uint64 supply;
+		uint8 decimals;
+		bool is_initialized;
+		bool freeze_authority_present;
+		address freeze_authority;
+	}
+
+	/// Retrieve the information saved in a mint account
+	///
+	/// @param mintAccount the account whose information we want to retrive
+	/// @return the MintAccountData struct
+	function get_mint_account_data(address mintAccount) public view returns (MintAccountData) {
+		AccountInfo ai = get_account_info(mintAccount);
+
+		uint32 authority_present = ai.data.readUint32LE(0);
+		uint32 freeze_authority_present = ai.data.readUint32LE(46);
+		MintAccountData data = MintAccountData( {
+			authority_present: authority_present > 0,
+			mint_authority: ai.data.readAddress(4),
+			supply: ai.data.readUint64LE(36),
+			decimals: uint8(ai.data[44]),
+			is_initialized: ai.data[45] > 0,
+			freeze_authority_present: freeze_authority_present > 0,
+			freeze_authority: ai.data.readAddress(50)
+		});
+
+		return data;
+	}
+
+	// A mint account has an authority, whose type is one of the members of this struct.
+	enum AuthorityType {
+		MintTokens,
+		FreezeAccount,
+		AccountOwner,
+		CloseAccount
+	}
+
+	/// Remove the mint authority from a mint account
+	///
+	/// @param mintAccount the public key for the mint account
+	/// @param mintAuthority the public for the mint authority
+	function remove_mint_authority(address mintAccount, address mintAuthority) public view {
+		AccountMeta[2] metas = [
+			AccountMeta({pubkey: mintAccount, is_signer: false, is_writable: true}),
+			AccountMeta({pubkey: mintAuthority, is_signer: true, is_writable: false})
+		];
+
+		bytes data = new bytes(9);
+		data[0] = uint8(TokenInstruction.SetAuthority);
+		data[1] = uint8(AuthorityType.MintTokens);
+		data[3] = 0;
+
+		tokenProgramId.call{accounts: metas}(data);
+	}
+}

+ 300 - 0
tokens/create-token/solang/solidity/system_instruction.sol

@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Disclaimer: This library provides a bridge for Solidity to interact with Solana's system instructions. Although it is production ready,
+// it has not been audited for security, so use it at your own risk.
+
+import 'solana';
+
+library SystemInstruction {
+    address constant systemAddress = address"11111111111111111111111111111111";
+    address constant recentBlockHashes = address"SysvarRecentB1ockHashes11111111111111111111";
+    address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+    uint64 constant state_size = 80;
+
+    enum Instruction {
+        CreateAccount,
+        Assign,
+        Transfer,
+        CreateAccountWithSeed,
+        AdvanceNounceAccount,
+        WithdrawNonceAccount,
+        InitializeNonceAccount,
+        AuthorizeNonceAccount,
+        Allocate,
+        AllocateWithSeed,
+        AssignWithSeed,
+        TransferWithSeed,
+        UpgradeNonceAccount // This is not available on Solana v1.9.15
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to public key for the account to be created
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account
+    /// @param owner public key for the program that will own the account being created
+    function create_account(address from, address to, uint64 lamports, uint64 space, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: true, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.CreateAccount), lamports, space, owner);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to the public key for the account to be created. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'to' address using the seed
+    /// @param seed the string utilized to created the 'to' public key
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account
+    /// @param owner public key for the program that will own the account being created
+    function create_account_with_seed(address from, address to, address base, string seed, uint64 lamports, uint64 space, address owner) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        uint32 buffer_size = 92 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.CreateAccountWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = seed.length + 44;
+        bincode.writeUint64LE(lamports, offset);
+        offset += 8;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Assign account to a program (owner)
+    ///
+    /// @param pubkey the public key for the account whose owner is going to be reassigned
+    /// @param owner the public key for the new account owner
+    function assign(address pubkey, address owner) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pubkey, is_signer: true, is_writable: true})
+        ];
+        bytes bincode = abi.encode(uint32(Instruction.Assign), owner);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Assign account to a program (owner) based on a seed
+    ///
+    /// @param addr the public key for the account whose owner is going to be reassigned. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to created the 'addr' public key
+    /// @param owner the public key for the new program owner
+    function assign_with_seed(address addr, address base, string seed, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+
+        uint32 buffer_size = 76 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.AssignWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        bincode.writeAddress(owner, 44 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports between accounts
+    ///
+    /// @param from public key for the funding account
+    /// @param to public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer(address from, address to, uint64 lamports) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Transfer), lamports);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports from a derived address
+    ///
+    /// @param from_pubkey The funding account public key. It should match create_with_seed(from_base, seed, from_owner)
+    /// @param from_base the base address that derived the 'from_pubkey' key using the seed
+    /// @param seed the string utilized to create the 'from_pubkey' public key
+    /// @param from_owner owner to use to derive the funding account address
+    /// @param to_pubkey the public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer_with_seed(address from_pubkey, address from_base, string seed, address from_owner, address to_pubkey, uint64 lamports) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: from_base, is_signer: true, is_writable: false}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true})
+        ];
+
+        uint32 buffer_size = seed.length + 52;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.TransferWithSeed), 0);
+        bincode.writeUint64LE(lamports, 4);
+        bincode.writeUint64LE(seed.length, 12);
+        bincode.writeString(seed, 20);
+        bincode.writeAddress(from_owner, 20 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Allocate space in a (possibly new) account without funding
+    ///
+    /// @param pub_key account for which to allocate space
+    /// @param space number of bytes of memory to allocate
+    function allocate(address pub_key, uint64 space) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pub_key, is_signer: true, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Allocate), space);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Allocate space for an assign an account at an address derived from a base public key and a seed
+    ///
+    /// @param addr account for which to allocate space. It should match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param space number of bytes of memory to allocate
+    /// @param owner owner to use to derive the 'addr' account address
+    function allocate_with_seed(address addr, address base, string seed, uint64 space, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = new bytes(seed.length + 84);
+        bincode.writeUint32LE(uint32(Instruction.AllocateWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(seed.length, 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = 44 + seed.length;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new nonce account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the account to be created. The public key must match create_with_seed(base, seed, systemAddress)
+    /// @param base the base address that derived the 'nonce' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account_with_seed(address from, address nonce, address base, string seed, address authority, uint64 lamports) internal view {
+        create_account_with_seed(from, nonce, base, seed, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the nonce account to be created
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account(address from, address nonce, address authority, uint64 lamports) internal view {
+        create_account(from, nonce, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Consumes a stored nonce, replacing it with a successor
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the publick key for the entity authorized to execute instructins on the account
+    function advance_nonce_account(address nonce_pubkey, address authorized_pubkey) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AdvanceNounceAccount));
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Withdraw funds from a nonce account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param to_pubkey the recipient account
+    /// @param lamports the number of lamports to withdraw
+    function withdraw_nonce_account(address nonce_pubkey, address authorized_pubkey, address to_pubkey, uint64 lamports) internal view {
+        AccountMeta[5] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.WithdrawNonceAccount), lamports);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Change the entity authorized to execute nonce instructions on the account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param new_authority
+    function authorize_nonce_account(address nonce_pubkey, address authorized_pubkey, address new_authority) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AuthorizeNonceAccount), new_authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// One-time idempotent upgrade of legacy nonce version in order to bump them out of chain domain.
+    ///
+    /// @param nonce the public key for the nonce account
+    // This is not available on Solana v1.9.15
+    function upgrade_nonce_account(address nonce) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.UpgradeNonceAccount));
+        systemAddress.call{accounts: meta}(bincode);
+    }
+}

+ 122 - 0
tokens/create-token/solang/tests/create-token.ts

@@ -0,0 +1,122 @@
+import * as anchor from "@coral-xyz/anchor"
+import { Program } from "@coral-xyz/anchor"
+import { CreateToken } from "../target/types/create_token"
+import { Metaplex } from "@metaplex-foundation/js"
+import { SYSVAR_RENT_PUBKEY, SystemProgram, PublicKey } from "@solana/web3.js"
+
+describe("create-token", () => {
+  const provider = anchor.AnchorProvider.env()
+  anchor.setProvider(provider)
+  const program = anchor.workspace.CreateToken as Program<CreateToken>
+
+  // Generate new keypair to use as data account
+  const dataAccount = anchor.web3.Keypair.generate()
+  const wallet = provider.wallet
+  const connection = provider.connection
+
+  // Metadata for the token
+  const tokenTitle = "Solana Gold"
+  const tokenSymbol = "GOLDSOL"
+  const tokenUri =
+    "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json"
+
+  it("Is initialized!", async () => {
+    // Initialize data account for the program, which is required by Solang
+    const tx = await program.methods
+      .new(wallet.publicKey)
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .signers([dataAccount])
+      .rpc()
+    console.log("Your transaction signature", tx)
+  })
+
+  it("Create an SPL Token!", async () => {
+    // Generate a new keypair for the mint
+    const mintKeypair = anchor.web3.Keypair.generate()
+
+    // Get the metadata address for the mint
+    const metaplex = Metaplex.make(connection)
+    const metadataAddress = await metaplex
+      .nfts()
+      .pdas()
+      .metadata({ mint: mintKeypair.publicKey })
+
+    const tx = await program.methods
+      .createTokenMint(
+        wallet.publicKey, // payer
+        mintKeypair.publicKey, // mint
+        wallet.publicKey, // freeze authority
+        wallet.publicKey, // mint authority
+        metadataAddress, // metadata address
+        9, // decimals for the mint
+        tokenTitle, // token name
+        tokenSymbol, // token symbol
+        tokenUri // token uri (off-chain metadata)
+      )
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey,
+          isWritable: true,
+          isSigner: true,
+        },
+        { pubkey: mintKeypair.publicKey, isWritable: true, isSigner: true },
+        {
+          pubkey: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), // Metadata program id
+          isWritable: false,
+          isSigner: false,
+        },
+        { pubkey: metadataAddress, isWritable: true, isSigner: false },
+        { pubkey: SystemProgram.programId, isWritable: false, isSigner: false },
+        { pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false },
+      ])
+      .signers([mintKeypair])
+      .rpc({ skipPreflight: true })
+    console.log("Your transaction signature", tx)
+  })
+
+  it("Create an NFT!", async () => {
+    // Generate a new keypair for the mint
+    const mintKeypair = anchor.web3.Keypair.generate()
+
+    // Get the metadata address for the mint
+    const metaplex = Metaplex.make(connection)
+    const metadataAddress = await metaplex
+      .nfts()
+      .pdas()
+      .metadata({ mint: mintKeypair.publicKey })
+
+    const tx = await program.methods
+      .createTokenMint(
+        wallet.publicKey,
+        mintKeypair.publicKey,
+        wallet.publicKey,
+        wallet.publicKey,
+        metadataAddress,
+        0, // decimals for the mint, 0 for NFTs "non-fungible"
+        tokenTitle,
+        tokenSymbol,
+        tokenUri
+      )
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey,
+          isWritable: true,
+          isSigner: true,
+        },
+        { pubkey: mintKeypair.publicKey, isWritable: true, isSigner: true },
+        {
+          pubkey: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), // Metadata program id
+          isWritable: false,
+          isSigner: false,
+        },
+        { pubkey: metadataAddress, isWritable: true, isSigner: false },
+        { pubkey: SystemProgram.programId, isWritable: false, isSigner: false },
+        { pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false },
+      ])
+      .signers([mintKeypair])
+      .rpc({ skipPreflight: true })
+    console.log("Your transaction signature", tx)
+  })
+})

+ 11 - 0
tokens/create-token/solang/tsconfig.json

@@ -0,0 +1,11 @@
+{
+            "compilerOptions": {
+              "types": ["mocha", "chai"],
+              "typeRoots": ["./node_modules/@types"],
+              "lib": ["es2015"],
+              "module": "commonjs",
+              "target": "es6",
+              "esModuleInterop": true
+            }
+          }
+          

+ 8 - 0
tokens/nft-minter/solang/.gitignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+**/*.rs.bk
+node_modules
+test-ledger
+.yarn

+ 21 - 0
tokens/nft-minter/solang/Anchor.toml

@@ -0,0 +1,21 @@
+[features]
+seeds = false
+skip-lint = false
+[programs.localnet]
+nft_minter = "F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC"
+
+[registry]
+url = "https://api.apr.dev"
+
+[provider]
+cluster = "Localnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+
+[test.validator]
+url = "https://api.mainnet-beta.solana.com"
+
+[[test.validator.clone]]
+address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

+ 21 - 0
tokens/nft-minter/solang/package.json

@@ -0,0 +1,21 @@
+{
+    "scripts": {
+        "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
+        "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    },
+    "dependencies": {
+        "@coral-xyz/anchor": "^0.28.0",
+        "@metaplex-foundation/js": "^0.19.4",
+        "@solana/spl-token": "^0.3.8"
+    },
+    "devDependencies": {
+        "@types/bn.js": "^5.1.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.0.0",
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "prettier": "^2.6.2",
+        "ts-mocha": "^10.0.0",
+        "typescript": "^4.3.5"
+    }
+}

+ 134 - 0
tokens/nft-minter/solang/solidity/mpl_metadata.sol

@@ -0,0 +1,134 @@
+import 'solana';
+
+// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L449
+// Solidity does not support Rust Option<> type, so we need to handle it manually
+// Requires creating a struct for each combination of Option<> types
+// If bool for Option<> type is false, comment out the corresponding struct field otherwise instruction fails with "invalid account data"
+// TODO: figure out better way to handle Option<> types
+library MplMetadata {
+	address constant metadataProgramId = address"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
+	address constant systemAddress = address"11111111111111111111111111111111";
+    address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L31
+	struct CreateMetadataAccountArgsV3 {
+        DataV2 data;
+        bool isMutable;
+        bool collectionDetailsPresent; // To handle Rust Option<> in Solidity
+        // CollectionDetails collectionDetails;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/data.rs#L22
+    struct DataV2 {
+        string name;
+        string symbol;
+        string uri;
+        uint16 sellerFeeBasisPoints;
+        bool creatorsPresent; // To handle Rust Option<> in Solidity
+        // Creator[] creators;
+        bool collectionPresent; // To handle Rust Option<> in Solidity
+        // Collection collection;
+        bool usesPresent; // To handle Rust Option<> in Solidity
+        // Uses uses;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L10
+    struct Creator {
+        address creatorAddress;
+        bool verified;
+        uint8 share;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L66
+    struct Collection {
+        bool verified;
+        address key;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/collection.rs#L57
+    struct CollectionDetails {
+        CollectionDetailsType detailType;
+        uint64 size;
+    }
+    enum CollectionDetailsType {
+        V1
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L43
+    struct Uses {
+        UseMethod useMethod;
+        uint64 remaining;
+        uint64 total;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L35
+    enum UseMethod {
+        Burn,
+        Multiple,
+        Single
+    }
+
+	function create_metadata_account(
+		address metadata,
+		address mint,
+		address mintAuthority,
+		address payer,
+		address updateAuthority,
+		string name,
+		string symbol,
+		string uri
+	) public view {
+        // // Example of how to add a Creator[] array to the DataV2 struct
+		// Creator[] memory creators = new Creator[](1);
+        // creators[0] = Creator({
+        //     creatorAddress: payer,
+        //     verified: false,
+        //     share: 100
+        // });
+
+        DataV2 data = DataV2({
+            name: name,
+            symbol: symbol,
+            uri: uri,
+            sellerFeeBasisPoints: 0,
+            creatorsPresent: false,
+             // creators: creators,
+            collectionPresent: false,
+            // collection: Collection({
+            //     verified: false,
+            //     key: address(0)
+            // }),
+            usesPresent: false
+            // uses: Uses({
+            //     useMethod: UseMethod.Burn,
+            //     remaining: 0,
+            //     total: 0
+            // })
+        });
+
+        CreateMetadataAccountArgsV3 args = CreateMetadataAccountArgsV3({
+            data: data,
+            isMutable: true,
+            collectionDetailsPresent: false
+			// collectionDetails: CollectionDetails({
+            //     detailType: CollectionDetailsType.V1,
+            //     size: 0
+            // })
+        });
+
+        AccountMeta[7] metas = [
+            AccountMeta({pubkey: metadata, is_writable: true, is_signer: false}),
+            AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: mintAuthority, is_writable: false, is_signer: true}),
+            AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+            AccountMeta({pubkey: updateAuthority, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: systemAddress, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
+        ];
+
+        bytes1 discriminator = 33;
+        bytes instructionData = abi.encode(discriminator, args);
+
+        metadataProgramId.call{accounts: metas}(instructionData);
+    }
+}

+ 69 - 0
tokens/nft-minter/solang/solidity/nft-minter.sol

@@ -0,0 +1,69 @@
+
+import "./spl_token.sol";
+import "./mpl_metadata.sol";
+
+@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC")
+contract nft_minter {
+
+    @payer(payer)
+    constructor(address payer) {}
+
+    function createTokenMint(
+        address payer, // payer account
+        address mint, // mint account to be created
+        address mintAuthority, // mint authority for the mint account
+        address freezeAuthority, // freeze authority for the mint account
+        address metadata, // metadata account to be created
+        uint8 decimals, // decimals for the mint account
+        string name, // name for the metadata account
+        string symbol, // symbol for the metadata account
+        string uri // uri for the metadata account
+    ) public view {
+        // Invoke System Program to create a new account for the mint account and,
+        // Invoke Token Program to initialize the mint account
+        // Set mint authority, freeze authority, and decimals for the mint account
+        SplToken.create_mint(
+            payer,            // payer account
+            mint,            // mint account
+            mintAuthority,   // mint authority
+            freezeAuthority, // freeze authority
+            decimals         // decimals
+        );
+
+        // Invoke Metadata Program to create a new account for the metadata account
+        MplMetadata.create_metadata_account(
+            metadata, // metadata account
+            mint,  // mint account
+            mintAuthority, // mint authority
+            payer, // payer
+            payer, // update authority (of the metadata account)
+            name, // name
+            symbol, // symbol
+            uri // uri (off-chain metadata json)
+        );
+    }
+
+    function mintTo(address payer, address tokenAccount, address mint, address owner) public view {
+        // Create an associated token account for the owner to receive the minted token
+        SplToken.create_associated_token_account(
+            payer, // payer account
+            tokenAccount, // associated token account address
+            mint, // mint account
+            owner // owner account
+        );
+
+        // Mint 1 token to the associated token account
+        SplToken.mint_to(
+            mint, // mint account
+            tokenAccount, // token account
+            payer, // mint authority
+            1 // amount
+        );
+
+        // Remove mint authority from mint account
+        SplToken.remove_mint_authority(
+            mint, // mint
+            payer // mint authority
+        );
+    }
+}

+ 387 - 0
tokens/nft-minter/solang/solidity/spl_token.sol

@@ -0,0 +1,387 @@
+import 'solana';
+import 'system_instruction.sol';
+
+library SplToken {
+	address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
+	address constant associatedTokenProgramId = address"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
+	address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+	enum TokenInstruction {
+		InitializeMint, // 0
+		InitializeAccount, // 1
+		InitializeMultisig, // 2
+		Transfer, // 3
+		Approve, // 4
+		Revoke, // 5
+		SetAuthority, // 6
+		MintTo, // 7
+		Burn, // 8
+		CloseAccount, // 9
+		FreezeAccount, // 10
+		ThawAccount, // 11
+		TransferChecked, // 12
+		ApproveChecked, // 13
+		MintToChecked, // 14
+		BurnChecked, // 15
+		InitializeAccount2, // 16
+		SyncNative, // 17
+		InitializeAccount3, // 18
+		InitializeMultisig2, // 19
+		InitializeMint2, // 20
+		GetAccountDataSize, // 21
+		InitializeImmutableOwner, // 22
+		AmountToUiAmount, // 23
+		UiAmountToAmount, // 24
+		InitializeMintCloseAuthority, // 25
+		TransferFeeExtension, // 26
+		ConfidentialTransferExtension, // 27
+		DefaultAccountStateExtension, // 28
+		Reallocate, // 29
+		MemoTransferExtension, // 30
+		CreateNativeMint // 31
+	}
+
+	/// Initialize a new token account.
+	///
+	/// @param tokenAccount the public key of the token account to initialize
+	/// @param mint the public key of the mint account for this new token account
+	/// @param owner the public key of the owner of this new token account
+	function initialize_account(address tokenAccount, address mint, address owner) internal view{
+		bytes instr = new bytes(1);
+
+		instr[0] = uint8(TokenInstruction.InitializeAccount);
+		AccountMeta[4] metas = [
+			AccountMeta({pubkey: tokenAccount, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Initialize a new associated token account.
+	///
+	/// @param payer the public key of the payer to create the associated token account
+	/// @param tokenAccount the public key of the token account to initialize
+	/// @param mint the public key of the mint account for this new token account
+	/// @param owner the public key of the owner of this new token account
+    function create_associated_token_account(address payer, address tokenAccount, address mint, address owner) internal view {
+        AccountMeta[6] metas = [
+			AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+			AccountMeta({pubkey: tokenAccount, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: SystemInstruction.systemAddress, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: SplToken.tokenProgramId, is_writable: false, is_signer: false})
+		];
+
+        bytes instructionData = abi.encode((0));
+		associatedTokenProgramId.call{accounts: metas}(instructionData);
+    }
+
+	// Initialize mint instruction data
+	struct InitializeMintInstruction {
+        uint8 instruction;
+        uint8 decimals;
+        address mintAuthority;
+        uint8 freezeAuthorityOption;
+        address freezeAuthority;
+    }
+
+	/// Initialize a new mint account.
+	///
+	/// @param mint the public key of the mint account to initialize
+	/// @param mintAuthority the public key of the mint authority
+	/// @param freezeAuthority the public key of the freeze authority
+	/// @param decimals the decimals of the mint
+	function initialize_mint(address mint, address mintAuthority, address freezeAuthority, uint8 decimals) internal view {
+    	InitializeMintInstruction instr = InitializeMintInstruction({
+            instruction: 20,
+            decimals: decimals,
+            mintAuthority: mintAuthority,
+            freezeAuthorityOption: 1,
+            freezeAuthority: freezeAuthority
+        });
+
+		AccountMeta[1] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Create and initialize a new mint account in one instruction
+	///
+	/// @param payer the public key of the account paying to create the mint account
+	/// @param mint the public key of the mint account to initialize
+	/// @param mintAuthority the public key of the mint authority
+	/// @param freezeAuthority the public key of the freeze authority
+	/// @param decimals the decimals of the mint
+	function create_mint(address payer, address mint, address mintAuthority, address freezeAuthority, uint8 decimals) internal view {
+		// Invoke System Program to create a new account for the mint account
+        // Program owner is set to the Token program
+        SystemInstruction.create_account(
+            payer,   // lamports sent from this account (payer)
+            mint,    // lamports sent to this account (account to be created)
+            1461600, // lamport amount (minimum lamports for mint account)
+            82,      // space required for the account (mint account)
+            SplToken.tokenProgramId // new program owner
+        );
+
+		InitializeMintInstruction instr = InitializeMintInstruction({
+            instruction: 20,
+            decimals: decimals,
+            mintAuthority: mintAuthority,
+            freezeAuthorityOption: 1,
+            freezeAuthority: freezeAuthority
+        });
+
+		AccountMeta[1] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Mint new tokens. The transaction should be signed by the mint authority keypair
+	///
+	/// @param mint the account of the mint
+	/// @param account the token account where the minted tokens should go
+	/// @param authority the public key of the mint authority
+	/// @param amount the amount of tokens to mint
+	function mint_to(address mint, address account, address authority, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.MintTo);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: authority, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Transfer @amount token from @from to @to. The transaction should be signed by the owner
+	/// keypair of the from account.
+	///
+	/// @param from the account to transfer tokens from
+	/// @param to the account to transfer tokens to
+	/// @param owner the publickey of the from account owner keypair
+	/// @param amount the amount to transfer
+	function transfer(address from, address to, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Transfer);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: from, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: to, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Burn @amount tokens in account. This transaction should be signed by the owner.
+	///
+	/// @param account the acount for which tokens should be burned
+	/// @param mint the mint for this token
+	/// @param owner the publickey of the account owner keypair
+	/// @param amount the amount to transfer
+	function burn(address account, address mint, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Burn);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Approve an amount to a delegate. This transaction should be signed by the owner
+	///
+	/// @param account the account for which a delegate should be approved
+	/// @param delegate the delegate publickey
+	/// @param owner the publickey of the account owner keypair
+	/// @param amount the amount to approve
+	function approve(address account, address delegate, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Approve);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: delegate, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Revoke a previously approved delegate. This transaction should be signed by the owner. After
+	/// this transaction, no delgate is approved for any amount.
+	///
+	/// @param account the account for which a delegate should be approved
+	/// @param owner the publickey of the account owner keypair
+	function revoke(address account, address owner) internal view {
+		bytes instr = new bytes(1);
+
+		instr[0] = uint8(TokenInstruction.Revoke);
+
+		AccountMeta[2] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Get the total supply for the mint, i.e. the total amount in circulation
+	/// @param mint the mint for this token
+	function total_supply(address mint) internal view returns (uint64) {
+		AccountInfo account = get_account_info(mint);
+
+		return account.data.readUint64LE(36);
+	}
+
+	/// Get the balance for an account.
+	///
+	/// @param account the account for which we want to know a balance
+	function get_balance(address account) internal view returns (uint64) {
+		AccountInfo ai = get_account_info(account);
+
+		return ai.data.readUint64LE(64);
+	}
+
+	/// Get the account info for an account. This walks the transaction account infos
+	/// and find the account info, or the transaction fails.
+	///
+	/// @param account the account for which we want to have the acount info.
+	function get_account_info(address account) internal view returns (AccountInfo) {
+		for (uint64 i = 0; i < tx.accounts.length; i++) {
+			AccountInfo ai = tx.accounts[i];
+			if (ai.key == account) {
+				return ai;
+			}
+		}
+
+		revert("account missing");
+	}
+
+	/// This enum represents the state of a token account
+	enum AccountState {
+		Uninitialized,
+		Initialized,
+		Frozen
+	}
+
+	/// This struct is the return of 'get_token_account_data'
+	struct TokenAccountData {
+		address mintAccount;
+		address owner;
+		uint64 balance;
+		bool delegate_present;
+		address delegate;
+		AccountState state;
+		bool is_native_present;
+		uint64 is_native;
+		uint64 delegated_amount;
+		bool close_authority_present;
+		address close_authority;
+	}
+
+	/// Fetch the owner, mint account and balance for an associated token account.
+	///
+	/// @param tokenAccount The token account
+	/// @return struct TokenAccountData
+	function get_token_account_data(address tokenAccount) public view returns (TokenAccountData) {
+		AccountInfo ai = get_account_info(tokenAccount);
+
+		TokenAccountData data = TokenAccountData(
+			{
+				mintAccount: ai.data.readAddress(0),
+				owner: ai.data.readAddress(32),
+			 	balance: ai.data.readUint64LE(64),
+				delegate_present: ai.data.readUint32LE(72) > 0,
+				delegate: ai.data.readAddress(76),
+				state: AccountState(ai.data[108]),
+				is_native_present: ai.data.readUint32LE(109) > 0,
+				is_native: ai.data.readUint64LE(113),
+				delegated_amount: ai.data.readUint64LE(121),
+				close_authority_present: ai.data.readUint32LE(129) > 10,
+				close_authority: ai.data.readAddress(133)
+			}
+		);
+
+		return data;
+	}
+
+	// This struct is the return of 'get_mint_account_data'
+	struct MintAccountData {
+		bool authority_present;
+		address mint_authority;
+		uint64 supply;
+		uint8 decimals;
+		bool is_initialized;
+		bool freeze_authority_present;
+		address freeze_authority;
+	}
+
+	/// Retrieve the information saved in a mint account
+	///
+	/// @param mintAccount the account whose information we want to retrive
+	/// @return the MintAccountData struct
+	function get_mint_account_data(address mintAccount) public view returns (MintAccountData) {
+		AccountInfo ai = get_account_info(mintAccount);
+
+		uint32 authority_present = ai.data.readUint32LE(0);
+		uint32 freeze_authority_present = ai.data.readUint32LE(46);
+		MintAccountData data = MintAccountData( {
+			authority_present: authority_present > 0,
+			mint_authority: ai.data.readAddress(4),
+			supply: ai.data.readUint64LE(36),
+			decimals: uint8(ai.data[44]),
+			is_initialized: ai.data[45] > 0,
+			freeze_authority_present: freeze_authority_present > 0,
+			freeze_authority: ai.data.readAddress(50)
+		});
+
+		return data;
+	}
+
+	// A mint account has an authority, whose type is one of the members of this struct.
+	enum AuthorityType {
+		MintTokens,
+		FreezeAccount,
+		AccountOwner,
+		CloseAccount
+	}
+
+	/// Remove the mint authority from a mint account
+	///
+	/// @param mintAccount the public key for the mint account
+	/// @param mintAuthority the public for the mint authority
+	function remove_mint_authority(address mintAccount, address mintAuthority) public view {
+		AccountMeta[2] metas = [
+			AccountMeta({pubkey: mintAccount, is_signer: false, is_writable: true}),
+			AccountMeta({pubkey: mintAuthority, is_signer: true, is_writable: false})
+		];
+
+		bytes data = new bytes(9);
+		data[0] = uint8(TokenInstruction.SetAuthority);
+		data[1] = uint8(AuthorityType.MintTokens);
+		data[3] = 0;
+
+		tokenProgramId.call{accounts: metas}(data);
+	}
+}

+ 300 - 0
tokens/nft-minter/solang/solidity/system_instruction.sol

@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Disclaimer: This library provides a bridge for Solidity to interact with Solana's system instructions. Although it is production ready,
+// it has not been audited for security, so use it at your own risk.
+
+import 'solana';
+
+library SystemInstruction {
+    address constant systemAddress = address"11111111111111111111111111111111";
+    address constant recentBlockHashes = address"SysvarRecentB1ockHashes11111111111111111111";
+    address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+    uint64 constant state_size = 80;
+
+    enum Instruction {
+        CreateAccount,
+        Assign,
+        Transfer,
+        CreateAccountWithSeed,
+        AdvanceNounceAccount,
+        WithdrawNonceAccount,
+        InitializeNonceAccount,
+        AuthorizeNonceAccount,
+        Allocate,
+        AllocateWithSeed,
+        AssignWithSeed,
+        TransferWithSeed,
+        UpgradeNonceAccount // This is not available on Solana v1.9.15
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to public key for the account to be created
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account
+    /// @param owner public key for the program that will own the account being created
+    function create_account(address from, address to, uint64 lamports, uint64 space, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: true, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.CreateAccount), lamports, space, owner);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to the public key for the account to be created. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'to' address using the seed
+    /// @param seed the string utilized to created the 'to' public key
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account
+    /// @param owner public key for the program that will own the account being created
+    function create_account_with_seed(address from, address to, address base, string seed, uint64 lamports, uint64 space, address owner) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        uint32 buffer_size = 92 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.CreateAccountWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = seed.length + 44;
+        bincode.writeUint64LE(lamports, offset);
+        offset += 8;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Assign account to a program (owner)
+    ///
+    /// @param pubkey the public key for the account whose owner is going to be reassigned
+    /// @param owner the public key for the new account owner
+    function assign(address pubkey, address owner) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pubkey, is_signer: true, is_writable: true})
+        ];
+        bytes bincode = abi.encode(uint32(Instruction.Assign), owner);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Assign account to a program (owner) based on a seed
+    ///
+    /// @param addr the public key for the account whose owner is going to be reassigned. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to created the 'addr' public key
+    /// @param owner the public key for the new program owner
+    function assign_with_seed(address addr, address base, string seed, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+
+        uint32 buffer_size = 76 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.AssignWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        bincode.writeAddress(owner, 44 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports between accounts
+    ///
+    /// @param from public key for the funding account
+    /// @param to public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer(address from, address to, uint64 lamports) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Transfer), lamports);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports from a derived address
+    ///
+    /// @param from_pubkey The funding account public key. It should match create_with_seed(from_base, seed, from_owner)
+    /// @param from_base the base address that derived the 'from_pubkey' key using the seed
+    /// @param seed the string utilized to create the 'from_pubkey' public key
+    /// @param from_owner owner to use to derive the funding account address
+    /// @param to_pubkey the public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer_with_seed(address from_pubkey, address from_base, string seed, address from_owner, address to_pubkey, uint64 lamports) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: from_base, is_signer: true, is_writable: false}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true})
+        ];
+
+        uint32 buffer_size = seed.length + 52;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.TransferWithSeed), 0);
+        bincode.writeUint64LE(lamports, 4);
+        bincode.writeUint64LE(seed.length, 12);
+        bincode.writeString(seed, 20);
+        bincode.writeAddress(from_owner, 20 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Allocate space in a (possibly new) account without funding
+    ///
+    /// @param pub_key account for which to allocate space
+    /// @param space number of bytes of memory to allocate
+    function allocate(address pub_key, uint64 space) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pub_key, is_signer: true, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Allocate), space);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Allocate space for an assign an account at an address derived from a base public key and a seed
+    ///
+    /// @param addr account for which to allocate space. It should match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param space number of bytes of memory to allocate
+    /// @param owner owner to use to derive the 'addr' account address
+    function allocate_with_seed(address addr, address base, string seed, uint64 space, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = new bytes(seed.length + 84);
+        bincode.writeUint32LE(uint32(Instruction.AllocateWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(seed.length, 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = 44 + seed.length;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new nonce account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the account to be created. The public key must match create_with_seed(base, seed, systemAddress)
+    /// @param base the base address that derived the 'nonce' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account_with_seed(address from, address nonce, address base, string seed, address authority, uint64 lamports) internal view {
+        create_account_with_seed(from, nonce, base, seed, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the nonce account to be created
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account(address from, address nonce, address authority, uint64 lamports) internal view {
+        create_account(from, nonce, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Consumes a stored nonce, replacing it with a successor
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the publick key for the entity authorized to execute instructins on the account
+    function advance_nonce_account(address nonce_pubkey, address authorized_pubkey) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AdvanceNounceAccount));
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Withdraw funds from a nonce account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param to_pubkey the recipient account
+    /// @param lamports the number of lamports to withdraw
+    function withdraw_nonce_account(address nonce_pubkey, address authorized_pubkey, address to_pubkey, uint64 lamports) internal view {
+        AccountMeta[5] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.WithdrawNonceAccount), lamports);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Change the entity authorized to execute nonce instructions on the account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param new_authority
+    function authorize_nonce_account(address nonce_pubkey, address authorized_pubkey, address new_authority) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AuthorizeNonceAccount), new_authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// One-time idempotent upgrade of legacy nonce version in order to bump them out of chain domain.
+    ///
+    /// @param nonce the public key for the nonce account
+    // This is not available on Solana v1.9.15
+    function upgrade_nonce_account(address nonce) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.UpgradeNonceAccount));
+        systemAddress.call{accounts: meta}(bincode);
+    }
+}

+ 123 - 0
tokens/nft-minter/solang/tests/nft-minter.ts

@@ -0,0 +1,123 @@
+import * as anchor from "@coral-xyz/anchor"
+import { Program } from "@coral-xyz/anchor"
+import { NftMinter } from "../target/types/nft_minter"
+import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js"
+import { Metaplex } from "@metaplex-foundation/js"
+import {
+  ASSOCIATED_TOKEN_PROGRAM_ID,
+  getAssociatedTokenAddressSync,
+  TOKEN_PROGRAM_ID,
+} from "@solana/spl-token"
+
+describe("nft-minter", () => {
+  // Configure the client to use the local cluster.
+  const provider = anchor.AnchorProvider.env()
+  anchor.setProvider(provider)
+
+  // Generate a new keypair for the data account for the program
+  const dataAccount = anchor.web3.Keypair.generate()
+
+  // Generate a mint keypair
+  const mintKeypair = anchor.web3.Keypair.generate()
+  const wallet = provider.wallet
+  const connection = provider.connection
+
+  const program = anchor.workspace.NftMinter as Program<NftMinter>
+
+  // Metadata for the NFT
+  const nftTitle = "Homer NFT"
+  const nftSymbol = "HOMR"
+  const nftUri =
+    "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json"
+
+  it("Is initialized!", async () => {
+    // Initialize data account for the program, which is required by Solang
+    const tx = await program.methods
+      .new(wallet.publicKey)
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .signers([dataAccount])
+      .rpc()
+    console.log("Your transaction signature", tx)
+  })
+
+  it("Create an NFT!", async () => {
+    // Get the metadata address for the mint
+    const metaplex = Metaplex.make(connection)
+    const metadataAddress = await metaplex
+      .nfts()
+      .pdas()
+      .metadata({ mint: mintKeypair.publicKey })
+
+    const tx = await program.methods
+      .createTokenMint(
+        wallet.publicKey, // payer
+        mintKeypair.publicKey, // mint
+        wallet.publicKey, // mint authority
+        wallet.publicKey, // freeze authority
+        metadataAddress, // metadata address
+        0, // 0 decimals for NFT
+        nftTitle, // NFT name
+        nftSymbol, // NFT symbol
+        nftUri // NFT URI
+      )
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey,
+          isWritable: true,
+          isSigner: true,
+        },
+        { pubkey: mintKeypair.publicKey, isWritable: true, isSigner: true },
+        {
+          pubkey: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), // Metadata program id
+          isWritable: false,
+          isSigner: false,
+        },
+        { pubkey: metadataAddress, isWritable: true, isSigner: false },
+        { pubkey: SystemProgram.programId, isWritable: false, isSigner: false },
+        { pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false },
+      ])
+      .signers([mintKeypair])
+      .rpc({ skipPreflight: true })
+    console.log("Your transaction signature", tx)
+  })
+
+  it("Mint the NFT to your wallet!", async () => {
+    // Derive wallet's associated token account address for mint
+    const tokenAccount = getAssociatedTokenAddressSync(
+      mintKeypair.publicKey,
+      wallet.publicKey
+    )
+
+    const tx = await program.methods
+      .mintTo(
+        wallet.publicKey, // payer
+        tokenAccount, // associated token account address
+        mintKeypair.publicKey, // mint
+        wallet.publicKey // owner of token account
+      )
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey,
+          isWritable: true,
+          isSigner: true,
+        },
+        { pubkey: tokenAccount, isWritable: true, isSigner: false },
+        { pubkey: mintKeypair.publicKey, isWritable: true, isSigner: false },
+        {
+          pubkey: SystemProgram.programId,
+          isWritable: false,
+          isSigner: false,
+        },
+        { pubkey: TOKEN_PROGRAM_ID, isWritable: false, isSigner: false },
+        {
+          pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
+          isWritable: false,
+          isSigner: false,
+        },
+      ])
+      .rpc({ skipPreflight: true })
+    console.log("Your transaction signature", tx)
+  })
+})

+ 11 - 0
tokens/nft-minter/solang/tsconfig.json

@@ -0,0 +1,11 @@
+{
+            "compilerOptions": {
+              "types": ["mocha", "chai"],
+              "typeRoots": ["./node_modules/@types"],
+              "lib": ["es2015"],
+              "module": "commonjs",
+              "target": "es6",
+              "esModuleInterop": true
+            }
+          }
+          

+ 8 - 0
tokens/spl-token-minter/solang/.gitignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+**/*.rs.bk
+node_modules
+test-ledger
+.yarn

+ 21 - 0
tokens/spl-token-minter/solang/Anchor.toml

@@ -0,0 +1,21 @@
+[features]
+seeds = false
+skip-lint = false
+[programs.localnet]
+spl_token_minter = "F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC"
+
+[registry]
+url = "https://api.apr.dev"
+
+[provider]
+cluster = "Localnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+
+[test.validator]
+url = "https://api.mainnet-beta.solana.com"
+
+[[test.validator.clone]]
+address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

+ 21 - 0
tokens/spl-token-minter/solang/package.json

@@ -0,0 +1,21 @@
+{
+    "scripts": {
+        "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
+        "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    },
+    "dependencies": {
+        "@coral-xyz/anchor": "^0.28.0",
+        "@metaplex-foundation/js": "^0.19.4",
+        "@solana/spl-token": "^0.3.8"
+    },
+    "devDependencies": {
+        "@types/bn.js": "^5.1.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.0.0",
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "prettier": "^2.6.2",
+        "ts-mocha": "^10.0.0",
+        "typescript": "^4.3.5"
+    }
+}

+ 134 - 0
tokens/spl-token-minter/solang/solidity/mpl_metadata.sol

@@ -0,0 +1,134 @@
+import 'solana';
+
+// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L449
+// Solidity does not support Rust Option<> type, so we need to handle it manually
+// Requires creating a struct for each combination of Option<> types
+// If bool for Option<> type is false, comment out the corresponding struct field otherwise instruction fails with "invalid account data"
+// TODO: figure out better way to handle Option<> types
+library MplMetadata {
+	address constant metadataProgramId = address"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
+	address constant systemAddress = address"11111111111111111111111111111111";
+    address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L31
+	struct CreateMetadataAccountArgsV3 {
+        DataV2 data;
+        bool isMutable;
+        bool collectionDetailsPresent; // To handle Rust Option<> in Solidity
+        // CollectionDetails collectionDetails;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/data.rs#L22
+    struct DataV2 {
+        string name;
+        string symbol;
+        string uri;
+        uint16 sellerFeeBasisPoints;
+        bool creatorsPresent; // To handle Rust Option<> in Solidity
+        // Creator[] creators;
+        bool collectionPresent; // To handle Rust Option<> in Solidity
+        // Collection collection;
+        bool usesPresent; // To handle Rust Option<> in Solidity
+        // Uses uses;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L10
+    struct Creator {
+        address creatorAddress;
+        bool verified;
+        uint8 share;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L66
+    struct Collection {
+        bool verified;
+        address key;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/collection.rs#L57
+    struct CollectionDetails {
+        CollectionDetailsType detailType;
+        uint64 size;
+    }
+    enum CollectionDetailsType {
+        V1
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L43
+    struct Uses {
+        UseMethod useMethod;
+        uint64 remaining;
+        uint64 total;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L35
+    enum UseMethod {
+        Burn,
+        Multiple,
+        Single
+    }
+
+	function create_metadata_account(
+		address metadata,
+		address mint,
+		address mintAuthority,
+		address payer,
+		address updateAuthority,
+		string name,
+		string symbol,
+		string uri
+	) public view {
+        // // Example of how to add a Creator[] array to the DataV2 struct
+		// Creator[] memory creators = new Creator[](1);
+        // creators[0] = Creator({
+        //     creatorAddress: payer,
+        //     verified: false,
+        //     share: 100
+        // });
+
+        DataV2 data = DataV2({
+            name: name,
+            symbol: symbol,
+            uri: uri,
+            sellerFeeBasisPoints: 0,
+            creatorsPresent: false,
+             // creators: creators,
+            collectionPresent: false,
+            // collection: Collection({
+            //     verified: false,
+            //     key: address(0)
+            // }),
+            usesPresent: false
+            // uses: Uses({
+            //     useMethod: UseMethod.Burn,
+            //     remaining: 0,
+            //     total: 0
+            // })
+        });
+
+        CreateMetadataAccountArgsV3 args = CreateMetadataAccountArgsV3({
+            data: data,
+            isMutable: true,
+            collectionDetailsPresent: false
+			// collectionDetails: CollectionDetails({
+            //     detailType: CollectionDetailsType.V1,
+            //     size: 0
+            // })
+        });
+
+        AccountMeta[7] metas = [
+            AccountMeta({pubkey: metadata, is_writable: true, is_signer: false}),
+            AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: mintAuthority, is_writable: false, is_signer: true}),
+            AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+            AccountMeta({pubkey: updateAuthority, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: systemAddress, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
+        ];
+
+        bytes1 discriminator = 33;
+        bytes instructionData = abi.encode(discriminator, args);
+
+        metadataProgramId.call{accounts: metas}(instructionData);
+    }
+}

+ 54 - 0
tokens/spl-token-minter/solang/solidity/spl-token-minter.sol

@@ -0,0 +1,54 @@
+
+import "./spl_token.sol";
+import "./mpl_metadata.sol";
+
+@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC")
+contract spl_token_minter {
+    @payer(payer)
+    constructor(address payer) {}
+
+    function createTokenMint(
+        address payer, // payer account
+        address mint, // mint account to be created
+        address mintAuthority, // mint authority for the mint account
+        address freezeAuthority, // freeze authority for the mint account
+        address metadata, // metadata account to be created
+        uint8 decimals, // decimals for the mint account
+        string name, // name for the metadata account
+        string symbol, // symbol for the metadata account
+        string uri // uri for the metadata account
+    ) public view {
+        // Invoke System Program to create a new account for the mint account and,
+        // Invoke Token Program to initialize the mint account
+        // Set mint authority, freeze authority, and decimals for the mint account
+        SplToken.create_mint(
+            payer,            // payer account
+            mint,            // mint account
+            mintAuthority,   // mint authority
+            freezeAuthority, // freeze authority
+            decimals         // decimals
+        );
+
+        // Invoke Metadata Program to create a new account for the metadata account
+        MplMetadata.create_metadata_account(
+            metadata, // metadata account
+            mint,  // mint account
+            mintAuthority, // mint authority
+            payer, // payer
+            payer, // update authority (of the metadata account)
+            name, // name
+            symbol, // symbol
+            uri // uri (off-chain metadata json)
+        );
+    }
+
+    function mintTo(address payer, address tokenAccount, address mint, address owner, uint64 amount) public view {
+        // Mint tokens to the token account
+        SplToken.mint_to(
+            mint, // mint account
+            tokenAccount, // token account
+            payer, // mint authority
+            amount // amount
+        );
+    }
+}

+ 387 - 0
tokens/spl-token-minter/solang/solidity/spl_token.sol

@@ -0,0 +1,387 @@
+import 'solana';
+import 'system_instruction.sol';
+
+library SplToken {
+	address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
+	address constant associatedTokenProgramId = address"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
+	address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+	enum TokenInstruction {
+		InitializeMint, // 0
+		InitializeAccount, // 1
+		InitializeMultisig, // 2
+		Transfer, // 3
+		Approve, // 4
+		Revoke, // 5
+		SetAuthority, // 6
+		MintTo, // 7
+		Burn, // 8
+		CloseAccount, // 9
+		FreezeAccount, // 10
+		ThawAccount, // 11
+		TransferChecked, // 12
+		ApproveChecked, // 13
+		MintToChecked, // 14
+		BurnChecked, // 15
+		InitializeAccount2, // 16
+		SyncNative, // 17
+		InitializeAccount3, // 18
+		InitializeMultisig2, // 19
+		InitializeMint2, // 20
+		GetAccountDataSize, // 21
+		InitializeImmutableOwner, // 22
+		AmountToUiAmount, // 23
+		UiAmountToAmount, // 24
+		InitializeMintCloseAuthority, // 25
+		TransferFeeExtension, // 26
+		ConfidentialTransferExtension, // 27
+		DefaultAccountStateExtension, // 28
+		Reallocate, // 29
+		MemoTransferExtension, // 30
+		CreateNativeMint // 31
+	}
+
+	/// Initialize a new token account.
+	///
+	/// @param tokenAccount the public key of the token account to initialize
+	/// @param mint the public key of the mint account for this new token account
+	/// @param owner the public key of the owner of this new token account
+	function initialize_account(address tokenAccount, address mint, address owner) internal view{
+		bytes instr = new bytes(1);
+
+		instr[0] = uint8(TokenInstruction.InitializeAccount);
+		AccountMeta[4] metas = [
+			AccountMeta({pubkey: tokenAccount, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Initialize a new associated token account.
+	///
+	/// @param payer the public key of the payer to create the associated token account
+	/// @param tokenAccount the public key of the token account to initialize
+	/// @param mint the public key of the mint account for this new token account
+	/// @param owner the public key of the owner of this new token account
+    function create_associated_token_account(address payer, address tokenAccount, address mint, address owner) internal view {
+        AccountMeta[6] metas = [
+			AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+			AccountMeta({pubkey: tokenAccount, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: SystemInstruction.systemAddress, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: SplToken.tokenProgramId, is_writable: false, is_signer: false})
+		];
+
+        bytes instructionData = abi.encode((0));
+		associatedTokenProgramId.call{accounts: metas}(instructionData);
+    }
+
+	// Initialize mint instruction data
+	struct InitializeMintInstruction {
+        uint8 instruction;
+        uint8 decimals;
+        address mintAuthority;
+        uint8 freezeAuthorityOption;
+        address freezeAuthority;
+    }
+
+	/// Initialize a new mint account.
+	///
+	/// @param mint the public key of the mint account to initialize
+	/// @param mintAuthority the public key of the mint authority
+	/// @param freezeAuthority the public key of the freeze authority
+	/// @param decimals the decimals of the mint
+	function initialize_mint(address mint, address mintAuthority, address freezeAuthority, uint8 decimals) internal view {
+    	InitializeMintInstruction instr = InitializeMintInstruction({
+            instruction: 20,
+            decimals: decimals,
+            mintAuthority: mintAuthority,
+            freezeAuthorityOption: 1,
+            freezeAuthority: freezeAuthority
+        });
+
+		AccountMeta[1] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Create and initialize a new mint account in one instruction
+	///
+	/// @param payer the public key of the account paying to create the mint account
+	/// @param mint the public key of the mint account to initialize
+	/// @param mintAuthority the public key of the mint authority
+	/// @param freezeAuthority the public key of the freeze authority
+	/// @param decimals the decimals of the mint
+	function create_mint(address payer, address mint, address mintAuthority, address freezeAuthority, uint8 decimals) internal view {
+		// Invoke System Program to create a new account for the mint account
+        // Program owner is set to the Token program
+        SystemInstruction.create_account(
+            payer,   // lamports sent from this account (payer)
+            mint,    // lamports sent to this account (account to be created)
+            1461600, // lamport amount (minimum lamports for mint account)
+            82,      // space required for the account (mint account)
+            SplToken.tokenProgramId // new program owner
+        );
+
+		InitializeMintInstruction instr = InitializeMintInstruction({
+            instruction: 20,
+            decimals: decimals,
+            mintAuthority: mintAuthority,
+            freezeAuthorityOption: 1,
+            freezeAuthority: freezeAuthority
+        });
+
+		AccountMeta[1] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Mint new tokens. The transaction should be signed by the mint authority keypair
+	///
+	/// @param mint the account of the mint
+	/// @param account the token account where the minted tokens should go
+	/// @param authority the public key of the mint authority
+	/// @param amount the amount of tokens to mint
+	function mint_to(address mint, address account, address authority, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.MintTo);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: authority, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Transfer @amount token from @from to @to. The transaction should be signed by the owner
+	/// keypair of the from account.
+	///
+	/// @param from the account to transfer tokens from
+	/// @param to the account to transfer tokens to
+	/// @param owner the publickey of the from account owner keypair
+	/// @param amount the amount to transfer
+	function transfer(address from, address to, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Transfer);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: from, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: to, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Burn @amount tokens in account. This transaction should be signed by the owner.
+	///
+	/// @param account the acount for which tokens should be burned
+	/// @param mint the mint for this token
+	/// @param owner the publickey of the account owner keypair
+	/// @param amount the amount to transfer
+	function burn(address account, address mint, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Burn);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Approve an amount to a delegate. This transaction should be signed by the owner
+	///
+	/// @param account the account for which a delegate should be approved
+	/// @param delegate the delegate publickey
+	/// @param owner the publickey of the account owner keypair
+	/// @param amount the amount to approve
+	function approve(address account, address delegate, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Approve);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: delegate, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Revoke a previously approved delegate. This transaction should be signed by the owner. After
+	/// this transaction, no delgate is approved for any amount.
+	///
+	/// @param account the account for which a delegate should be approved
+	/// @param owner the publickey of the account owner keypair
+	function revoke(address account, address owner) internal view {
+		bytes instr = new bytes(1);
+
+		instr[0] = uint8(TokenInstruction.Revoke);
+
+		AccountMeta[2] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Get the total supply for the mint, i.e. the total amount in circulation
+	/// @param mint the mint for this token
+	function total_supply(address mint) internal view returns (uint64) {
+		AccountInfo account = get_account_info(mint);
+
+		return account.data.readUint64LE(36);
+	}
+
+	/// Get the balance for an account.
+	///
+	/// @param account the account for which we want to know a balance
+	function get_balance(address account) internal view returns (uint64) {
+		AccountInfo ai = get_account_info(account);
+
+		return ai.data.readUint64LE(64);
+	}
+
+	/// Get the account info for an account. This walks the transaction account infos
+	/// and find the account info, or the transaction fails.
+	///
+	/// @param account the account for which we want to have the acount info.
+	function get_account_info(address account) internal view returns (AccountInfo) {
+		for (uint64 i = 0; i < tx.accounts.length; i++) {
+			AccountInfo ai = tx.accounts[i];
+			if (ai.key == account) {
+				return ai;
+			}
+		}
+
+		revert("account missing");
+	}
+
+	/// This enum represents the state of a token account
+	enum AccountState {
+		Uninitialized,
+		Initialized,
+		Frozen
+	}
+
+	/// This struct is the return of 'get_token_account_data'
+	struct TokenAccountData {
+		address mintAccount;
+		address owner;
+		uint64 balance;
+		bool delegate_present;
+		address delegate;
+		AccountState state;
+		bool is_native_present;
+		uint64 is_native;
+		uint64 delegated_amount;
+		bool close_authority_present;
+		address close_authority;
+	}
+
+	/// Fetch the owner, mint account and balance for an associated token account.
+	///
+	/// @param tokenAccount The token account
+	/// @return struct TokenAccountData
+	function get_token_account_data(address tokenAccount) public view returns (TokenAccountData) {
+		AccountInfo ai = get_account_info(tokenAccount);
+
+		TokenAccountData data = TokenAccountData(
+			{
+				mintAccount: ai.data.readAddress(0),
+				owner: ai.data.readAddress(32),
+			 	balance: ai.data.readUint64LE(64),
+				delegate_present: ai.data.readUint32LE(72) > 0,
+				delegate: ai.data.readAddress(76),
+				state: AccountState(ai.data[108]),
+				is_native_present: ai.data.readUint32LE(109) > 0,
+				is_native: ai.data.readUint64LE(113),
+				delegated_amount: ai.data.readUint64LE(121),
+				close_authority_present: ai.data.readUint32LE(129) > 10,
+				close_authority: ai.data.readAddress(133)
+			}
+		);
+
+		return data;
+	}
+
+	// This struct is the return of 'get_mint_account_data'
+	struct MintAccountData {
+		bool authority_present;
+		address mint_authority;
+		uint64 supply;
+		uint8 decimals;
+		bool is_initialized;
+		bool freeze_authority_present;
+		address freeze_authority;
+	}
+
+	/// Retrieve the information saved in a mint account
+	///
+	/// @param mintAccount the account whose information we want to retrive
+	/// @return the MintAccountData struct
+	function get_mint_account_data(address mintAccount) public view returns (MintAccountData) {
+		AccountInfo ai = get_account_info(mintAccount);
+
+		uint32 authority_present = ai.data.readUint32LE(0);
+		uint32 freeze_authority_present = ai.data.readUint32LE(46);
+		MintAccountData data = MintAccountData( {
+			authority_present: authority_present > 0,
+			mint_authority: ai.data.readAddress(4),
+			supply: ai.data.readUint64LE(36),
+			decimals: uint8(ai.data[44]),
+			is_initialized: ai.data[45] > 0,
+			freeze_authority_present: freeze_authority_present > 0,
+			freeze_authority: ai.data.readAddress(50)
+		});
+
+		return data;
+	}
+
+	// A mint account has an authority, whose type is one of the members of this struct.
+	enum AuthorityType {
+		MintTokens,
+		FreezeAccount,
+		AccountOwner,
+		CloseAccount
+	}
+
+	/// Remove the mint authority from a mint account
+	///
+	/// @param mintAccount the public key for the mint account
+	/// @param mintAuthority the public for the mint authority
+	function remove_mint_authority(address mintAccount, address mintAuthority) public view {
+		AccountMeta[2] metas = [
+			AccountMeta({pubkey: mintAccount, is_signer: false, is_writable: true}),
+			AccountMeta({pubkey: mintAuthority, is_signer: true, is_writable: false})
+		];
+
+		bytes data = new bytes(9);
+		data[0] = uint8(TokenInstruction.SetAuthority);
+		data[1] = uint8(AuthorityType.MintTokens);
+		data[3] = 0;
+
+		tokenProgramId.call{accounts: metas}(data);
+	}
+}

+ 300 - 0
tokens/spl-token-minter/solang/solidity/system_instruction.sol

@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Disclaimer: This library provides a bridge for Solidity to interact with Solana's system instructions. Although it is production ready,
+// it has not been audited for security, so use it at your own risk.
+
+import 'solana';
+
+library SystemInstruction {
+    address constant systemAddress = address"11111111111111111111111111111111";
+    address constant recentBlockHashes = address"SysvarRecentB1ockHashes11111111111111111111";
+    address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+    uint64 constant state_size = 80;
+
+    enum Instruction {
+        CreateAccount,
+        Assign,
+        Transfer,
+        CreateAccountWithSeed,
+        AdvanceNounceAccount,
+        WithdrawNonceAccount,
+        InitializeNonceAccount,
+        AuthorizeNonceAccount,
+        Allocate,
+        AllocateWithSeed,
+        AssignWithSeed,
+        TransferWithSeed,
+        UpgradeNonceAccount // This is not available on Solana v1.9.15
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to public key for the account to be created
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account
+    /// @param owner public key for the program that will own the account being created
+    function create_account(address from, address to, uint64 lamports, uint64 space, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: true, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.CreateAccount), lamports, space, owner);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to the public key for the account to be created. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'to' address using the seed
+    /// @param seed the string utilized to created the 'to' public key
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account
+    /// @param owner public key for the program that will own the account being created
+    function create_account_with_seed(address from, address to, address base, string seed, uint64 lamports, uint64 space, address owner) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        uint32 buffer_size = 92 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.CreateAccountWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = seed.length + 44;
+        bincode.writeUint64LE(lamports, offset);
+        offset += 8;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Assign account to a program (owner)
+    ///
+    /// @param pubkey the public key for the account whose owner is going to be reassigned
+    /// @param owner the public key for the new account owner
+    function assign(address pubkey, address owner) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pubkey, is_signer: true, is_writable: true})
+        ];
+        bytes bincode = abi.encode(uint32(Instruction.Assign), owner);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Assign account to a program (owner) based on a seed
+    ///
+    /// @param addr the public key for the account whose owner is going to be reassigned. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to created the 'addr' public key
+    /// @param owner the public key for the new program owner
+    function assign_with_seed(address addr, address base, string seed, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+
+        uint32 buffer_size = 76 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.AssignWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        bincode.writeAddress(owner, 44 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports between accounts
+    ///
+    /// @param from public key for the funding account
+    /// @param to public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer(address from, address to, uint64 lamports) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Transfer), lamports);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports from a derived address
+    ///
+    /// @param from_pubkey The funding account public key. It should match create_with_seed(from_base, seed, from_owner)
+    /// @param from_base the base address that derived the 'from_pubkey' key using the seed
+    /// @param seed the string utilized to create the 'from_pubkey' public key
+    /// @param from_owner owner to use to derive the funding account address
+    /// @param to_pubkey the public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer_with_seed(address from_pubkey, address from_base, string seed, address from_owner, address to_pubkey, uint64 lamports) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: from_base, is_signer: true, is_writable: false}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true})
+        ];
+
+        uint32 buffer_size = seed.length + 52;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.TransferWithSeed), 0);
+        bincode.writeUint64LE(lamports, 4);
+        bincode.writeUint64LE(seed.length, 12);
+        bincode.writeString(seed, 20);
+        bincode.writeAddress(from_owner, 20 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Allocate space in a (possibly new) account without funding
+    ///
+    /// @param pub_key account for which to allocate space
+    /// @param space number of bytes of memory to allocate
+    function allocate(address pub_key, uint64 space) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pub_key, is_signer: true, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Allocate), space);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Allocate space for an assign an account at an address derived from a base public key and a seed
+    ///
+    /// @param addr account for which to allocate space. It should match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param space number of bytes of memory to allocate
+    /// @param owner owner to use to derive the 'addr' account address
+    function allocate_with_seed(address addr, address base, string seed, uint64 space, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = new bytes(seed.length + 84);
+        bincode.writeUint32LE(uint32(Instruction.AllocateWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(seed.length, 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = 44 + seed.length;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new nonce account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the account to be created. The public key must match create_with_seed(base, seed, systemAddress)
+    /// @param base the base address that derived the 'nonce' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account_with_seed(address from, address nonce, address base, string seed, address authority, uint64 lamports) internal view {
+        create_account_with_seed(from, nonce, base, seed, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the nonce account to be created
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account(address from, address nonce, address authority, uint64 lamports) internal view {
+        create_account(from, nonce, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Consumes a stored nonce, replacing it with a successor
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the publick key for the entity authorized to execute instructins on the account
+    function advance_nonce_account(address nonce_pubkey, address authorized_pubkey) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AdvanceNounceAccount));
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Withdraw funds from a nonce account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param to_pubkey the recipient account
+    /// @param lamports the number of lamports to withdraw
+    function withdraw_nonce_account(address nonce_pubkey, address authorized_pubkey, address to_pubkey, uint64 lamports) internal view {
+        AccountMeta[5] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.WithdrawNonceAccount), lamports);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Change the entity authorized to execute nonce instructions on the account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param new_authority
+    function authorize_nonce_account(address nonce_pubkey, address authorized_pubkey, address new_authority) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AuthorizeNonceAccount), new_authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// One-time idempotent upgrade of legacy nonce version in order to bump them out of chain domain.
+    ///
+    /// @param nonce the public key for the nonce account
+    // This is not available on Solana v1.9.15
+    function upgrade_nonce_account(address nonce) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.UpgradeNonceAccount));
+        systemAddress.call{accounts: meta}(bincode);
+    }
+}

+ 126 - 0
tokens/spl-token-minter/solang/tests/spl-token-minter.ts

@@ -0,0 +1,126 @@
+import * as anchor from "@coral-xyz/anchor"
+import { Program } from "@coral-xyz/anchor"
+import { SplTokenMinter } from "../target/types/spl_token_minter"
+import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js"
+import { Metaplex } from "@metaplex-foundation/js"
+import {
+  ASSOCIATED_TOKEN_PROGRAM_ID,
+  getOrCreateAssociatedTokenAccount,
+  TOKEN_PROGRAM_ID,
+} from "@solana/spl-token"
+
+describe("spl-token-minter", () => {
+  // Configure the client to use the local cluster.
+  const provider = anchor.AnchorProvider.env()
+  anchor.setProvider(provider)
+
+  // Generate a new keypair for the data account for the program
+  const dataAccount = anchor.web3.Keypair.generate()
+  // Generate a mint keypair
+  const mintKeypair = anchor.web3.Keypair.generate()
+  const wallet = provider.wallet as anchor.Wallet
+  const connection = provider.connection
+
+  const program = anchor.workspace.SplTokenMinter as Program<SplTokenMinter>
+
+  // Metadata for the Token
+  const tokenTitle = "Solana Gold"
+  const tokenSymbol = "GOLDSOL"
+  const tokenUri =
+    "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json"
+
+  it("Is initialized!", async () => {
+    // Initialize data account for the program, which is required by Solang
+    const tx = await program.methods
+      .new(wallet.publicKey)
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .signers([dataAccount])
+      .rpc()
+    console.log("Your transaction signature", tx)
+  })
+
+  it("Create an SPL Token!", async () => {
+    // Get the metadata address for the mint
+    const metaplex = Metaplex.make(connection)
+    const metadataAddress = await metaplex
+      .nfts()
+      .pdas()
+      .metadata({ mint: mintKeypair.publicKey })
+
+    // Create the token mint
+    const tx = await program.methods
+      .createTokenMint(
+        wallet.publicKey, // payer
+        mintKeypair.publicKey, // mint
+        wallet.publicKey, // mint authority
+        wallet.publicKey, // freeze authority
+        metadataAddress, // metadata address
+        9, // decimals
+        tokenTitle, // token name
+        tokenSymbol, // token symbol
+        tokenUri // token uri
+      )
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey,
+          isWritable: true,
+          isSigner: true,
+        },
+        { pubkey: mintKeypair.publicKey, isWritable: true, isSigner: true },
+        {
+          pubkey: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), // Metadata program id
+          isWritable: false,
+          isSigner: false,
+        },
+        { pubkey: metadataAddress, isWritable: true, isSigner: false },
+        { pubkey: SystemProgram.programId, isWritable: false, isSigner: false },
+        { pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false },
+      ])
+      .signers([mintKeypair])
+      .rpc({ skipPreflight: true })
+    console.log("Your transaction signature", tx)
+  })
+
+  it("Mint some tokens to your wallet!", async () => {
+    // Wallet's associated token account address for mint
+    const tokenAccount = await getOrCreateAssociatedTokenAccount(
+      connection,
+      wallet.payer, // payer
+      mintKeypair.publicKey, // mint
+      wallet.publicKey // owner
+    )
+
+    const tx = await program.methods
+      .mintTo(
+        wallet.publicKey, // payer
+        tokenAccount.address, // associated token account address
+        mintKeypair.publicKey, // mint
+        wallet.publicKey, // owner of token account
+        new anchor.BN(150) // amount to mint
+      )
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey,
+          isWritable: true,
+          isSigner: true,
+        },
+        { pubkey: tokenAccount.address, isWritable: true, isSigner: false },
+        { pubkey: mintKeypair.publicKey, isWritable: true, isSigner: false },
+        {
+          pubkey: SystemProgram.programId,
+          isWritable: false,
+          isSigner: false,
+        },
+        { pubkey: TOKEN_PROGRAM_ID, isWritable: false, isSigner: false },
+        {
+          pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
+          isWritable: false,
+          isSigner: false,
+        },
+      ])
+      .rpc({ skipPreflight: true })
+    console.log("Your transaction signature", tx)
+  })
+})

+ 11 - 0
tokens/spl-token-minter/solang/tsconfig.json

@@ -0,0 +1,11 @@
+{
+            "compilerOptions": {
+              "types": ["mocha", "chai"],
+              "typeRoots": ["./node_modules/@types"],
+              "lib": ["es2015"],
+              "module": "commonjs",
+              "target": "es6",
+              "esModuleInterop": true
+            }
+          }
+          

+ 8 - 0
tokens/transfer-tokens/solang/.gitignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+**/*.rs.bk
+node_modules
+test-ledger
+.yarn

+ 8 - 0
tokens/transfer-tokens/solang/.prettierignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+node_modules
+dist
+build
+test-ledger

+ 21 - 0
tokens/transfer-tokens/solang/Anchor.toml

@@ -0,0 +1,21 @@
+[features]
+seeds = false
+skip-lint = false
+[programs.localnet]
+transfer_tokens = "F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC"
+
+[registry]
+url = "https://api.apr.dev"
+
+[provider]
+cluster = "Localnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+
+[test.validator]
+url = "https://api.mainnet-beta.solana.com"
+
+[[test.validator.clone]]
+address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

+ 12 - 0
tokens/transfer-tokens/solang/migrations/deploy.ts

@@ -0,0 +1,12 @@
+// Migrations are an early feature. Currently, they're nothing more than this
+// single deploy script that's invoked from the CLI, injecting a provider
+// configured from the workspace's Anchor.toml.
+
+const anchor = require("@coral-xyz/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+};

+ 21 - 0
tokens/transfer-tokens/solang/package.json

@@ -0,0 +1,21 @@
+{
+    "scripts": {
+        "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
+        "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    },
+    "dependencies": {
+        "@coral-xyz/anchor": "^0.28.0",
+        "@metaplex-foundation/js": "^0.19.4",
+        "@solana/spl-token": "^0.3.8"
+    },
+    "devDependencies": {
+        "@types/bn.js": "^5.1.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.0.0",
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "prettier": "^2.6.2",
+        "ts-mocha": "^10.0.0",
+        "typescript": "^4.3.5"
+    }
+}

+ 134 - 0
tokens/transfer-tokens/solang/solidity/mpl_metadata.sol

@@ -0,0 +1,134 @@
+import 'solana';
+
+// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L449
+// Solidity does not support Rust Option<> type, so we need to handle it manually
+// Requires creating a struct for each combination of Option<> types
+// If bool for Option<> type is false, comment out the corresponding struct field otherwise instruction fails with "invalid account data"
+// TODO: figure out better way to handle Option<> types
+library MplMetadata {
+	address constant metadataProgramId = address"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
+	address constant systemAddress = address"11111111111111111111111111111111";
+    address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L31
+	struct CreateMetadataAccountArgsV3 {
+        DataV2 data;
+        bool isMutable;
+        bool collectionDetailsPresent; // To handle Rust Option<> in Solidity
+        // CollectionDetails collectionDetails;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/data.rs#L22
+    struct DataV2 {
+        string name;
+        string symbol;
+        string uri;
+        uint16 sellerFeeBasisPoints;
+        bool creatorsPresent; // To handle Rust Option<> in Solidity
+        // Creator[] creators;
+        bool collectionPresent; // To handle Rust Option<> in Solidity
+        // Collection collection;
+        bool usesPresent; // To handle Rust Option<> in Solidity
+        // Uses uses;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L10
+    struct Creator {
+        address creatorAddress;
+        bool verified;
+        uint8 share;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L66
+    struct Collection {
+        bool verified;
+        address key;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/collection.rs#L57
+    struct CollectionDetails {
+        CollectionDetailsType detailType;
+        uint64 size;
+    }
+    enum CollectionDetailsType {
+        V1
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L43
+    struct Uses {
+        UseMethod useMethod;
+        uint64 remaining;
+        uint64 total;
+    }
+
+	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L35
+    enum UseMethod {
+        Burn,
+        Multiple,
+        Single
+    }
+
+	function create_metadata_account(
+		address metadata,
+		address mint,
+		address mintAuthority,
+		address payer,
+		address updateAuthority,
+		string name,
+		string symbol,
+		string uri
+	) public view {
+        // // Example of how to add a Creator[] array to the DataV2 struct
+		// Creator[] memory creators = new Creator[](1);
+        // creators[0] = Creator({
+        //     creatorAddress: payer,
+        //     verified: false,
+        //     share: 100
+        // });
+
+        DataV2 data = DataV2({
+            name: name,
+            symbol: symbol,
+            uri: uri,
+            sellerFeeBasisPoints: 0,
+            creatorsPresent: false,
+             // creators: creators,
+            collectionPresent: false,
+            // collection: Collection({
+            //     verified: false,
+            //     key: address(0)
+            // }),
+            usesPresent: false
+            // uses: Uses({
+            //     useMethod: UseMethod.Burn,
+            //     remaining: 0,
+            //     total: 0
+            // })
+        });
+
+        CreateMetadataAccountArgsV3 args = CreateMetadataAccountArgsV3({
+            data: data,
+            isMutable: true,
+            collectionDetailsPresent: false
+			// collectionDetails: CollectionDetails({
+            //     detailType: CollectionDetailsType.V1,
+            //     size: 0
+            // })
+        });
+
+        AccountMeta[7] metas = [
+            AccountMeta({pubkey: metadata, is_writable: true, is_signer: false}),
+            AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: mintAuthority, is_writable: false, is_signer: true}),
+            AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+            AccountMeta({pubkey: updateAuthority, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: systemAddress, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
+        ];
+
+        bytes1 discriminator = 33;
+        bytes instructionData = abi.encode(discriminator, args);
+
+        metadataProgramId.call{accounts: metas}(instructionData);
+    }
+}

+ 387 - 0
tokens/transfer-tokens/solang/solidity/spl_token.sol

@@ -0,0 +1,387 @@
+import 'solana';
+import 'system_instruction.sol';
+
+library SplToken {
+	address constant tokenProgramId = address"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
+	address constant associatedTokenProgramId = address"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
+	address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+	enum TokenInstruction {
+		InitializeMint, // 0
+		InitializeAccount, // 1
+		InitializeMultisig, // 2
+		Transfer, // 3
+		Approve, // 4
+		Revoke, // 5
+		SetAuthority, // 6
+		MintTo, // 7
+		Burn, // 8
+		CloseAccount, // 9
+		FreezeAccount, // 10
+		ThawAccount, // 11
+		TransferChecked, // 12
+		ApproveChecked, // 13
+		MintToChecked, // 14
+		BurnChecked, // 15
+		InitializeAccount2, // 16
+		SyncNative, // 17
+		InitializeAccount3, // 18
+		InitializeMultisig2, // 19
+		InitializeMint2, // 20
+		GetAccountDataSize, // 21
+		InitializeImmutableOwner, // 22
+		AmountToUiAmount, // 23
+		UiAmountToAmount, // 24
+		InitializeMintCloseAuthority, // 25
+		TransferFeeExtension, // 26
+		ConfidentialTransferExtension, // 27
+		DefaultAccountStateExtension, // 28
+		Reallocate, // 29
+		MemoTransferExtension, // 30
+		CreateNativeMint // 31
+	}
+
+	/// Initialize a new token account.
+	///
+	/// @param tokenAccount the public key of the token account to initialize
+	/// @param mint the public key of the mint account for this new token account
+	/// @param owner the public key of the owner of this new token account
+	function initialize_account(address tokenAccount, address mint, address owner) internal view{
+		bytes instr = new bytes(1);
+
+		instr[0] = uint8(TokenInstruction.InitializeAccount);
+		AccountMeta[4] metas = [
+			AccountMeta({pubkey: tokenAccount, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Initialize a new associated token account.
+	///
+	/// @param payer the public key of the payer to create the associated token account
+	/// @param tokenAccount the public key of the token account to initialize
+	/// @param mint the public key of the mint account for this new token account
+	/// @param owner the public key of the owner of this new token account
+    function create_associated_token_account(address payer, address tokenAccount, address mint, address owner) internal view {
+        AccountMeta[6] metas = [
+			AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+			AccountMeta({pubkey: tokenAccount, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: SystemInstruction.systemAddress, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: SplToken.tokenProgramId, is_writable: false, is_signer: false})
+		];
+
+        bytes instructionData = abi.encode((0));
+		associatedTokenProgramId.call{accounts: metas}(instructionData);
+    }
+
+	// Initialize mint instruction data
+	struct InitializeMintInstruction {
+        uint8 instruction;
+        uint8 decimals;
+        address mintAuthority;
+        uint8 freezeAuthorityOption;
+        address freezeAuthority;
+    }
+
+	/// Initialize a new mint account.
+	///
+	/// @param mint the public key of the mint account to initialize
+	/// @param mintAuthority the public key of the mint authority
+	/// @param freezeAuthority the public key of the freeze authority
+	/// @param decimals the decimals of the mint
+	function initialize_mint(address mint, address mintAuthority, address freezeAuthority, uint8 decimals) internal view {
+    	InitializeMintInstruction instr = InitializeMintInstruction({
+            instruction: 20,
+            decimals: decimals,
+            mintAuthority: mintAuthority,
+            freezeAuthorityOption: 1,
+            freezeAuthority: freezeAuthority
+        });
+
+		AccountMeta[1] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Create and initialize a new mint account in one instruction
+	///
+	/// @param payer the public key of the account paying to create the mint account
+	/// @param mint the public key of the mint account to initialize
+	/// @param mintAuthority the public key of the mint authority
+	/// @param freezeAuthority the public key of the freeze authority
+	/// @param decimals the decimals of the mint
+	function create_mint(address payer, address mint, address mintAuthority, address freezeAuthority, uint8 decimals) internal view {
+		// Invoke System Program to create a new account for the mint account
+        // Program owner is set to the Token program
+        SystemInstruction.create_account(
+            payer,   // lamports sent from this account (payer)
+            mint,    // lamports sent to this account (account to be created)
+            1461600, // lamport amount (minimum lamports for mint account)
+            82,      // space required for the account (mint account)
+            SplToken.tokenProgramId // new program owner
+        );
+
+		InitializeMintInstruction instr = InitializeMintInstruction({
+            instruction: 20,
+            decimals: decimals,
+            mintAuthority: mintAuthority,
+            freezeAuthorityOption: 1,
+            freezeAuthority: freezeAuthority
+        });
+
+		AccountMeta[1] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Mint new tokens. The transaction should be signed by the mint authority keypair
+	///
+	/// @param mint the account of the mint
+	/// @param account the token account where the minted tokens should go
+	/// @param authority the public key of the mint authority
+	/// @param amount the amount of tokens to mint
+	function mint_to(address mint, address account, address authority, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.MintTo);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: authority, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Transfer @amount token from @from to @to. The transaction should be signed by the owner
+	/// keypair of the from account.
+	///
+	/// @param from the account to transfer tokens from
+	/// @param to the account to transfer tokens to
+	/// @param owner the publickey of the from account owner keypair
+	/// @param amount the amount to transfer
+	function transfer(address from, address to, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Transfer);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: from, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: to, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Burn @amount tokens in account. This transaction should be signed by the owner.
+	///
+	/// @param account the acount for which tokens should be burned
+	/// @param mint the mint for this token
+	/// @param owner the publickey of the account owner keypair
+	/// @param amount the amount to transfer
+	function burn(address account, address mint, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Burn);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: mint, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: true, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Approve an amount to a delegate. This transaction should be signed by the owner
+	///
+	/// @param account the account for which a delegate should be approved
+	/// @param delegate the delegate publickey
+	/// @param owner the publickey of the account owner keypair
+	/// @param amount the amount to approve
+	function approve(address account, address delegate, address owner, uint64 amount) internal view {
+		bytes instr = new bytes(9);
+
+		instr[0] = uint8(TokenInstruction.Approve);
+		instr.writeUint64LE(amount, 1);
+
+		AccountMeta[3] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: delegate, is_writable: false, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Revoke a previously approved delegate. This transaction should be signed by the owner. After
+	/// this transaction, no delgate is approved for any amount.
+	///
+	/// @param account the account for which a delegate should be approved
+	/// @param owner the publickey of the account owner keypair
+	function revoke(address account, address owner) internal view {
+		bytes instr = new bytes(1);
+
+		instr[0] = uint8(TokenInstruction.Revoke);
+
+		AccountMeta[2] metas = [
+			AccountMeta({pubkey: account, is_writable: true, is_signer: false}),
+			AccountMeta({pubkey: owner, is_writable: false, is_signer: true})
+		];
+
+		tokenProgramId.call{accounts: metas}(instr);
+	}
+
+	/// Get the total supply for the mint, i.e. the total amount in circulation
+	/// @param mint the mint for this token
+	function total_supply(address mint) internal view returns (uint64) {
+		AccountInfo account = get_account_info(mint);
+
+		return account.data.readUint64LE(36);
+	}
+
+	/// Get the balance for an account.
+	///
+	/// @param account the account for which we want to know a balance
+	function get_balance(address account) internal view returns (uint64) {
+		AccountInfo ai = get_account_info(account);
+
+		return ai.data.readUint64LE(64);
+	}
+
+	/// Get the account info for an account. This walks the transaction account infos
+	/// and find the account info, or the transaction fails.
+	///
+	/// @param account the account for which we want to have the acount info.
+	function get_account_info(address account) internal view returns (AccountInfo) {
+		for (uint64 i = 0; i < tx.accounts.length; i++) {
+			AccountInfo ai = tx.accounts[i];
+			if (ai.key == account) {
+				return ai;
+			}
+		}
+
+		revert("account missing");
+	}
+
+	/// This enum represents the state of a token account
+	enum AccountState {
+		Uninitialized,
+		Initialized,
+		Frozen
+	}
+
+	/// This struct is the return of 'get_token_account_data'
+	struct TokenAccountData {
+		address mintAccount;
+		address owner;
+		uint64 balance;
+		bool delegate_present;
+		address delegate;
+		AccountState state;
+		bool is_native_present;
+		uint64 is_native;
+		uint64 delegated_amount;
+		bool close_authority_present;
+		address close_authority;
+	}
+
+	/// Fetch the owner, mint account and balance for an associated token account.
+	///
+	/// @param tokenAccount The token account
+	/// @return struct TokenAccountData
+	function get_token_account_data(address tokenAccount) public view returns (TokenAccountData) {
+		AccountInfo ai = get_account_info(tokenAccount);
+
+		TokenAccountData data = TokenAccountData(
+			{
+				mintAccount: ai.data.readAddress(0),
+				owner: ai.data.readAddress(32),
+			 	balance: ai.data.readUint64LE(64),
+				delegate_present: ai.data.readUint32LE(72) > 0,
+				delegate: ai.data.readAddress(76),
+				state: AccountState(ai.data[108]),
+				is_native_present: ai.data.readUint32LE(109) > 0,
+				is_native: ai.data.readUint64LE(113),
+				delegated_amount: ai.data.readUint64LE(121),
+				close_authority_present: ai.data.readUint32LE(129) > 10,
+				close_authority: ai.data.readAddress(133)
+			}
+		);
+
+		return data;
+	}
+
+	// This struct is the return of 'get_mint_account_data'
+	struct MintAccountData {
+		bool authority_present;
+		address mint_authority;
+		uint64 supply;
+		uint8 decimals;
+		bool is_initialized;
+		bool freeze_authority_present;
+		address freeze_authority;
+	}
+
+	/// Retrieve the information saved in a mint account
+	///
+	/// @param mintAccount the account whose information we want to retrive
+	/// @return the MintAccountData struct
+	function get_mint_account_data(address mintAccount) public view returns (MintAccountData) {
+		AccountInfo ai = get_account_info(mintAccount);
+
+		uint32 authority_present = ai.data.readUint32LE(0);
+		uint32 freeze_authority_present = ai.data.readUint32LE(46);
+		MintAccountData data = MintAccountData( {
+			authority_present: authority_present > 0,
+			mint_authority: ai.data.readAddress(4),
+			supply: ai.data.readUint64LE(36),
+			decimals: uint8(ai.data[44]),
+			is_initialized: ai.data[45] > 0,
+			freeze_authority_present: freeze_authority_present > 0,
+			freeze_authority: ai.data.readAddress(50)
+		});
+
+		return data;
+	}
+
+	// A mint account has an authority, whose type is one of the members of this struct.
+	enum AuthorityType {
+		MintTokens,
+		FreezeAccount,
+		AccountOwner,
+		CloseAccount
+	}
+
+	/// Remove the mint authority from a mint account
+	///
+	/// @param mintAccount the public key for the mint account
+	/// @param mintAuthority the public for the mint authority
+	function remove_mint_authority(address mintAccount, address mintAuthority) public view {
+		AccountMeta[2] metas = [
+			AccountMeta({pubkey: mintAccount, is_signer: false, is_writable: true}),
+			AccountMeta({pubkey: mintAuthority, is_signer: true, is_writable: false})
+		];
+
+		bytes data = new bytes(9);
+		data[0] = uint8(TokenInstruction.SetAuthority);
+		data[1] = uint8(AuthorityType.MintTokens);
+		data[3] = 0;
+
+		tokenProgramId.call{accounts: metas}(data);
+	}
+}

+ 300 - 0
tokens/transfer-tokens/solang/solidity/system_instruction.sol

@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: Apache-2.0
+
+// Disclaimer: This library provides a bridge for Solidity to interact with Solana's system instructions. Although it is production ready,
+// it has not been audited for security, so use it at your own risk.
+
+import 'solana';
+
+library SystemInstruction {
+    address constant systemAddress = address"11111111111111111111111111111111";
+    address constant recentBlockHashes = address"SysvarRecentB1ockHashes11111111111111111111";
+    address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
+    uint64 constant state_size = 80;
+
+    enum Instruction {
+        CreateAccount,
+        Assign,
+        Transfer,
+        CreateAccountWithSeed,
+        AdvanceNounceAccount,
+        WithdrawNonceAccount,
+        InitializeNonceAccount,
+        AuthorizeNonceAccount,
+        Allocate,
+        AllocateWithSeed,
+        AssignWithSeed,
+        TransferWithSeed,
+        UpgradeNonceAccount // This is not available on Solana v1.9.15
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to public key for the account to be created
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account
+    /// @param owner public key for the program that will own the account being created
+    function create_account(address from, address to, uint64 lamports, uint64 space, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: true, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.CreateAccount), lamports, space, owner);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param to the public key for the account to be created. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'to' address using the seed
+    /// @param seed the string utilized to created the 'to' public key
+    /// @param lamports amount of lamports to be transfered to the new account
+    /// @param space the size in bytes that is going to be made available for the account
+    /// @param owner public key for the program that will own the account being created
+    function create_account_with_seed(address from, address to, address base, string seed, uint64 lamports, uint64 space, address owner) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        uint32 buffer_size = 92 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.CreateAccountWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = seed.length + 44;
+        bincode.writeUint64LE(lamports, offset);
+        offset += 8;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Assign account to a program (owner)
+    ///
+    /// @param pubkey the public key for the account whose owner is going to be reassigned
+    /// @param owner the public key for the new account owner
+    function assign(address pubkey, address owner) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pubkey, is_signer: true, is_writable: true})
+        ];
+        bytes bincode = abi.encode(uint32(Instruction.Assign), owner);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Assign account to a program (owner) based on a seed
+    ///
+    /// @param addr the public key for the account whose owner is going to be reassigned. The public key must match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to created the 'addr' public key
+    /// @param owner the public key for the new program owner
+    function assign_with_seed(address addr, address base, string seed, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+
+        uint32 buffer_size = 76 + seed.length;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.AssignWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(uint64(seed.length), 36);
+        bincode.writeString(seed, 44);
+        bincode.writeAddress(owner, 44 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports between accounts
+    ///
+    /// @param from public key for the funding account
+    /// @param to public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer(address from, address to, uint64 lamports) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
+            AccountMeta({pubkey: to, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Transfer), lamports);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Transfer lamports from a derived address
+    ///
+    /// @param from_pubkey The funding account public key. It should match create_with_seed(from_base, seed, from_owner)
+    /// @param from_base the base address that derived the 'from_pubkey' key using the seed
+    /// @param seed the string utilized to create the 'from_pubkey' public key
+    /// @param from_owner owner to use to derive the funding account address
+    /// @param to_pubkey the public key for the recipient account
+    /// @param lamports amount of lamports to transfer
+    function transfer_with_seed(address from_pubkey, address from_base, string seed, address from_owner, address to_pubkey, uint64 lamports) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: from_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: from_base, is_signer: true, is_writable: false}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true})
+        ];
+
+        uint32 buffer_size = seed.length + 52;
+        bytes bincode = new bytes(buffer_size);
+        bincode.writeUint32LE(uint32(Instruction.TransferWithSeed), 0);
+        bincode.writeUint64LE(lamports, 4);
+        bincode.writeUint64LE(seed.length, 12);
+        bincode.writeString(seed, 20);
+        bincode.writeAddress(from_owner, 20 + seed.length);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Allocate space in a (possibly new) account without funding
+    ///
+    /// @param pub_key account for which to allocate space
+    /// @param space number of bytes of memory to allocate
+    function allocate(address pub_key, uint64 space) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: pub_key, is_signer: true, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.Allocate), space);
+
+        systemAddress.call{accounts: meta}(bincode);
+    }
+
+    /// Allocate space for an assign an account at an address derived from a base public key and a seed
+    ///
+    /// @param addr account for which to allocate space. It should match create_with_seed(base, seed, owner)
+    /// @param base the base address that derived the 'addr' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param space number of bytes of memory to allocate
+    /// @param owner owner to use to derive the 'addr' account address
+    function allocate_with_seed(address addr, address base, string seed, uint64 space, address owner) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: base, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = new bytes(seed.length + 84);
+        bincode.writeUint32LE(uint32(Instruction.AllocateWithSeed), 0);
+        bincode.writeAddress(base, 4);
+        bincode.writeUint64LE(seed.length, 36);
+        bincode.writeString(seed, 44);
+        uint32 offset = 44 + seed.length;
+        bincode.writeUint64LE(space, offset);
+        offset += 8;
+        bincode.writeAddress(owner, offset);
+
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new nonce account on Solana using a public key derived from a seed
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the account to be created. The public key must match create_with_seed(base, seed, systemAddress)
+    /// @param base the base address that derived the 'nonce' key using the seed
+    /// @param seed the string utilized to create the 'addr' public key
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account_with_seed(address from, address nonce, address base, string seed, address authority, uint64 lamports) internal view {
+        create_account_with_seed(from, nonce, base, seed, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Create a new account on Solana
+    ///
+    /// @param from public key for the account from which to transfer lamports to the new account
+    /// @param nonce the public key for the nonce account to be created
+    /// @param authority The entity authorized to execute nonce instructions on the account
+    /// @param lamports amount of lamports to be transfered to the new account
+    function create_nonce_account(address from, address nonce, address authority, uint64 lamports) internal view {
+        create_account(from, nonce, lamports, state_size, systemAddress);
+
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Consumes a stored nonce, replacing it with a successor
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the publick key for the entity authorized to execute instructins on the account
+    function advance_nonce_account(address nonce_pubkey, address authorized_pubkey) internal view {
+        AccountMeta[3] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AdvanceNounceAccount));
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Withdraw funds from a nonce account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param to_pubkey the recipient account
+    /// @param lamports the number of lamports to withdraw
+    function withdraw_nonce_account(address nonce_pubkey, address authorized_pubkey, address to_pubkey, uint64 lamports) internal view {
+        AccountMeta[5] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.WithdrawNonceAccount), lamports);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// Change the entity authorized to execute nonce instructions on the account
+    ///
+    /// @param nonce_pubkey the public key for the nonce account
+    /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
+    /// @param new_authority
+    function authorize_nonce_account(address nonce_pubkey, address authorized_pubkey, address new_authority) internal view {
+        AccountMeta[2] metas = [
+            AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
+            AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.AuthorizeNonceAccount), new_authority);
+        systemAddress.call{accounts: metas}(bincode);
+    }
+
+    /// One-time idempotent upgrade of legacy nonce version in order to bump them out of chain domain.
+    ///
+    /// @param nonce the public key for the nonce account
+    // This is not available on Solana v1.9.15
+    function upgrade_nonce_account(address nonce) internal view {
+        AccountMeta[1] meta = [
+            AccountMeta({pubkey: nonce, is_signer: false, is_writable: true})
+        ];
+
+        bytes bincode = abi.encode(uint32(Instruction.UpgradeNonceAccount));
+        systemAddress.call{accounts: meta}(bincode);
+    }
+}

+ 71 - 0
tokens/transfer-tokens/solang/solidity/transfer-tokens.sol

@@ -0,0 +1,71 @@
+
+import "./spl_token.sol";
+import "./mpl_metadata.sol";
+
+@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC")
+contract transfer_tokens {
+
+    @payer(payer)
+    constructor(address payer) {}
+
+    function createTokenMint(
+        address payer, // payer account
+        address mint, // mint account to be created
+        address mintAuthority, // mint authority for the mint account
+        address freezeAuthority, // freeze authority for the mint account
+        address metadata, // metadata account to be created
+        uint8 decimals, // decimals for the mint account
+        string name, // name for the metadata account
+        string symbol, // symbol for the metadata account
+        string uri // uri for the metadata account
+    ) public view {
+        // Invoke System Program to create a new account for the mint account and,
+        // Invoke Token Program to initialize the mint account
+        // Set mint authority, freeze authority, and decimals for the mint account
+        SplToken.create_mint(
+            payer,            // payer account
+            mint,            // mint account
+            mintAuthority,   // mint authority
+            freezeAuthority, // freeze authority
+            decimals         // decimals
+        );
+
+        // Invoke Metadata Program to create a new account for the metadata account
+        MplMetadata.create_metadata_account(
+            metadata, // metadata account
+            mint,  // mint account
+            mintAuthority, // mint authority
+            payer, // payer
+            payer, // update authority (of the metadata account)
+            name, // name
+            symbol, // symbol
+            uri // uri (off-chain metadata json)
+        );
+    }
+
+    function mintTo(
+        address payer, // payer account
+        address tokenAccount, // token account to create and receive the minted token
+        address mint, // mint account
+        address owner, // token account owner
+        uint64 amount // amount to mint
+    ) public view {
+        // Mint token to the token account
+        SplToken.mint_to(
+            mint, // mint account
+            tokenAccount, // token account
+            payer, // mint authority
+            amount // amount
+        );
+    }
+
+    // Transfer tokens from one token account to another via Cross Program Invocation to Token Program
+    function transferTokens(
+        address from, // token account to transfer from
+        address to, // token account to transfer to
+        uint64 amount // amount to transfer
+    ) public view {
+        SplToken.TokenAccountData from_data = SplToken.get_token_account_data(from);
+        SplToken.transfer(from, to, from_data.owner, amount);
+    }
+}

+ 173 - 0
tokens/transfer-tokens/solang/tests/transfer-tokens.ts

@@ -0,0 +1,173 @@
+import * as anchor from "@coral-xyz/anchor"
+import { Program } from "@coral-xyz/anchor"
+import { TransferTokens } from "../target/types/transfer_tokens"
+import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js"
+import { Metaplex } from "@metaplex-foundation/js"
+import {
+  ASSOCIATED_TOKEN_PROGRAM_ID,
+  getAssociatedTokenAddressSync,
+  getOrCreateAssociatedTokenAccount,
+  TOKEN_PROGRAM_ID,
+} from "@solana/spl-token"
+
+describe("Transfer Tokens", () => {
+  // Configure the client to use the local cluster.
+  const provider = anchor.AnchorProvider.env()
+  anchor.setProvider(provider)
+
+  const dataAccount = anchor.web3.Keypair.generate()
+  const mintKeypair = anchor.web3.Keypair.generate()
+  const wallet = provider.wallet as anchor.Wallet
+  const connection = provider.connection
+
+  const program = anchor.workspace.TransferTokens as Program<TransferTokens>
+
+  const nftTitle = "Homer NFT"
+  const nftSymbol = "HOMR"
+  const nftUri =
+    "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json"
+
+  it("Is initialized!", async () => {
+    // Add your test here.
+    const tx = await program.methods
+      .new(wallet.publicKey)
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .signers([dataAccount])
+      .rpc()
+    console.log("Your transaction signature", tx)
+  })
+
+  it("Create an SPL Token!", async () => {
+    const metaplex = Metaplex.make(connection)
+    const metadataAddress = await metaplex
+      .nfts()
+      .pdas()
+      .metadata({ mint: mintKeypair.publicKey })
+
+    // Add your test here.
+    const tx = await program.methods
+      .createTokenMint(
+        wallet.publicKey, // payer
+        mintKeypair.publicKey, // mint
+        wallet.publicKey, // mint authority
+        wallet.publicKey, // freeze authority
+        metadataAddress, // metadata address
+        9, // 0 decimals for NFT
+        nftTitle, // NFT name
+        nftSymbol, // NFT symbol
+        nftUri // NFT URI
+      )
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey,
+          isWritable: true,
+          isSigner: true,
+        },
+        { pubkey: mintKeypair.publicKey, isWritable: true, isSigner: true },
+        {
+          pubkey: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
+          isWritable: false,
+          isSigner: false,
+        },
+        { pubkey: metadataAddress, isWritable: true, isSigner: false },
+        { pubkey: SystemProgram.programId, isWritable: false, isSigner: false },
+        { pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false },
+      ])
+      .signers([mintKeypair])
+      .rpc({ skipPreflight: true })
+    console.log("Your transaction signature", tx)
+  })
+
+  it("Mint some tokens to your wallet!", async () => {
+    // Wallet's associated token account address for mint
+    const tokenAccount = await getOrCreateAssociatedTokenAccount(
+      connection,
+      wallet.payer, // payer
+      mintKeypair.publicKey, // mint
+      wallet.publicKey // owner
+    )
+
+    const tx = await program.methods
+      .mintTo(
+        wallet.publicKey, // payer
+        tokenAccount.address, // associated token account address
+        mintKeypair.publicKey, // mint
+        wallet.publicKey, // owner of token account
+        new anchor.BN(150) // amount to mint
+      )
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey,
+          isWritable: true,
+          isSigner: true,
+        },
+        { pubkey: tokenAccount.address, isWritable: true, isSigner: false },
+        { pubkey: mintKeypair.publicKey, isWritable: true, isSigner: false },
+        {
+          pubkey: SystemProgram.programId,
+          isWritable: false,
+          isSigner: false,
+        },
+        { pubkey: TOKEN_PROGRAM_ID, isWritable: false, isSigner: false },
+        {
+          pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
+          isWritable: false,
+          isSigner: false,
+        },
+      ])
+      .rpc({ skipPreflight: true })
+    console.log("Your transaction signature", tx)
+  })
+
+  it("Transfer some tokens to another wallet!", async () => {
+    // Wallet's associated token account address for mint
+    const tokenAccount = await getOrCreateAssociatedTokenAccount(
+      connection,
+      wallet.payer, // payer
+      mintKeypair.publicKey, // mint
+      wallet.publicKey // owner
+    )
+
+    const receipient = anchor.web3.Keypair.generate()
+    const receipientTokenAccount = await getOrCreateAssociatedTokenAccount(
+      connection,
+      wallet.payer, // payer
+      mintKeypair.publicKey, // mint account
+      receipient.publicKey // owner account
+    )
+
+    const tx = await program.methods
+      .transferTokens(
+        tokenAccount.address,
+        receipientTokenAccount.address,
+        new anchor.BN(150)
+      )
+      .accounts({ dataAccount: dataAccount.publicKey })
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey,
+          isWritable: true,
+          isSigner: true,
+        },
+        {
+          pubkey: mintKeypair.publicKey,
+          isWritable: false,
+          isSigner: false,
+        },
+        {
+          pubkey: tokenAccount.address,
+          isWritable: true,
+          isSigner: false,
+        },
+        {
+          pubkey: receipientTokenAccount.address,
+          isWritable: true,
+          isSigner: false,
+        },
+      ])
+      .rpc()
+    console.log("Your transaction signature", tx)
+  })
+})

+ 11 - 0
tokens/transfer-tokens/solang/tsconfig.json

@@ -0,0 +1,11 @@
+{
+            "compilerOptions": {
+              "types": ["mocha", "chai"],
+              "typeRoots": ["./node_modules/@types"],
+              "lib": ["es2015"],
+              "module": "commonjs",
+              "target": "es6",
+              "esModuleInterop": true
+            }
+          }
+