Skip to content

Commit

Permalink
feat: transfer token builders (#40)
Browse files Browse the repository at this point in the history
* feat: transfer tokens

* chore: ignore

* feat: added transfer to token example

* test: added tests

* docs: docs

* chore: changeset

* fix: typos
  • Loading branch information
nickfrosty authored Feb 23, 2025
1 parent 64d138a commit 9ae5ee8
Show file tree
Hide file tree
Showing 11 changed files with 756 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/pink-spies-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"gill": minor
---

added transfer token transaction/instruction builders
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ yarn-error.log*

# `solana-test-validator`
.agave/
test-ledger/
.cache
test-ledger

# GitHub Pages deploy directory
.ghpages-deploy
Expand All @@ -32,4 +33,4 @@ test-ledger/
docs/

.env
.env.local
.env.local
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ You can find [transaction builders](#transaction-builders) for common tasks, inc

- [Creating a token with metadata](#create-a-token-with-metadata)
- [Minting tokens to a destination wallet](#mint-tokens-to-a-destination-wallet)
- [Transfer tokens to a destination wallet](#transfer-tokens-to-a-destination-wallet)

For troubleshooting and debugging your Solana transactions, see [Debug mode](#debug-mode) below.

Expand Down Expand Up @@ -604,6 +605,45 @@ const mintTokensTx = await buildMintTokensTransaction({
});
```

### Transfer tokens to a destination wallet

Build a transaction that transfers tokens to the `destination` wallet address from the `source` (aka
from `sourceAta` to `destinationAta`).

- ensure you set the correct `tokenProgram` used by the `mint` itself
- if the `destination` owner does not have an associated token account (ata) created for the `mint`,
one will be auto-created for them
- ensure you take into account the `decimals` for the `mint` when setting the `amount` in this
transaction

Related instruction builder: `getTransferTokensInstructions`

```typescript
import { buildTransferTokensTransaction } from "gill/programs/token";

const transferTokensTx = await buildTransferTokensTransaction({
feePayer: signer,
latestBlockhash,
mint,
authority: signer,
// sourceAta, // default=derived from the `authority`.
/**
* if the `sourceAta` is not derived from the `authority` (like for multi-sig wallets),
* manually derive with `getAssociatedTokenAccountAddress()`
*/
amount: 900, // note: be sure to consider the mint's `decimals` value
// if decimals=2 => this will transfer 9.00 tokens
// if decimals=4 => this will transfer 0.090 tokens
destination: address(...),
// use the correct token program for the `mint`
tokenProgram, // default=TOKEN_PROGRAM_ADDRESS
// default cu limit set to be optimized, but can be overriden here
// computeUnitLimit?: number,
// obtain from your favorite priority fee api
// computeUnitPrice?: number, // no default set
});
```

## Debug mode

Within `gill`, you can enable "debug mode" to automatically log additional information that will be
Expand Down
122 changes: 117 additions & 5 deletions examples/get-started/src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { loadKeypairSignerFromFile } from "gill/node";
import {
buildCreateTokenTransaction,
buildMintTokensTransaction,
buildTransferTokensTransaction,
getAssociatedTokenAccountAddress,
TOKEN_2022_PROGRAM_ADDRESS,
} from "gill/programs/token";

Expand Down Expand Up @@ -57,6 +59,7 @@ const { rpc, sendAndConfirmTransaction } = createSolanaClient({
* Declare our token mint and desired token program
*/
const tokenProgram = TOKEN_2022_PROGRAM_ADDRESS;
// const tokenProgram = TOKEN_PROGRAM_ADDRESS;
const mint = await generateKeyPairSigner();

/**
Expand Down Expand Up @@ -124,7 +127,9 @@ await sendAndConfirmTransaction(signedTransaction);
/**
* Declare the wallet address that we want to mint the tokens to
*/
const destination = address("nicktrLHhYzLmoVbuZQzHUTicd2sfP571orwo9jfc8c");
const mintToDestination = address(
"nicktrLHhYzLmoVbuZQzHUTicd2sfP571orwo9jfc8c",
);

/**
* Create a transaction that mints new tokens to the `destination` wallet address
Expand All @@ -138,10 +143,10 @@ const mintTokensTx = await buildMintTokensTransaction({
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,
amount: 2000, // note: be sure to consider the mint's `decimals` value
// if decimals=2 => this will mint 20.00 tokens
// if decimals=4 => this will mint 0.200 tokens
destination: mintToDestination,
// use the correct token program for the `mint`
tokenProgram, // default=TOKEN_PROGRAM_ADDRESS
// default cu limit set to be optimized, but can be overriden here
Expand Down Expand Up @@ -171,4 +176,111 @@ console.log(

await sendAndConfirmTransaction(signedTransaction);

/**
* Get the token balance of a wallet's associated token account (ata)
*
* In this case, we are checking our original wallet's ata
*/
let { value: postMintBalance } = await rpc
.getTokenAccountBalance(
await getAssociatedTokenAccountAddress(
mint,
mintToDestination,
tokenProgram,
),
)
.send();

console.log(
"token balance after minting to 'mintToDestination':",
postMintBalance,
);

/**
* We will generate a new, random wallet in order to show that this wallet's ata
* will be automatically created during the token transfer transaction
*/
const transferToDestination = await generateKeyPairSigner();
console.log("transfer to destination:", transferToDestination.address);

/**
* The `authority` address that can authorize the token transfer.
* This is usually the user's wallet or the delegated authority
*/
const authority = address("7sZoCrE3cGgEpNgxcPnGffDeWfTewKnk6wWdLxmYA7Cy");

/**
* 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 transferTokensTx = await buildTransferTokensTransaction({
feePayer: signer,
latestBlockhash,
mint,
authority,
amount: 900, // note: be sure to consider the mint's `decimals` value
// if decimals=2 => this will mint 9.00 tokens
// if decimals=4 => this will mint 0.090 tokens
destination: transferToDestination,
// use the correct token program for the `mint`
tokenProgram, // default=TOKEN_PROGRAM_ADDRESS
// default cu limit set to be optimized, but can be overriden here
// computeUnitLimit?: number,
// obtain from your favorite priority fee api
// computeUnitPrice?: number, // no default set
});

/**
* Sign the transaction with the provided `signer` from when it was created
*/
signedTransaction = await signTransactionMessageWithSigners(transferTokensTx);
signature = getSignatureFromTransaction(signedTransaction);

console.log("\nExplorer Link (for transferring tokens to the new wallet):");
console.log(
getExplorerLink({
cluster,
transaction: signature,
}),
);

// process.exit();

await sendAndConfirmTransaction(signedTransaction);

/**
* Now that we have transferred tokens FROM the source (in this example, the `signer`),
* we can check this wallets current balance by deriving the ATA
*/

const sourceAta = await getAssociatedTokenAccountAddress(
mint,
authority,
tokenProgram,
);
const { value: updatedBalance } = await rpc
.getTokenAccountBalance(sourceAta)
.send();

console.log("new token balance for original source/authority", updatedBalance);

/**
* We can also check the destination wallet's balance,
* including that their ATA was created automatically!
*/
const destinationAta = await getAssociatedTokenAccountAddress(
mint,
transferToDestination,
tokenProgram,
);

const { value: destinationWalletBalance } = await rpc
.getTokenAccountBalance(destinationAta)
.send();

console.log("token balance for destination wallet:", destinationWalletBalance);

console.log("Complete.");
39 changes: 39 additions & 0 deletions packages/gill/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ You can find [transaction builders](#transaction-builders) for common tasks, inc

- [Creating a token with metadata](#create-a-token-with-metadata)
- [Minting tokens to a destination wallet](#mint-tokens-to-a-destination-wallet)
- [Transfer tokens to a destination wallet](#transfer-tokens-to-a-destination-wallet)

For troubleshooting and debugging your Solana transactions, see [Debug mode](#debug-mode) below.

Expand Down Expand Up @@ -604,6 +605,44 @@ const mintTokensTx = await buildMintTokensTransaction({
});
```

### Transfer tokens to a destination wallet

Build a transaction that transfers tokens to the `destination` wallet address form the `source`

- ensure you set the correct `tokenProgram` used by the `mint` itself
- if the `destination` owner does not have an associated token account (ata) created for the `mint`,
one will be auto-created for them
- ensure you take into account the `decimals` for the `mint` when setting the `amount` in this
transaction

Related instruction builder: `getTransferTokensInstructions`

```typescript
import { buildTransferTokensTransaction } from "gill/programs/token";

const transferTokensTx = await buildTransferTokensTransaction({
feePayer: signer,
latestBlockhash,
mint,
authority: signer,
// sourceAta, // default=derived from the `authority`.
/**
* if the `sourceAta` is not derived from the `authority` (like for multi-sig wallets),
* manually derive with `getAssociatedTokenAccountAddress()`
*/
amount: 900, // note: be sure to consider the mint's `decimals` value
// if decimals=2 => this will transfer 9.00 tokens
// if decimals=4 => this will transfer 0.090 tokens
destination: address(...),
// use the correct token program for the `mint`
tokenProgram, // default=TOKEN_PROGRAM_ADDRESS
// default cu limit set to be optimized, but can be overriden here
// computeUnitLimit?: number,
// obtain from your favorite priority fee api
// computeUnitPrice?: number, // no default set
});
```

## Debug mode

Within `gill`, you can enable "debug mode" to automatically log additional information that will be
Expand Down
Loading

0 comments on commit 9ae5ee8

Please sign in to comment.