--- name: swig-smart-wallet description: > Create and manage Solana smart wallets using the Swig TypeScript SDK. This skill enables AI agents to generate Swig wallets, manage authorities (add/remove/update with granular permissions), execute transactions through the wallet, and handle gas sponsorship via the Swig Paymaster API, a custom gas server, or self-funded SOL. Works with both @swig-wallet/classic (web3.js 1.x) and @swig-wallet/kit (@solana/kit 2.x). --- # Swig Smart Wallet Skill You are an AI agent that can create and manage Solana smart wallets using the Swig protocol. Swig wallets are on-chain programmable wallets with granular authority and permission management. You write TypeScript scripts that use the Swig SDK to perform wallet operations. ## Prerequisites Before doing anything, you MUST gather the following from the user: ### 1. Solana RPC Endpoint Ask the user: "What Solana RPC endpoint should I use for transactions?" Acceptable answers: - A specific URL (e.g. `https://api.mainnet-beta.solana.com`, `https://api.devnet.solana.com`, or a custom RPC like Helius, Triton, etc.) - "devnet" — use `https://api.devnet.solana.com` - "mainnet" — use `https://api.mainnet-beta.solana.com` - "localnet" or "localhost" — use `http://localhost:8899` If the user does not know, suggest they use devnet for testing: `https://api.devnet.solana.com` Store the RPC URL in an environment variable `SOLANA_RPC_URL` or in a `.env` file. ### 2. Gas / Fee Sponsorship Ask the user: "How would you like transaction fees to be handled?" Present these three options: **Option A — Swig Paymaster (recommended for production)** The Swig Paymaster API covers transaction fees. Ask the user to provide: - Their **API Key** from `dashboard.onswig.com` - Their **Paymaster Public Key** from `dashboard.onswig.com` - The **network** they want to use: `mainnet` or `devnet` Store these as environment variables: ``` SWIG_PAYMASTER_API_KEY= SWIG_PAYMASTER_PUBKEY= SWIG_PAYMASTER_NETWORK=devnet ``` **Option B — Custom Gas Sponsorship Server** The user runs their own server that signs/sponsors transactions. Ask for: - The **API URL** of their gas sponsorship server Store as: ``` GAS_SPONSOR_URL= ``` **Option C — Self-funded (agent pays its own fees)** The agent generates its own Solana keypair and pays fees from its own balance. The user must send a small amount of SOL (0.01-0.1 SOL) to the agent's address. When using this option: 1. Generate a new Ed25519 keypair for the agent 2. Save the keypair securely to a file (e.g. `agent-keypair.json`) 3. Display the public key to the user 4. Ask: "Please send a small amount of SOL (at least 0.01 SOL) to `` and let me know when done." 5. Verify the balance before proceeding ## SDK Choice The Swig SDK comes in two flavors. Choose based on the project: | Package | Solana SDK | When to Use | | ---------------------- | ---------------------- | ------------------------------------------------------------------- | | `@swig-wallet/classic` | `@solana/web3.js` v1.x | Existing projects using web3.js v1, broader ecosystem compatibility | | `@swig-wallet/kit` | `@solana/kit` v2.x | New projects, modern Solana development | If the user has no preference, default to `@swig-wallet/classic` for broader compatibility. ## Installation ```bash # For classic (web3.js 1.x) npm install @swig-wallet/classic @solana/web3.js # For kit (@solana/kit 2.x) npm install @swig-wallet/kit @solana/kit # For paymaster support (add one based on SDK choice) npm install @swig-wallet/paymaster-classic # or npm install @swig-wallet/paymaster-kit ``` ## Core Operations ### 1. Create a Swig Wallet A Swig wallet is created on-chain with: - A random 32-byte **ID** (used to derive the PDA address) - A **root authority** (the initial owner, typically an Ed25519 public key) - **Root actions/permissions** (typically `Actions.set().all().get()` for full control) #### Classic Example ```typescript import { Connection, Keypair, Transaction, sendAndConfirmTransaction, } from '@solana/web3.js'; import { Actions, createEd25519AuthorityInfo, fetchSwig, findSwigPda, getCreateSwigInstruction, getSwigWalletAddress, } from '@swig-wallet/classic'; const connection = new Connection(process.env.SOLANA_RPC_URL!, 'confirmed'); const payer = Keypair.fromSecretKey(/* loaded from file or env */); // 1. Generate random 32-byte Swig ID const id = new Uint8Array(32); crypto.getRandomValues(id); // 2. Derive the Swig account PDA const swigAccountAddress = findSwigPda(id); // 3. Create root authority info from the payer's pubkey const rootAuthorityInfo = createEd25519AuthorityInfo(payer.publicKey); // 4. Set root permissions (full control) const rootActions = Actions.set().all().get(); // 5. Build the create instruction const createSwigIx = await getCreateSwigInstruction({ payer: payer.publicKey, id, actions: rootActions, authorityInfo: rootAuthorityInfo, }); // 6. Send transaction const tx = new Transaction().add(createSwigIx); const signature = await sendAndConfirmTransaction(connection, tx, [payer]); // 7. Fetch and verify const swig = await fetchSwig(connection, swigAccountAddress); const walletAddress = await getSwigWalletAddress(swig); console.log('Swig account:', swigAccountAddress.toBase58()); console.log('Swig wallet address:', walletAddress.toBase58()); ``` #### Kit Example ```typescript import { createSolanaRpc, createSolanaRpcSubscriptions, generateKeyPairSigner, sendAndConfirmTransactionFactory, signTransaction, pipe, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, compileTransaction, getSignatureFromTransaction, } from '@solana/kit'; import { Actions, createEd25519AuthorityInfo, fetchSwig, findSwigPda, getCreateSwigInstruction, getSwigWalletAddress, } from '@swig-wallet/kit'; const rpc = createSolanaRpc(process.env.SOLANA_RPC_URL!); const rpcSubscriptions = createSolanaRpcSubscriptions( process.env.SOLANA_RPC_URL!.replace('https', 'wss'), ); const payer = await generateKeyPairSigner(); const id = new Uint8Array(32); crypto.getRandomValues(id); const swigAccountAddress = await findSwigPda(id); const rootAuthorityInfo = createEd25519AuthorityInfo(payer.address); const rootActions = Actions.set().all().get(); const createSwigIx = await getCreateSwigInstruction({ payer: payer.address, id, actions: rootActions, authorityInfo: rootAuthorityInfo, }); const { value: blockhash } = await rpc.getLatestBlockhash().send(); const txMessage = pipe( createTransactionMessage({ version: 0 }), (m) => setTransactionMessageFeePayer(payer.address, m), (m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m), (m) => appendTransactionMessageInstructions([createSwigIx], m), ); const compiledTx = compileTransaction(txMessage); const signedTx = await signTransaction([payer.keyPair], compiledTx); await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTx, { commitment: 'confirmed', }); const swig = await fetchSwig(rpc, swigAccountAddress); const walletAddress = await getSwigWalletAddress(swig); ``` ### 2. Add an Authority Authorities are additional keys that can interact with the Swig wallet, each with specific permissions. ```typescript import { fetchSwig, getAddAuthorityInstructions, createEd25519AuthorityInfo, Actions, } from '@swig-wallet/classic'; const swig = await fetchSwig(connection, swigAccountAddress); const rootRole = swig.findRolesByEd25519SignerPk(rootKeypair.publicKey)[0]; // Create a new authority with specific permissions const newAuthorityInfo = createEd25519AuthorityInfo(newKeypair.publicKey); // Example permissions: 0.5 SOL spend limit const actions = Actions.set() .solLimit({ amount: 500_000_000n }) // 0.5 SOL in lamports .get(); const ixs = await getAddAuthorityInstructions( swig, rootRole.id, newAuthorityInfo, actions, ); const tx = new Transaction().add(...ixs); await sendAndConfirmTransaction(connection, tx, [rootKeypair]); ``` ### 3. Remove an Authority ```typescript import { getRemoveAuthorityInstructions, fetchSwig, } from '@swig-wallet/classic'; const swig = await fetchSwig(connection, swigAccountAddress); const rootRole = swig.findRolesByEd25519SignerPk(rootKeypair.publicKey)[0]; // Find the role ID of the authority to remove const roleToRemove = swig.findRolesByEd25519SignerPk(targetPubkey)[0]; const ixs = await getRemoveAuthorityInstructions( swig, rootRole.id, roleToRemove.id, ); const tx = new Transaction().add(...ixs); await sendAndConfirmTransaction(connection, tx, [rootKeypair]); ``` ### 4. Update Authority Permissions ```typescript import { getUpdateAuthorityInstructions, updateAuthorityReplaceAllActions, updateAuthorityAddActions, updateAuthorityRemoveByType, Actions, Permission, fetchSwig, } from '@swig-wallet/classic'; const swig = await fetchSwig(connection, swigAccountAddress); const rootRole = swig.findRolesByEd25519SignerPk(rootKeypair.publicKey)[0]; const roleToUpdate = swig.findRolesByEd25519SignerPk(targetPubkey)[0]; // Option 1: Replace all actions const updateInfo = updateAuthorityReplaceAllActions( Actions.set().solLimit({ amount: 1_000_000_000n }).programAll().get(), ); // Option 2: Add new actions // const updateInfo = updateAuthorityAddActions( // Actions.set().tokenLimit({ mint: tokenMintPubkey, amount: 1_000_000n }).get() // ); // Option 3: Remove actions by type // const updateInfo = updateAuthorityRemoveByType([Permission.SolLimit]); const ixs = await getUpdateAuthorityInstructions( swig, rootRole.id, roleToUpdate.id, updateInfo, ); const tx = new Transaction().add(...ixs); await sendAndConfirmTransaction(connection, tx, [rootKeypair]); ``` ### 5. Execute Transactions Through the Swig Wallet The Swig wallet acts as the signer for inner instructions. Use `getSignInstructions` to wrap any instruction so it executes from the Swig wallet. ```typescript import { SystemProgram } from '@solana/web3.js'; import { getSignInstructions, getSwigWalletAddress, fetchSwig, } from '@swig-wallet/classic'; const swig = await fetchSwig(connection, swigAccountAddress); const walletAddress = await getSwigWalletAddress(swig); const role = swig.findRolesByEd25519SignerPk(signerKeypair.publicKey)[0]; // Build the inner instruction (SOL transfer from the Swig wallet) const transferIx = SystemProgram.transfer({ fromPubkey: walletAddress, toPubkey: recipientPubkey, lamports: 100_000_000, // 0.1 SOL }); // Wrap it with Swig signing const signedIxs = await getSignInstructions(swig, role.id, [transferIx]); const tx = new Transaction().add(...signedIxs); await sendAndConfirmTransaction(connection, tx, [signerKeypair]); ``` ### 6. Using the Paymaster (Gasless Transactions) #### Classic Paymaster ```typescript import { Keypair } from '@solana/web3.js'; import { createPaymasterClient } from '@swig-wallet/paymaster-classic'; const paymaster = createPaymasterClient({ apiKey: process.env.SWIG_PAYMASTER_API_KEY!, paymasterPubkey: process.env.SWIG_PAYMASTER_PUBKEY!, baseUrl: 'https://api.onswig.com', network: process.env.SWIG_PAYMASTER_NETWORK as 'mainnet' | 'devnet', }); // Build your instructions normally, then: const tx = await paymaster.createLegacyTransaction( [instruction1, instruction2], [userKeypair], // user signs ); // Paymaster signs and sends const signature = await paymaster.signAndSend(tx); ``` #### Kit Paymaster ```typescript import { partiallySignTransaction } from '@solana/kit'; import { createPaymasterClient } from '@swig-wallet/paymaster-kit'; const paymaster = createPaymasterClient({ apiKey: process.env.SWIG_PAYMASTER_API_KEY!, paymasterPubkey: process.env.SWIG_PAYMASTER_PUBKEY!, baseUrl: 'https://api.onswig.com', network: process.env.SWIG_PAYMASTER_NETWORK as 'mainnet' | 'devnet', }); const unsignedTx = await paymaster.createTransaction([instruction]); const partiallySignedTx = await partiallySignTransaction( [userKeypair.keyPair], unsignedTx, ); const fullySignedTx = await paymaster.fullySign(partiallySignedTx); await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })( fullySignedTx, { commitment: 'confirmed' }, ); ``` ### 7. Custom Gas Sponsorship Server If the user has their own gas server, send the serialized transaction to it for signing: ```typescript async function sponsorTransaction( serializedTx: Uint8Array, sponsorUrl: string, ): Promise { const response = await fetch(`${sponsorUrl}/sponsor`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transaction: Buffer.from(serializedTx).toString('base64'), }), }); const { signedTransaction } = await response.json(); return Buffer.from(signedTransaction, 'base64'); } ``` ## Available Permission Types Use the `Actions` builder to set permissions for authorities: | Method | Description | | --------------------------------------------------------------------------------- | ------------------------------------------------ | | `.all()` | Full root permissions (everything) | | `.manageAuthority()` | Can add/remove/update other authorities | | `.allButManageAuthority()` | Everything except managing authorities | | `.closeSwigAuthority()` | Can close the Swig account | | `.solLimit({ amount })` | One-time SOL spend limit (in lamports as bigint) | | `.solRecurringLimit({ recurringAmount, window })` | Recurring SOL limit (window in slots) | | `.solDestinationLimit({ amount, destination })` | SOL limit to specific recipient | | `.solRecurringDestinationLimit({ recurringAmount, window, destination })` | Recurring SOL to specific recipient | | `.tokenLimit({ mint, amount })` | One-time token spend limit | | `.tokenRecurringLimit({ mint, recurringAmount, window })` | Recurring token limit | | `.tokenDestinationLimit({ mint, amount, destination })` | Token limit to specific recipient | | `.tokenRecurringDestinationLimit({ mint, recurringAmount, window, destination })` | Recurring token to specific recipient | | `.programLimit({ programId })` | Can interact with a specific program | | `.programAll()` | Can interact with any program | | `.programCurated()` | Can interact with curated programs | | `.subAccount()` | Can create/manage sub-accounts | | `.stakeAll()` | Full staking permissions | | `.stakeLimit({ amount })` | Staking with amount limit | Combine permissions by chaining: ```typescript const actions = Actions.set() .solLimit({ amount: 1_000_000_000n }) // 1 SOL limit .tokenLimit({ mint: usdcMint, amount: 100_000_000n }) // 100 USDC .programLimit({ programId: jupiterProgramId }) // Jupiter access .get(); ``` ## Authority Types | Type | Function | Use Case | | --------------- | ----------------------------------------------------------- | ----------------------- | | Ed25519 | `createEd25519AuthorityInfo(publicKey)` | Standard Solana keypair | | Ed25519 Session | `createEd25519SessionAuthorityInfo(publicKey, maxDuration)` | Time-limited Ed25519 | | Secp256k1 | `createSecp256k1AuthorityInfo(publicKey)` | Ethereum-style keys | | Secp256r1 | `createSecp256r1AuthorityInfo(publicKey)` | Passkeys / WebAuthn | ## Agent Identity Key Setup (Self-Funded) When the agent needs its own keypair: ```typescript import { Keypair } from '@solana/web3.js'; import fs from 'fs'; // Generate and save const agentKeypair = Keypair.generate(); fs.writeFileSync( 'agent-keypair.json', JSON.stringify(Array.from(agentKeypair.secretKey)), ); console.log('Agent public key:', agentKeypair.publicKey.toBase58()); // Load later const loaded = Keypair.fromSecretKey( new Uint8Array(JSON.parse(fs.readFileSync('agent-keypair.json', 'utf-8'))), ); ``` ## Important Constants - **Swig Program ID**: `swigypWHEksbC64pWKwah1WTeh9JXwx8H1rJHLdbQMB` - **1 SOL** = `1_000_000_000` lamports (use `BigInt` or `n` suffix) - **Swig Paymaster Dashboard**: `dashboard.onswig.com` ## Workflow Summary 1. **Gather config**: RPC URL, gas strategy, SDK choice 2. **Install packages**: `@swig-wallet/classic` or `@swig-wallet/kit` (+ paymaster if needed) 3. **Set up identity**: Load or generate a keypair 4. **Create Swig wallet**: Generate ID, derive PDA, send create instruction 5. **Fund the wallet**: Transfer SOL to the Swig wallet address 6. **Add authorities**: Grant granular permissions to other keys 7. **Transact**: Wrap inner instructions with `getSignInstructions` to execute from the wallet 8. **Manage**: Update or remove authorities as needed ## Error Handling Always wrap transactions in try/catch and handle common errors: - Insufficient balance (need SOL for rent + fees) - Authority not found (refetch swig before operations) - Permission denied (authority lacks required actions) - Spend limit exceeded (authority has already used its allowance) Always call `swig.refetch()` before building new instructions to ensure you have the latest on-chain state.