-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
minting tokens and various renames (#33)
* refactor: renamed * feat: added mint tokens instructions * refactor: changed input param name * docs: example text * feat: create transaction * refactor: rename functions * refactor: checked function * fix: checked addresses * chore: changeset * feat: added tokens example * fix: ata bug and cu * feat: improved example * docs: typos
- Loading branch information
1 parent
60d00a9
commit be3110d
Showing
12 changed files
with
746 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"gill": minor | ||
--- | ||
|
||
added mint token functions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import { | ||
getExplorerLink, | ||
createSolanaClient, | ||
SolanaClusterMoniker, | ||
getSignatureFromTransaction, | ||
signTransactionMessageWithSigners, | ||
generateKeyPairSigner, | ||
address, | ||
} from "gill"; | ||
import { loadKeypairSignerFromFile } from "gill/node"; | ||
import { buildCreateTokenTransaction, buildMintTokensTransaction } from "gill/programs"; | ||
import { TOKEN_2022_PROGRAM_ADDRESS } from "gill/programs/token22"; | ||
|
||
/** Turn on debug mode */ | ||
global.__GILL_DEBUG_LEVEL__ = "debug"; | ||
|
||
/** | ||
* Load a keypair signer from the local filesystem | ||
* | ||
* This defaults to the file path used by the Solana CLI: `~/.config/solana/id.json` | ||
*/ | ||
const signer = await loadKeypairSignerFromFile(); | ||
console.log("address:", signer.address); | ||
|
||
/** | ||
* Declare what Solana network cluster we want our code to interact with | ||
*/ | ||
const cluster: SolanaClusterMoniker = "devnet"; | ||
|
||
/** | ||
* Create a client connection to the Solana blockchain | ||
* | ||
* Note: `urlOrMoniker` can be either a Solana network moniker or a full URL of your RPC provider | ||
*/ | ||
const { rpc, sendAndConfirmTransaction } = createSolanaClient({ | ||
urlOrMoniker: cluster, | ||
}); | ||
|
||
/** | ||
* Declare our token mint and desired token program | ||
*/ | ||
const tokenProgram = TOKEN_2022_PROGRAM_ADDRESS; | ||
const mint = await generateKeyPairSigner(); | ||
|
||
/** | ||
* Get the latest blockhash (aka transaction lifetime). This acts as a recent timestamp | ||
* for the blockchain to key on when processing your transaction | ||
* | ||
* Pro tip: only request this value just before you are going to use it your code | ||
*/ | ||
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); | ||
console.log("latestBlockhash:", latestBlockhash); | ||
|
||
/** | ||
* Create a transaction that will create a new token (with metadata) | ||
* | ||
* - this will use the original SPL token by default (`TOKEN_PROGRAM_ADDRESS`) | ||
*/ | ||
const createTokenTx = await buildCreateTokenTransaction({ | ||
mint, | ||
latestBlockhash, | ||
payer: signer, | ||
// mintAuthority, // default=same as the `payer` | ||
metadata: { | ||
isMutable: true, // if the `updateAuthority` can change this metadata in the future | ||
name: "Only Possible On Solana", | ||
symbol: "OPOS", | ||
uri: "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/Climate/metadata.json", | ||
}, | ||
decimals: 2, // default=9, | ||
tokenProgram, //default=TOKEN_PROGRAM_ADDRESS | ||
}); | ||
|
||
/** | ||
* Sign the transaction with the provided `signer` from when it was created | ||
*/ | ||
let signedTransaction = await signTransactionMessageWithSigners(createTokenTx); | ||
console.log("signedTransaction:"); | ||
console.log(signedTransaction); | ||
|
||
/** | ||
* Get the transaction signature after it has been signed by at least one signer | ||
*/ | ||
let signature = getSignatureFromTransaction(signedTransaction); | ||
|
||
/** | ||
* Log the Solana Explorer link for the transaction we are about to send | ||
*/ | ||
console.log("\nExplorer Link (for creating the mint):"); | ||
console.log( | ||
getExplorerLink({ | ||
cluster, | ||
transaction: signature, | ||
}), | ||
); | ||
|
||
/** | ||
* Actually send the transaction to the blockchain and confirm it | ||
*/ | ||
await sendAndConfirmTransaction(signedTransaction); | ||
|
||
/** | ||
* Declare the wallet address that we want to mint the tokens to | ||
*/ | ||
const destination = address("nicktrLHhYzLmoVbuZQzHUTicd2sfP571orwo9jfc8c"); | ||
|
||
/** | ||
* Create a transaction that mints new tokens to the `destination` wallet address | ||
* (raising the token's overall supply) | ||
* | ||
* - be sure to use the correct token program that the `mint` was created with | ||
* - ensure the `mintAuthority` is the correct signer in order to actually mint new tokens | ||
*/ | ||
const mintTokensTx = await buildMintTokensTransaction({ | ||
payer: signer, | ||
latestBlockhash, | ||
mint, | ||
mintAuthority: signer, | ||
amount: 1000, // note: be sure to consider the mint's `decimals` value | ||
// if decimals=2 => this will mint 10.00 tokens | ||
// if decimals=4 => this will mint 0.100 tokens | ||
destination, | ||
tokenProgram, // default=TOKEN_PROGRAM_ADDRESS | ||
}); | ||
|
||
console.log("Transaction to mint tokens:"); | ||
console.log(mintTokensTx); | ||
|
||
/** | ||
* Sign the transaction with the provided `signer` from when it was created | ||
*/ | ||
signedTransaction = await signTransactionMessageWithSigners(mintTokensTx); | ||
signature = getSignatureFromTransaction(signedTransaction); | ||
|
||
console.log("\nExplorer Link (for minting the tokens to the destination wallet):"); | ||
console.log( | ||
getExplorerLink({ | ||
cluster, | ||
transaction: signature, | ||
}), | ||
); | ||
|
||
await sendAndConfirmTransaction(signedTransaction); | ||
|
||
console.log("Complete."); |
177 changes: 177 additions & 0 deletions
177
packages/gill/src/__tests__/mint-tokens-instructions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import { | ||
getCreateAssociatedTokenIdempotentInstruction, | ||
getMintToInstruction, | ||
TOKEN_2022_PROGRAM_ADDRESS, | ||
} from "@solana-program/token-2022"; | ||
import { getMintTokensInstructions, GetMintTokensInstructionsArgs } from "../programs"; | ||
import type { KeyPairSigner } from "@solana/signers"; | ||
import type { Address } from "@solana/addresses"; | ||
import { TOKEN_PROGRAM_ADDRESS } from "@solana-program/token"; | ||
|
||
// Mock the imported functions | ||
jest.mock("@solana-program/token-2022", () => ({ | ||
// preserve all real implementations to only change the desired ones | ||
...jest.requireActual("@solana-program/token-2022"), | ||
|
||
getCreateAssociatedTokenIdempotentInstruction: jest.fn(), | ||
getMintToInstruction: jest.fn(), | ||
})); | ||
|
||
describe("getMintTokensInstructions", () => { | ||
const mockPayer = { address: "payer" } as KeyPairSigner; | ||
const mockMint = { address: "mint" } as KeyPairSigner; | ||
const mockMintAuthority = { address: "mintAuthority" } as KeyPairSigner; | ||
const mockDestination = { address: "destination" } as KeyPairSigner; | ||
|
||
const mockAta = "mockAtaAddress" as Address; | ||
const mockAmount = BigInt(1000); | ||
|
||
beforeEach(() => { | ||
(getCreateAssociatedTokenIdempotentInstruction as jest.Mock).mockReturnValue({ | ||
instruction: "mockCreateAtaInstruction", | ||
}); | ||
|
||
(getMintToInstruction as jest.Mock).mockReturnValue({ | ||
instruction: "mockMintToInstruction", | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it("should create instructions with default token program", () => { | ||
const args: GetMintTokensInstructionsArgs = { | ||
payer: mockPayer, | ||
mint: mockMint.address, | ||
mintAuthority: mockMintAuthority, | ||
destination: mockDestination.address, | ||
ata: mockAta, | ||
amount: mockAmount, | ||
}; | ||
|
||
const instructions = getMintTokensInstructions(args); | ||
|
||
expect(instructions).toHaveLength(2); | ||
|
||
expect(getCreateAssociatedTokenIdempotentInstruction).toHaveBeenCalledWith({ | ||
owner: mockDestination.address, | ||
mint: mockMint.address, | ||
ata: mockAta, | ||
payer: mockPayer, | ||
tokenProgram: TOKEN_PROGRAM_ADDRESS, | ||
}); | ||
|
||
expect(getMintToInstruction).toHaveBeenCalledWith( | ||
{ | ||
mint: mockMint.address, | ||
mintAuthority: mockMintAuthority, | ||
token: mockAta, | ||
amount: mockAmount, | ||
}, | ||
{ | ||
programAddress: TOKEN_PROGRAM_ADDRESS, | ||
}, | ||
); | ||
}); | ||
|
||
it("should create instructions with Token-2022 program", () => { | ||
const args: GetMintTokensInstructionsArgs = { | ||
payer: mockPayer, | ||
mint: mockMint.address, | ||
mintAuthority: mockMintAuthority, | ||
destination: mockDestination.address, | ||
ata: mockAta, | ||
amount: mockAmount, | ||
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS, | ||
}; | ||
|
||
const instructions = getMintTokensInstructions(args); | ||
|
||
expect(instructions).toHaveLength(2); | ||
expect(getCreateAssociatedTokenIdempotentInstruction).toHaveBeenCalledWith({ | ||
owner: mockDestination.address, | ||
mint: mockMint.address, | ||
ata: mockAta, | ||
payer: mockPayer, | ||
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS, | ||
}); | ||
}); | ||
|
||
it("should accept Address type for mint, mintAuthority, and destination", () => { | ||
const args: GetMintTokensInstructionsArgs = { | ||
payer: mockPayer, | ||
mint: "mintAddress" as Address, | ||
mintAuthority: "mintAuthorityAddress" as Address, | ||
destination: "ownerAddress" as Address, | ||
ata: mockAta, | ||
amount: mockAmount, | ||
}; | ||
|
||
const instructions = getMintTokensInstructions(args); | ||
|
||
expect(instructions).toHaveLength(2); | ||
|
||
expect(getCreateAssociatedTokenIdempotentInstruction).toHaveBeenCalledWith({ | ||
owner: args.destination, | ||
mint: args.mint, | ||
ata: mockAta, | ||
payer: mockPayer, | ||
tokenProgram: TOKEN_PROGRAM_ADDRESS, | ||
}); | ||
|
||
expect(getMintToInstruction).toHaveBeenCalledWith( | ||
{ | ||
mint: "mintAddress", | ||
mintAuthority: "mintAuthorityAddress", | ||
token: mockAta, | ||
amount: mockAmount, | ||
}, | ||
{ | ||
programAddress: TOKEN_PROGRAM_ADDRESS, | ||
}, | ||
); | ||
}); | ||
|
||
it("should accept number type for amount", () => { | ||
const args: GetMintTokensInstructionsArgs = { | ||
payer: mockPayer, | ||
mint: mockMint.address, | ||
mintAuthority: mockMintAuthority, | ||
destination: mockDestination.address, | ||
ata: mockAta, | ||
amount: 1000, | ||
}; | ||
|
||
const instructions = getMintTokensInstructions(args); | ||
|
||
expect(instructions).toHaveLength(2); | ||
expect(getMintToInstruction).toHaveBeenCalledWith( | ||
{ | ||
mint: mockMint.address, | ||
mintAuthority: mockMintAuthority, | ||
token: mockAta, | ||
amount: 1000, | ||
}, | ||
{ | ||
programAddress: TOKEN_PROGRAM_ADDRESS, | ||
}, | ||
); | ||
}); | ||
|
||
it("should throw error for unsupported token program", () => { | ||
const args: GetMintTokensInstructionsArgs = { | ||
payer: mockPayer, | ||
mint: mockMint.address, | ||
mintAuthority: mockMintAuthority, | ||
destination: mockDestination.address, | ||
ata: mockAta, | ||
amount: mockAmount, | ||
tokenProgram: "UnsupportedProgramId" as Address, | ||
}; | ||
|
||
expect(() => getMintTokensInstructions(args)).toThrow( | ||
"Unsupported token program. Try 'TOKEN_PROGRAM_ADDRESS' or 'TOKEN_2022_PROGRAM_ADDRESS'", | ||
); | ||
}); | ||
}); |
Oops, something went wrong.