swig-smart-wallet
npx skills add https://github.com/anagrambuild/swig-ts --skill swig-smart-wallet
Agent 安装分布
Skill 文档
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:
mainnetordevnet
Store these as environment variables:
SWIG_PAYMASTER_API_KEY=<their-api-key>
SWIG_PAYMASTER_PUBKEY=<their-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=<their-server-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:
- Generate a new Ed25519 keypair for the agent
- Save the keypair securely to a file (e.g.
agent-keypair.json) - Display the public key to the user
- Ask: “Please send a small amount of SOL (at least 0.01 SOL) to
<agent-public-key>and let me know when done.” - 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
# 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
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
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.
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
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
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.
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
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
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:
async function sponsorTransaction(
serializedTx: Uint8Array,
sponsorUrl: string,
): Promise<Uint8Array> {
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:
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:
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_000lamports (useBigIntornsuffix) - Swig Paymaster Dashboard:
dashboard.onswig.com
Workflow Summary
- Gather config: RPC URL, gas strategy, SDK choice
- Install packages:
@swig-wallet/classicor@swig-wallet/kit(+ paymaster if needed) - Set up identity: Load or generate a keypair
- Create Swig wallet: Generate ID, derive PDA, send create instruction
- Fund the wallet: Transfer SOL to the Swig wallet address
- Add authorities: Grant granular permissions to other keys
- Transact: Wrap inner instructions with
getSignInstructionsto execute from the wallet - 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.