Prerequisites
Before starting this tutorial:- Make sure you’re in the
examples/tutorial/classicdirectory for theclassicversion andexamples/tutorial/kitforkit - Ensure your local validator is running with:
bun start-validator
Quick Review
In Create a Wallet, you created a basic Swig wallet. This page builds on that by adding multiple authorities with different permissions.mkdir tutorial && touch tutorial-2.ts
addNewAuthority helper.
Example
- Classic
- Kit
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
Actions,
createEd25519AuthorityInfo,
fetchSwig,
findSwigPda,
getAddAuthorityInstructions,
getCreateSwigInstruction,
} from "@swig-wallet/classic";
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 () => {
console.log(chalk.blue('🚀 Starting tutorial - Adding Multiple Authorities'));
// Connect to local Solana network
const connection = new Connection("http://localhost:8899", "confirmed");
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,
});
console.log(chalk.yellow('\n📝 Creating Swig account...'));
const swigAddress = await createSwigAccount(connection, rootUser);
const spendingAuthority = Keypair.generate();
console.log(
chalk.green("\n👥 Spending authority public key:"),
chalk.cyan(spendingAuthority.publicKey.toBase58()),
);
// Create the token authority
const tokenAuthority = Keypair.generate();
console.log(
chalk.green("👥 Token authority public key:"),
chalk.cyan(tokenAuthority.publicKey.toBase58()),
);
// Add some delay to ensure the Swig account is created
await new Promise((resolve) => setTimeout(resolve, 2000));
const spendingActions = Actions.set()
.solLimit({ amount: BigInt(0.1 * LAMPORTS_PER_SOL) })
.get();
await addNewAuthority(
connection,
rootUser,
spendingAuthority,
swigAddress,
spendingActions,
'spending',
);
const tokenMint = Keypair.generate().publicKey;
const tokenActions = Actions.set()
.tokenLimit({ mint: tokenMint, amount: BigInt(1000000) })
.get();
await addNewAuthority(
connection,
rootUser,
tokenAuthority,
swigAddress,
tokenActions,
'token',
);
const swig = await fetchSwig(connection, swigAddress);
console.log(chalk.blue('\n📊 Authority Permissions:'));
const spendingRole = swig.findRolesByEd25519SignerPk(
spendingAuthority.publicKey,
)[0];
console.log(chalk.yellow('Spending Authority:'));
console.log(
'- Can spend SOL:',
chalk.green(
spendingRole.actions.canSpendSol(BigInt(0.1 * LAMPORTS_PER_SOL)),
),
);
console.log(
'- Can spend tokens:',
chalk.red(spendingRole.actions.canSpendToken(tokenMint, BigInt(1000000))),
);
const tokenRole = swig.findRolesByEd25519SignerPk(
tokenAuthority.publicKey,
)[0];
console.log(chalk.yellow('\nToken Authority:'));
console.log(
'- Can spend SOL:',
chalk.red(tokenRole.actions.canSpendSol(BigInt(0.1 * LAMPORTS_PER_SOL))),
);
console.log(
'- Can spend tokens:',
chalk.green(tokenRole.actions.canSpendToken(tokenMint, BigInt(1000000))),
);
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`,
),
);
})();
import {
createSolanaRpc,
createSolanaRpcSubscriptions,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
signTransactionMessageWithSigners,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
addSignersToTransactionMessage,
type Address,
type Blockhash,
type IInstruction,
type KeyPairSigner,
} from '@solana/kit';
import {
Actions,
createEd25519AuthorityInfo,
fetchSwig,
findSwigPda,
getAddAuthorityInstructions,
getCreateSwigInstruction,
} from '@swig-wallet/kit';
import chalk from 'chalk';
import { sleepSync } from 'bun';
function randomBytes(length: number): Uint8Array {
const arr = new Uint8Array(length);
crypto.getRandomValues(arr);
return arr;
}
function getTransactionMessage<Inst extends IInstruction[]>(
instructions: Inst,
latestBlockhash: Readonly<{
blockhash: Blockhash;
lastValidBlockHeight: bigint;
}>,
feePayer: KeyPairSigner,
signers: KeyPairSigner[] = [],
) {
return pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx),
(tx) => addSignersToTransactionMessage(signers, tx),
);
}
async function sendTransaction<T extends IInstruction[]>(
connection: {
rpc: ReturnType<typeof createSolanaRpc>;
rpcSubscriptions: ReturnType<typeof createSolanaRpcSubscriptions>;
},
instructions: T,
payer: KeyPairSigner,
signers: KeyPairSigner[] = [],
) {
const { value: latestBlockhash } = await connection.rpc
.getLatestBlockhash()
.send();
const txMsg = getTransactionMessage(instructions, latestBlockhash, payer, signers);
const signedTx = await signTransactionMessageWithSigners(txMsg);
await sendAndConfirmTransactionFactory(connection as any)(signedTx, { commitment: 'confirmed' });
return getSignatureFromTransaction(signedTx).toString();
}
async function createSwigAccount(connection: any, user: KeyPairSigner) {
const id = randomBytes(32);
const swigAddress = await findSwigPda(id);
const rootActions = Actions.set().manageAuthority().get();
const authorityInfo = createEd25519AuthorityInfo(user.address);
const ix = await getCreateSwigInstruction({
payer: user.address,
id,
actions: rootActions,
authorityInfo,
});
const sig = await sendTransaction(connection, [ix], user);
console.log(chalk.green('✓ Swig account created at:'), chalk.cyan(swigAddress.toString()));
console.log(chalk.blue('Transaction signature:'), chalk.cyan(sig));
return { swigAddress, transactionSignature: sig };
}
async function addNewAuthority(
connection: any,
rootUser: KeyPairSigner,
newAuthority: KeyPairSigner,
swigAddress: Address,
actions: any, // Actions built from the Actions builder pattern
description: string,
) {
const swig = await fetchSwig(connection.rpc, swigAddress);
const rootRole = swig.findRolesByEd25519SignerPk(rootUser.address)[0];
if (!rootRole) throw new Error('Root role not found');
const ix = await getAddAuthorityInstructions(
swig,
rootRole.id,
createEd25519AuthorityInfo(newAuthority.address),
actions,
);
await sendTransaction(connection, ix, rootUser);
console.log(
chalk.green(`✓ New ${description} authority added:`),
chalk.cyan(newAuthority.address.toString()),
);
}
(async () => {
console.log(chalk.blue('🚀 Starting tutorial - Swig + Kit style'));
const connection = {
rpc: createSolanaRpc('http://localhost:8899'),
rpcSubscriptions: createSolanaRpcSubscriptions('ws://localhost:8900'),
};
const rootUser = await generateKeyPairSigner();
console.log(chalk.green('👤 Root user public key:'), chalk.cyan(rootUser.address.toString()));
await connection.rpc
.requestAirdrop(rootUser.address, lamports(BigInt(100 * 1_000_000_000)))
.send();
sleepSync(2000);
console.log(chalk.yellow('\n📝 Creating Swig account...'));
const { swigAddress } = await createSwigAccount(connection, rootUser);
const spendingAuthority = await generateKeyPairSigner();
const tokenAuthority = await generateKeyPairSigner();
console.log(chalk.green('\n👥 Spending authority public key:'), chalk.cyan(spendingAuthority.address.toString()));
console.log(chalk.green('👥 Token authority public key:'), chalk.cyan(tokenAuthority.address.toString()));
sleepSync(2000);
const spendingActions = Actions.set()
.solLimit({ amount: BigInt(0.1 * 1_000_000_000) })
.get();
await addNewAuthority(connection, rootUser, spendingAuthority, swigAddress, spendingActions, 'spending');
const fakeMint = await generateKeyPairSigner();
const tokenActions = Actions.set()
.tokenLimit({ mint: fakeMint.address, amount: BigInt(1_000_000) })
.get();
await addNewAuthority(connection, rootUser, tokenAuthority, swigAddress, tokenActions, 'token');
const swig = await fetchSwig(connection.rpc, swigAddress);
console.log(chalk.blue('\n📊 Authority Permissions:'));
const spendingRole = swig.findRolesByEd25519SignerPk(spendingAuthority.address)[0];
console.log(chalk.yellow('Spending Authority:'));
console.log(
'- Can spend SOL:',
chalk.green(spendingRole.actions.canSpendSol(BigInt(0.1 * 1_000_000_000))),
);
console.log(
'- Can spend tokens:',
chalk.red(spendingRole.actions.canSpendToken(fakeMint.address, BigInt(1_000_000))),
);
const tokenRole = swig.findRolesByEd25519SignerPk(tokenAuthority.address)[0];
console.log(chalk.yellow('\nToken Authority:'));
console.log(
'- Can spend SOL:',
chalk.red(tokenRole.actions.canSpendSol(BigInt(0.1 * 1_000_000_000))),
);
console.log(
'- Can spend tokens:',
chalk.green(tokenRole.actions.canSpendToken(fakeMint.address, BigInt(1_000_000))),
);
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`,
),
);
})();
Code Breakdown
Let’s examine the key parts oftutorial-2.ts:
1. Creating a Root Authority
- Classic
- Kit
const rootActions = Actions.set().manageAuthority().get();
const createSwigIx = await getCreateSwigInstruction({
payer: user.publicKey,
id,
actions: rootActions,
authorityInfo: createEd25519AuthorityInfo(user.publicKey),
});
const transaction = new Transaction().add(createSwigIx);
const signature = await sendAndConfirmTransaction(connection, transaction, [user]);
const rootActions = Actions.set().manageAuthority().get();
const authorityInfo = createEd25519AuthorityInfo(user.address);
const ix = await getCreateSwigInstruction({
payer: user.address,
id,
actions: rootActions,
authorityInfo,
});
const sig = await sendTransaction(connection, [ix], user);
.manageAuthority() to limit this authority to only managing other authorities.
2. Adding a SOL Spending Authority
See the full list of actions an authority can have.- Classic
- Kit
const spendingActions = Actions.set()
.solLimit({ amount: BigInt(0.1 * LAMPORTS_PER_SOL) })
.get();
await addNewAuthority(
connection,
rootUser,
spendingAuthority,
swigAddress,
spendingActions,
"spending"
);
const spendingActions = Actions.set()
.solLimit({ amount: BigInt(0.1 * 1_000_000_000) })
.get();
await addNewAuthority(
connection,
rootUser,
spendingAuthority,
swigAddress,
spendingActions,
"spending"
);
- Can only spend SOL (no token permissions)
- Has a spending limit of 0.1 SOL
- Cannot manage other authorities
3. Adding a Token Authority
- Classic
- Kit
const tokenMint = Keypair.generate().publicKey; // Example token mint
const tokenActions = Actions.set()
.tokenLimit({ mint: tokenMint, amount: BigInt(1000000) })
.get();
await addNewAuthority(
connection,
rootUser,
tokenAuthority,
swigAddress,
tokenActions,
"token"
);
const fakeMint = await generateKeyPairSigner();
const tokenActions = Actions.set()
.tokenLimit({ mint: fakeMint.address, amount: BigInt(1_000_000) })
.get();
await addNewAuthority(
connection,
rootUser,
tokenAuthority,
swigAddress,
tokenActions,
"token"
);
- Can only manage tokens for a specific mint
- Has a token spending limit of 1,000,000 units
- Cannot spend SOL or manage other authorities
Understanding the Output
When you run the tutorial, you’ll see:- The root user’s public key
- The Swig account address
- Two new authority public keys
- A permission summary showing what each authority can and cannot do
Key Concepts
- Root Authority: An authority with management permissions that can add or remove other authorities
- Specialized Authorities: Authorities with specific, limited permissions (e.g., SOL spending or token management)
- Permission Limits:
- SOL limits are set in lamports (1 SOL = 1,000,000,000 lamports)
- Token limits are set based on the token’s decimals
- Action Sets: Define what an authority can do using methods like:
.manageAuthority().solLimit().tokenLimit()
Running the Tutorial
Execute the tutorial with:bun run ./tutorial/tutorial-2.ts

