tutorial-3.ts in your examples/tutorial/classic directory for the classic version or examples/tutorial/kit for kit:
Copy
Ask AI
touch tutorial/tutorial-3.ts
Example
- Classic
- Kit
Copy
Ask AI
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
Actions,
createEd25519AuthorityInfo,
fetchSwig,
findSwigPda,
getAddAuthorityInstructions,
getCreateSwigInstruction,
getSignInstructions,
} from "@swig-wallet/classic";
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
createAssociatedTokenAccount,
createMint,
createTransferInstruction,
getAccount,
mintTo,
} from "@solana/spl-token";
import chalk from "chalk";
async function createSwigAccount(connection: Connection, user: Keypair) {
try {
const id = new Uint8Array(32);
crypto.getRandomValues(id);
const swigAddress = findSwigPda(id);
const rootAuthorityInfo = createEd25519AuthorityInfo(user.publicKey);
const rootActions = Actions.set().manageAuthority().get();
const createSwigIx = await getCreateSwigInstruction({
payer: user.publicKey,
id,
actions: rootActions,
authorityInfo: rootAuthorityInfo,
});
const transaction = new Transaction().add(createSwigIx);
const signature = await sendAndConfirmTransaction(connection, transaction, [
user,
]);
console.log(
chalk.green("✓ Swig account created at:"),
chalk.cyan(swigAddress.toBase58()),
);
console.log(chalk.blue("Transaction signature:"), chalk.cyan(signature));
return swigAddress;
} catch (error) {
console.error(
chalk.red("✗ Error creating Swig account:"),
chalk.red(error),
);
throw error;
}
}
async function addNewAuthority(
connection: Connection,
rootUser: Keypair,
newAuthority: Keypair,
swigAddress: PublicKey,
actions: any,
description: string,
) {
try {
const swig = await fetchSwig(connection, swigAddress);
const rootRole = swig.findRolesByEd25519SignerPk(rootUser.publicKey)[0];
if (!rootRole) {
throw new Error("Root role not found for authority");
}
const addAuthorityInstructions = await getAddAuthorityInstructions(
swig,
rootRole.id,
createEd25519AuthorityInfo(newAuthority.publicKey),
actions,
);
const transaction = new Transaction().add(...addAuthorityInstructions);
await sendAndConfirmTransaction(connection, transaction, [rootUser]);
console.log(
chalk.green(`✓ New ${description} authority added:`),
chalk.cyan(newAuthority.publicKey.toBase58()),
);
} catch (error) {
console.error(
chalk.red(`✗ Error adding ${description} authority:`),
chalk.red(error),
);
throw error;
}
}
async function displayTokenBalance(
connection: Connection,
tokenAccount: PublicKey,
label: string,
) {
const account = await getAccount(connection, tokenAccount);
console.log(
chalk.yellow(`${label} token balance:`),
chalk.cyan(account.amount.toString()),
);
}
(async () => {
console.log(
chalk.blue("🚀 Starting tutorial - Token Authority and Transfers"),
);
// Connect to local Solana network
const connection = new Connection("http://localhost:8899", "confirmed");
// Create and fund the root user
const rootUser = Keypair.generate();
console.log(
chalk.green("👤 Root user public key:"),
chalk.cyan(rootUser.publicKey.toBase58()),
);
const airdrop = await connection.requestAirdrop(
rootUser.publicKey,
100 * LAMPORTS_PER_SOL,
);
const blockhash = await connection.getLatestBlockhash();
await connection.confirmTransaction({
signature: airdrop,
blockhash: blockhash.blockhash,
lastValidBlockHeight: blockhash.lastValidBlockHeight,
});
// Create the Swig account
console.log(chalk.yellow("\n📝 Creating Swig account..."));
const swigAddress = await createSwigAccount(connection, rootUser);
// Create token mint
console.log(chalk.yellow("\n💎 Creating token mint..."));
const mintAuthority = Keypair.generate();
const tokenMint = await createMint(
connection,
rootUser,
mintAuthority.publicKey,
null,
0,
);
console.log(
chalk.green("✓ Token mint created:"),
chalk.cyan(tokenMint.toBase58()),
);
// Create token accounts
console.log(chalk.yellow("\n💰 Creating token accounts..."));
const swigTokenAccount = await createAssociatedTokenAccount(
connection,
rootUser,
tokenMint,
swigAddress,
{},
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID,
true,
);
console.log(
chalk.green("✓ Swig token account created:"),
chalk.cyan(swigTokenAccount.toBase58()),
);
const recipientKeypair = Keypair.generate();
const recipientTokenAccount = await createAssociatedTokenAccount(
connection,
rootUser,
tokenMint,
recipientKeypair.publicKey,
);
console.log(
chalk.green("✓ Recipient token account created:"),
chalk.cyan(recipientTokenAccount.toBase58()),
);
// Mint initial tokens to Swig account
console.log(chalk.yellow("\n🏦 Minting tokens to Swig account..."));
await mintTo(
connection,
rootUser,
tokenMint,
swigTokenAccount,
mintAuthority,
10,
);
await displayTokenBalance(connection, swigTokenAccount, "Swig");
await displayTokenBalance(connection, recipientTokenAccount, "Recipient");
// Create token authority with permission to send exactly 10 tokens
const tokenAuthority = Keypair.generate();
console.log(
chalk.green("\n👥 Token authority public key:"),
chalk.cyan(tokenAuthority.publicKey.toBase58()),
);
await connection.requestAirdrop(
tokenAuthority.publicKey,
100 * LAMPORTS_PER_SOL,
);
console.log(chalk.yellow("\n🔑 Adding token authority..."));
const tokenActions = Actions.set()
.tokenLimit({ mint: tokenMint, amount: BigInt(10) })
.get();
await addNewAuthority(
connection,
rootUser,
tokenAuthority,
swigAddress,
tokenActions,
"token",
);
// Add some delay to ensure the authority is added
await new Promise((resolve) => setTimeout(resolve, 2000));
// First transfer - should succeed
console.log(chalk.yellow("\n💸 Attempting first transfer of 10 tokens..."));
try {
const swig = await fetchSwig(connection, swigAddress);
const tokenRole = swig.findRolesByEd25519SignerPk(
tokenAuthority.publicKey,
)[0];
const transferIx = createTransferInstruction(
swigTokenAccount,
recipientTokenAccount,
swigAddress,
BigInt(10),
);
const signedTransfer = await signInstruction(
tokenRole,
tokenAuthority.publicKey,
[transferIx],
);
const transaction = new Transaction().add(signedTransfer);
await sendAndConfirmTransaction(connection, transaction, [tokenAuthority]);
console.log(chalk.green("✓ First transfer successful!"));
await displayTokenBalance(connection, swigTokenAccount, "Swig");
await displayTokenBalance(connection, recipientTokenAccount, "Recipient");
} catch (error) {
console.error(chalk.red("✗ First transfer failed:"), chalk.red(error));
}
// Second transfer - should fail
console.log(chalk.yellow("\n💸 Attempting second transfer (should fail)..."));
try {
const swig = await fetchSwig(connection, swigAddress);
const tokenRole = swig.findRolesByEd25519SignerPk(
tokenAuthority.publicKey,
)[0];
const transferIx = createTransferInstruction(
swigTokenAccount,
recipientTokenAccount,
swigAddress,
BigInt(10),
);
const signedTransfer = await signInstruction(
tokenRole,
tokenAuthority.publicKey,
[transferIx],
);
const transaction = new Transaction().add(signedTransfer);
await sendAndConfirmTransaction(connection, transaction, [tokenAuthority], {
skipPreflight: true,
});
console.error(chalk.red("✗ Second transfer unexpectedly succeeded!"));
} catch {
console.log(
chalk.green("✓ Second transfer failed as expected:"),
"Authority has no remaining token allowance",
);
}
await displayTokenBalance(connection, swigTokenAccount, "Final Swig");
await displayTokenBalance(
connection,
recipientTokenAccount,
"Final Recipient",
);
console.log(chalk.green("\n✨ Tutorial completed successfully!"));
console.log(
chalk.yellow("🔍 Check out your transaction on Solana Explorer:"),
);
console.log(
chalk.cyan(
`https://explorer.solana.com/address/${swigAddress}?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899`,
),
);
})();
Copy
Ask AI
import {
createSolanaRpc,
createSolanaRpcSubscriptions,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
signTransactionMessageWithSigners,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
addSignersToTransactionMessage,
type IInstruction,
type KeyPairSigner,
} from '@solana/kit';
import {
Actions,
createEd25519AuthorityInfo,
fetchSwig,
findSwigPda,
getAddAuthorityInstructions,
getCreateSwigInstruction,
getSignInstructions,
} from '@swig-wallet/kit';
import {
TOKEN_2022_PROGRAM_ADDRESS,
findAssociatedTokenPda,
getCreateAssociatedTokenInstructionAsync,
getInitializeMintInstruction,
getMintSize,
getMintToCheckedInstruction,
getTransferCheckedInstruction,
} from '@solana-program/token-2022';
import { getCreateAccountInstruction } from '@solana-program/system';
import chalk from 'chalk';
import { sleepSync } from 'bun';
function randomBytes(length: number): Uint8Array {
const arr = new Uint8Array(length);
crypto.getRandomValues(arr);
return arr;
}
async function sendTransaction(
instructions: IInstruction[],
payer: KeyPairSigner,
signers: KeyPairSigner[] = []
): Promise<string> {
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const txMsg = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(payer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx),
(tx) => addSignersToTransactionMessage(signers, tx)
);
const signedTx = await signTransactionMessageWithSigners(txMsg);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTx, {
commitment: 'confirmed',
});
return getSignatureFromTransaction(signedTx).toString();
}
const rpc = createSolanaRpc('http://localhost:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://localhost:8900');
(async () => {
console.log(chalk.blue('🚀 Starting tutorial - Token Authority and Transfers (Kit Style)'));
const rootUser = await generateKeyPairSigner();
await rpc.requestAirdrop(rootUser.address, lamports(100n * 1_000_000_000n)).send();
sleepSync(2000);
console.log(chalk.green('👤 Root user public key:'), chalk.cyan(rootUser.address.toString()));
const id = randomBytes(32);
const swigAddress = await findSwigPda(id);
const rootAuthorityInfo = createEd25519AuthorityInfo(rootUser.address);
const rootActions = Actions.set().manageAuthority().get();
const createSwigIx = await getCreateSwigInstruction({
payer: rootUser.address,
id,
actions: rootActions,
authorityInfo: rootAuthorityInfo,
});
await sendTransaction([createSwigIx], rootUser);
console.log(chalk.green('✓ Swig account created at:'), chalk.cyan(swigAddress.toString()));
await rpc.requestAirdrop(swigAddress, lamports(1n)).send();
sleepSync(2000);
console.log(chalk.green('✓ Airdropped 1 SOL to Swig account'));
const mint = await generateKeyPairSigner();
const mintAuthority = rootUser;
const decimals = 0;
const rent = await rpc.getMinimumBalanceForRentExemption(BigInt(getMintSize())).send();
const createMintAccountIx = getCreateAccountInstruction({
payer: mintAuthority,
newAccount: mint,
lamports: rent,
space: BigInt(getMintSize()),
programAddress: TOKEN_2022_PROGRAM_ADDRESS,
});
const initMintIx = getInitializeMintInstruction({
mint: mint.address,
decimals,
mintAuthority: mintAuthority.address,
});
const [swigATA] = await findAssociatedTokenPda({
mint: mint.address,
owner: swigAddress,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
});
const createSwigATAIx = await getCreateAssociatedTokenInstructionAsync({
payer: mintAuthority,
mint: mint.address,
owner: swigAddress,
});
const mintToSwigIx = await getMintToCheckedInstruction({
mint: mint.address,
token: swigATA,
mintAuthority,
amount: 10n,
decimals,
});
await sendTransaction(
[createMintAccountIx, initMintIx, createSwigATAIx, mintToSwigIx],
mintAuthority,
[mint]
);
console.log(chalk.green('✓ Token mint and Swig ATA created and funded'));
const recipient = await generateKeyPairSigner();
const [recipientATA] = await findAssociatedTokenPda({
mint: mint.address,
owner: recipient.address,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
});
const createRecipientATAIx = await getCreateAssociatedTokenInstructionAsync({
payer: rootUser,
mint: mint.address,
owner: recipient.address,
});
await sendTransaction([createRecipientATAIx], rootUser);
console.log(chalk.green('✓ Recipient ATA created'));
const tokenAuthority = await generateKeyPairSigner();
await rpc.requestAirdrop(tokenAuthority.address, lamports(1n)).send();
sleepSync(2000);
const tokenActions = Actions.set()
.tokenLimit({ mint: mint.address, amount: 10n })
.get();
const swig = await fetchSwig(rpc, swigAddress);
const rootRole = swig.findRolesByEd25519SignerPk(rootUser.address)[0];
const addAuthorityIxs = await getAddAuthorityInstructions(
swig,
rootRole.id,
createEd25519AuthorityInfo(tokenAuthority.address),
tokenActions
);
await sendTransaction(addAuthorityIxs, rootUser);
console.log(chalk.green('✓ Token authority added'));
//check how much sol the swig has
const swigBalance = await rpc.getBalance(swigAddress).send();
console.log(chalk.green('✓ Swig account balance:'), chalk.cyan(swigBalance.value.toString()));
// Add balance checks for Swig ATA and Recipient ATA
const swigATABalance = await rpc.getTokenAccountBalance(swigATA).send();
console.log(chalk.green('✓ Swig ATA balance:'), chalk.cyan(swigATABalance.value.amount));
const recipientATABalance = await rpc.getTokenAccountBalance(recipientATA).send();
console.log(chalk.green('✓ Recipient ATA balance:'), chalk.cyan(recipientATABalance.value.amount));
// ❌ Second transfer (should fail)
try {
const swigLatest = await fetchSwig(rpc, swigAddress);
const role = swigLatest.findRolesByEd25519SignerPk(tokenAuthority.address)[0];
const transferAgain = getTransferCheckedInstruction({
source: swigATA,
destination: recipientATA,
mint: mint.address,
amount: 10n,
decimals,
authority: swigAddress,
});
const signed = await getSignInstructions(swigLatest, role.id, [transferAgain]);
await sendTransaction(signed, tokenAuthority);
console.error(chalk.red('✗ Second transfer unexpectedly succeeded!'));
} catch {
console.log(chalk.green('✓ Second transfer failed as expected (no allowance left)'));
}
console.log(chalk.green('\n✨ Tutorial completed successfully!'));
console.log(
chalk.yellow('🔍 View your Swig on Explorer:'),
chalk.cyan(
`https://explorer.solana.com/address/${swigAddress}?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899`
)
);
})();
1. Fetch the Swig Account
- Classic
- Kit
Copy
Ask AI
const swig = await fetchSwig(connection, swigAddress);
Copy
Ask AI
const swig = await fetchSwig(rpc, swigAddress);
2. Find the Appropriate Role
- Classic
- Kit
Copy
Ask AI
const tokenRole = swig.findRolesByEd25519SignerPk(tokenAuthority.publicKey)[0];
Copy
Ask AI
const role = swig.findRolesByEd25519SignerPk(tokenAuthority.address)[0];
💡 A single authority can have multiple roles. Always make sure you’re using the correct role for your intended action.
3. Create the Instruction
- Classic
- Kit
Copy
Ask AI
const transferIx = createTransferInstruction(
swigTokenAccount,
recipientTokenAccount,
swigAddress,
BigInt(10),
);
Copy
Ask AI
const transferAgain = getTransferCheckedInstruction({
source: swigATA,
destination: recipientATA,
mint: mint.address,
amount: 10n,
decimals,
authority: swigAddress,
});
4. Sign the Instruction
- Classic
- Kit
Copy
Ask AI
const signedTransferInstructions = await getSignInstructions(
swig,
tokenRole.id,
[transferIx],
);
Copy
Ask AI
const signed = await getSignInstructions(swigLatest, role.id, [transferAgain]);
getSignInstructions function:
- Verifies that the role has permission to sign this type of instruction
- Creates the necessary signing instructions
- Wraps the original instruction with Swig’s signing logic
🔒 Swig validates both the authority type and action permissions at this stage
5. Build and Send the Transaction
- Classic
- Kit
Copy
Ask AI
const transaction = new Transaction().add(...signedTransferInstructions);
await sendAndConfirmTransaction(connection, transaction, [tokenAuthority]);
Copy
Ask AI
await sendTransaction(signed, tokenAuthority);
Understanding Signature Verification
When the transaction reaches the Solana network, the Swig program will:- Extract the original instruction
- Verify the authority’s signature
- Check that the role’s permissions allow this action
- Execute the instruction if all checks pass
🧠 This multi-step verification ensures that only authorized actions are performed
Ready to try it?
Make sure you have:- Completed the previous tutorials
- Have a running validator (
bun start-validator) - Created a Swig account with appropriate authorities
Copy
Ask AI
bun run ./tutorial/tutorial-3.ts

