import {
createSolanaRpc,
createSolanaRpcSubscriptions,
generateKeyPairSigner,
sendAndConfirmTransactionFactory,
getSignatureFromTransaction,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
signTransactionMessageWithSigners,
lamports,
pipe,
AccountRole,
type IInstructionWithData,
type KeyPairSigner,
type ReadonlyUint8Array,
} from '@solana/kit';
import {
Actions,
createSecp256k1AuthorityInfo,
findSwigPda,
fetchSwig,
getCreateSwigInstruction,
getEvmPersonalSignPrefix,
getSignInstructions,
type SigningFn,
} from '@swig-wallet/kit';
import {
getTransferSolInstructionDataEncoder,
SYSTEM_PROGRAM_ADDRESS,
} from '@solana-program/system';
import { Wallet } from '@ethereumjs/wallet';
import { privateKeyToAccount } from 'viem/accounts';
import { keccak256, hexToBytes } from 'viem';
// Set up Solana localnet RPC
const rpc = createSolanaRpc('http://localhost:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://localhost:8900');
const connection = { rpc, rpcSubscriptions };
// Generate EVM wallet
const userWallet = Wallet.generate();
const evmAccount = privateKeyToAccount(userWallet.getPrivateKeyString());
// Generate localnet payers
const payer = await generateKeyPairSigner();
const dappTreasury = await generateKeyPairSigner();
// Swig ID and address
const swigId = Uint8Array.from(Array(32).fill(1));
const swigAddress = await findSwigPda(swigId);
// Create Swig with secp256k1 authority
const rootActions = Actions.set().all().get();
const createSwigIx = await getCreateSwigInstruction({
authorityInfo: createSecp256k1AuthorityInfo(evmAccount.publicKey),
id: swigId,
payer: payer.address,
actions: rootActions,
});
// Fetch Swig account and role
let swig = await fetchSwig(rpc, swigAddress);
let rootRole = swig.findRolesBySecp256k1SignerAddress(evmAccount.address)[0];
// Different signing methods available:
// 1. Direct keccak256 hash signing (raw message bytes)
const viemSign: SigningFn = async (message) => {
const sig = await evmAccount.sign({ hash: keccak256(message) });
return { signature: hexToBytes(sig) };
};
// 2. Manual Ethereum personal sign prefix
const viemSignWithPrefix: SigningFn = async (message) => {
const prefix = getEvmPersonalSignPrefix(message.length);
const prefixedMessage = new Uint8Array(prefix.length + message.length);
prefixedMessage.set(prefix);
prefixedMessage.set(message, prefix.length);
const sig = await evmAccount.sign({ hash: keccak256(prefixedMessage) });
return { signature: hexToBytes(sig), prefix };
};
// 3. Viem signMessage method (automatically adds Ethereum personal sign prefix)
const viemSignMessage: SigningFn = async (message) => {
const sig = await evmAccount.signMessage({ message: { raw: message } });
return {
signature: hexToBytes(sig),
prefix: getEvmPersonalSignPrefix(message.length),
};
};
// Prepare transfer instruction (0.1 SOL)
const transferIx = {
programAddress: SYSTEM_PROGRAM_ADDRESS,
accounts: [
{ address: swigAddress, role: AccountRole.WRITABLE_SIGNER },
{ address: dappTreasury.address, role: AccountRole.WRITABLE },
],
data: new Uint8Array(
getTransferSolInstructionDataEncoder().encode({
amount: lamports(100_000_000n), // 0.1 SOL
}),
),
};
// Sign and send transfer
const currentSlot = await rpc.getSlot().send();
const signedIxs = await getSignInstructions(swig, rootRole.id, [transferIx], false, {
payer: payer.address,
currentSlot,
signingFn: viemSign, // or viemSignWithPrefix, or viemSignMessage
});