This guide walks through different ways to perform transfers using Swig, progressing from basic to more advanced patterns.
You can find all example code in the swig-ts/examples/classic/transfer directory or for kit refer to this directory swig-ts/examples/kit/transfer.
Examples with svm in the file name use LiteSVM for testing. Those without svm in the file name use a local validator. Which you can run with the bun start-validator command.
Basic Transfer with Ed25519 Keys
The simplest way to use Swig is with standard Ed25519 keypairs. Here are the key parts:
// Key Setup - Using standard Solana keypairs
const userRootKeypair = Keypair.generate();
const dappAuthorityKeypair = Keypair.generate();
// Create a new Swig account with root authority
const rootActions = Actions.set().all().get();
const createSwigInstruction = await getCreateSwigInstruction({
payer: userRootKeypair.publicKey,
authorityInfo: createEd25519AuthorityInfo(userRootKeypair.publicKey),
id,
actions: rootActions,
});
// Add a new authority with a spending limit
const dappActions = Actions.set().solLimit({ amount: BigInt(0.1 * LAMPORTS_PER_SOL) }).get();
const addAuthorityInstructions = await getAddAuthorityInstructions(
swig,
rootRole.id,
createEd25519AuthorityInfo(dappAuthorityKeypair.publicKey),
dappActions,
);
// Transfer SOL with Swig authority
const transferIx = SystemProgram.transfer({
fromPubkey: swigAddress,
toPubkey: dappAuthorityKeypair.publicKey,
lamports: 0.1 * LAMPORTS_PER_SOL,
});
const signedInstructions = await getSignInstructions(
swig,
dappRole.id,
[transferIx]
);
await sendTransaction(connection, signedInstructions, dappAuthorityKeypair);
You can find the complete example in transfer-local.ts and transfer-svm.ts for local and LiteSVM environments respectively.
Using Secp256k1 Keys (EVM Compatible)
For EVM compatibility, Swig supports Secp256k1 keys. Note the key differences from the Ed25519 example:
// Key Setup - Using Ethereum wallet
const userWallet = Wallet.generate(); // 👈 EVM wallet instead of Solana Keypair
// Connect to network and create payers
const connection = new Connection('http://localhost:8899', 'confirmed');
const payer = Keypair.generate();
await connection.requestAirdrop(payer.publicKey, LAMPORTS_PER_SOL);
const signer = Keypair.generate();
await connection.requestAirdrop(signer.publicKey, LAMPORTS_PER_SOL);
// Find Swig PDA by ID
const id = Uint8Array.from(Array(32).fill(1)); // use any 32-byte identifier
const swigAddress = findSwigPda(id);
// Create Swig with Secp256k1 authority
const rootActions = Actions.set().all().get();
const createSwigInstruction = await getCreateSwigInstruction({
authorityInfo: createSecp256k1AuthorityInfo(userWallet.getPublicKey()),
id,
payer: payer.publicKey,
actions: rootActions,
});
// Prepare transfer
const transfer = SystemProgram.transfer({
fromPubkey: swigAddress,
toPubkey: dappTreasury,
lamports: 0.1 * LAMPORTS_PER_SOL,
});
// Sign using the Secp256k1 private key
const signingFn = getSigningFnForSecp256k1PrivateKey(
userWallet.getPrivateKey(),
);
const slot = await connection.getSlot('finalized');
const signedTransfer = await getSignInstructions(
swig,
rootRole.id,
[transfer],
false,
{
currentSlot: BigInt(slot),
signingFn,
payer: signer.publicKey,
},
);
Check out transfer-local-secp.ts and transfer-svm-secp.ts for complete examples.
Using Viem for Ethereum Wallet Integration
For a more complete Ethereum wallet integration using Viem:
// Using Viem account
const privateKeyAccount = privateKeyToAccount(userWallet.getPrivateKeyString());
const swigAddress = findSwigPda(id);
const rootActions = Actions.set().all().get();
const createSwigInstruction = await getCreateSwigInstruction({
authorityInfo: createSecp256k1AuthorityInfo(privateKeyAccount.publicKey),
id,
payer: transactionPayer.publicKey,
actions: rootActions,
});
sendSVMTransaction(svm, [createSwigInstruction], transactionPayer);
let swig = fetchSwig(svm, swigAddress);
svm.airdrop(swigAddress, BigInt(LAMPORTS_PER_SOL));
let rootRole = swig.findRolesBySecp256k1SignerAddress(privateKeyAccount.address)[0];
if (!rootRole) throw new Error('Role not found for authority');
const viemSign: SigningFn = async (message: Uint8Array) => {
const sig = await privateKeyAccount.sign({ hash: keccak256(message) });
return { signature: hexToBytes(sig) };
};
const transfer = SystemProgram.transfer({
fromPubkey: swigAddress,
toPubkey: dappTreasury,
lamports: 0.1 * LAMPORTS_PER_SOL,
});
let signTransfer = await getSignInstructions(swig, rootRole.id, [transfer], false, {
currentSlot: svm.getClock().slot,
signingFn: viemSign,
payer: transactionPayer.publicKey,
});
sendSVMTransaction(svm, signTransfer, transactionPayer);
const viemSignWithPrefix: SigningFn = async (message: Uint8Array) => {
const prefix = getEvmPersonalSignPrefix(message.length);
const prefixedMessage = new Uint8Array(prefix.length + message.length);
prefixedMessage.set(prefix);
prefixedMessage.set(message, prefix.length);
const sig = await privateKeyAccount.sign({ hash: keccak256(prefixedMessage) });
return { signature: hexToBytes(sig), prefix };
};
signTransfer = await getSignInstructions(swig, rootRole.id, [transfer], false, {
currentSlot: svm.getClock().slot,
signingFn: viemSignWithPrefix,
payer: transactionPayer.publicKey,
});
sendSVMTransaction(svm, signTransfer, transactionPayer);
const viemSignMessage: SigningFn = async (message: Uint8Array) => {
const sig = await privateKeyAccount.signMessage({ message: { raw: message } });
return {
signature: hexToBytes(sig),
prefix: getEvmPersonalSignPrefix(message.length),
};
};
signTransfer = await getSignInstructions(swig, rootRole.id, [transfer], false, {
currentSlot: svm.getClock().slot,
signingFn: viemSignMessage,
payer: transactionPayer.publicKey,
});
sendSVMTransaction(svm, signTransfer, transactionPayer);
See transfer-svm-viem.ts for the full working code.
Using Sessions for Enhanced Security
Sessions allow limited, temporary authority:
Ed25519 Sessions
// Key Setup
const userRootKeypair = Keypair.generate();
const dappSessionKeypair = Keypair.generate();
// Create Swig with Session Authority
const createSwigIx = await getCreateSwigInstruction({
id,
authorityInfo: createEd25519SessionAuthorityInfo(
userRootKeypair.publicKey,
100n, // session duration in slots
),
actions: rootActions,
payer: userRootKeypair.publicKey,
});
// Create Session for dApp
const createSessionIx = await getCreateSessionInstructions(
swig,
rootRole.id,
dappSessionKeypair.publicKey,
50n, // session duration
);
// Find and use session role
const sessionRole = swig.findRoleBySessionKey(dappSessionKeypair.publicKey);
if (!sessionRole || !sessionRole.isSessionBased()) {
throw new Error('Invalid session role');
}
// Sign with session key
const signedTransfer = await getSignInstructions(
swig,
sessionRole.id,
[transfer],
false,
{ payer: dappSessionKeypair.publicKey },
);
Secp256k1 Sessions
// Key Setup - EVM wallet with session capability
const userWallet = Wallet.generate();
const userRootKeypair = Keypair.generate();
const dappSessionKeypair = Keypair.generate();
// Create Swig with Secp256k1 Session Authority
const createSwigInstruction = await getCreateSwigInstruction({
authorityInfo: createSecp256k1SessionAuthorityInfo(
userWallet.getPublicKey(),
100n, // max session duration in slots
),
id,
payer: userRootKeypair.publicKey,
actions: rootActions,
});
// Create Session with EVM signing
const signingFn = getSigningFnForSecp256k1PrivateKey(
userWallet.getPrivateKeyString(),
);
const currentSlot = await connection.getSlot('confirmed');
const sessionInstructions = await getCreateSessionInstructions(
swig,
rootRole.id,
dappSessionKeypair.publicKey,
50n, // session duration
{
currentSlot: BigInt(currentSlot),
signingFn,
payer: userRootKeypair.publicKey,
},
);
// Find and use session role
const sessionRole = swig.findRoleBySessionKey(dappSessionKeypair.publicKey);
if (!sessionRole) throw new Error('Session role not found');
// Sign with session key
const signTransfer = await getSignInstructions(
swig,
sessionRole.id,
[transferIx],
false,
{
currentSlot: BigInt(currentSlot),
signingFn,
payer: dappSessionKeypair.publicKey,
},
);
Key aspects of sessions:
- Can be created with any supported authority type (Ed25519, Secp256k1)
- Limited duration specified in slots
- Restricted set of actions (defined by sessionActions)
- Perfect for dApp integrations where temporary access is needed
You can find complete session examples in (we have just mentioned a few you can find all of them labelled a specific way but it will contain the word “sessions” in the file name):
transfer-local-session.ts (Ed25519)
transfer-svm-session.ts (Ed25519)
transfer-svm-secp-session.ts (Secp256k1)
For more detailed information about sessions, check out the Creating Sessions Guide.
Testing Environment Options
These examples can be run in different environments:
Local Validator: Use the transfer-local-* examples
- Requires running a local Solana validator
- Real blockchain environment
- Good for final testing
LiteSVM: Use the transfer-svm-* examples
- No validator required
- Instant transaction confirmation
- Perfect for rapid development
- Simulates the Solana runtime
Devnet: All examples work with devnet
- Change connection URL to
https://api.devnet.solana.com
- Real network environment
- Free to use with airdropped SOL
LiteSVM is recommended for initial development as it provides the fastest feedback loop. Move to local validator or devnet for more thorough testing.