Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "programs/anchor/cp-swap-reference"]
path = programs/anchor/cp-swap-reference
url = https://github.com/Lightprotocol/cp-swap-reference.git
[submodule "vendor/smart-account-program"]
path = vendor/smart-account-program
url = https://github.com/klausundklaus/smart-account-program.git
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
"toolkits/sign-with-privy/react",
"toolkits/sign-with-privy/nodejs",
"toolkits/sign-with-privy/scripts",
"toolkits/sponsor-rent-top-ups/typescript"
"toolkits/sponsor-rent-top-ups/typescript",
"toolkits/squads-smart-wallet",
"vendor/smart-account-program/sdk/smart-account"
],
"scripts": {
"toolkit:payments": "npm run -w toolkits/payments-and-wallets"
"toolkit:payments": "npm run -w toolkits/payments-and-wallets",
"toolkit:squads": "npm run -w toolkits/squads-smart-wallet"
},
"dependencies": {
"@lightprotocol/compressed-token": "beta",
Expand Down
93 changes: 93 additions & 0 deletions toolkits/squads-smart-wallet/fund-wallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import "dotenv/config";
import {
Keypair,
PublicKey,
SystemProgram,
TransactionMessage,
VersionedTransaction,
} from "@solana/web3.js";
import {
createRpc,
buildAndSignTx,
sendAndConfirmTx,
} from "@lightprotocol/stateless.js";
import {
createMintInterface,
createAtaInterface,
getAssociatedTokenAddressInterface,
mintToInterface,
createLightTokenTransferInstruction,
} from "@lightprotocol/compressed-token";
import * as smartAccount from "@sqds/smart-account";
import { homedir } from "os";
import { readFileSync } from "fs";

const RPC_URL = process.env.RPC_URL || "http://127.0.0.1:8899";
const rpc = createRpc(RPC_URL);

const payer = Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
)
);

(async function () {
// 1. Create Light Token mint and mint tokens to payer
const { mint } = await createMintInterface(rpc, payer, payer, null, 9);
await createAtaInterface(rpc, payer, mint, payer.publicKey);
const payerAta = getAssociatedTokenAddressInterface(mint, payer.publicKey);
await mintToInterface(rpc, payer, mint, payerAta, payer, 1_000_000);

// 2. Create a 1-of-1 smart account
const programConfig =
await smartAccount.accounts.ProgramConfig.fromAccountAddress(
rpc,
smartAccount.getProgramConfigPda({})[0]
);
const accountIndex =
BigInt(programConfig.smartAccountIndex.toString()) + 1n;
const [settingsPda] = smartAccount.getSettingsPda({ accountIndex });
const [walletPda] = smartAccount.getSmartAccountPda({
settingsPda,
accountIndex: 0,
});

const createSig = await smartAccount.rpc.createSmartAccount({
connection: rpc,
treasury: programConfig.treasury,
creator: payer,
settings: settingsPda,
settingsAuthority: null,
threshold: 1,
signers: [
{
key: payer.publicKey,
permissions: smartAccount.types.Permissions.all(),
},
],
timeLock: 0,
rentCollector: null,
sendOptions: { skipPreflight: true },
});
await rpc.confirmTransaction(createSig, "confirmed");

// 3. Create Light Token ATA for the wallet (off-curve PDA)
await createAtaInterface(rpc, payer, mint, walletPda, true);
const walletAta = getAssociatedTokenAddressInterface(mint, walletPda, true);

// 4. Transfer Light Tokens to the wallet — no approval needed
const ix = createLightTokenTransferInstruction(
payerAta,
walletAta,
payer.publicKey,
500_000
);
const { blockhash } = await rpc.getLatestBlockhash();
const tx = buildAndSignTx([ix], payer, blockhash, []);
const sig = await sendAndConfirmTx(rpc, tx);

console.log("Settings PDA:", settingsPda.toBase58());
console.log("Wallet PDA:", walletPda.toBase58());
console.log("Wallet ATA:", walletAta.toBase58());
console.log("Fund tx:", sig);
})();
221 changes: 221 additions & 0 deletions toolkits/squads-smart-wallet/guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
# Squads Smart Account + Light Token Integration

This toolkit demonstrates how to use rent-free Light Tokens with Squads Smart Accounts — programmable wallets with configurable access control on Solana.

## Overview

A Squads Smart Account is a wallet (PDA) with rules: who can sign, at what threshold, with what time lock. Light Tokens are real Solana ATAs with protocol-sponsored rent. Combined, you get a programmable wallet that holds rent-free tokens.

Two execution modes:

- **Sync** — Immediate execution in a single transaction. Requires all signers present and `timeLock=0`. No proposal overhead.
- **Async** — Full proposal lifecycle: create → propose → approve → execute. For multi-party governance.

## Smart Account Setup

```typescript
import * as smartAccount from "@sqds/smart-account";

// Read ProgramConfig to get the next available account index
const programConfig =
await smartAccount.accounts.ProgramConfig.fromAccountAddress(
rpc,
smartAccount.getProgramConfigPda({})[0]
);
const accountIndex =
BigInt(programConfig.smartAccountIndex.toString()) + 1n;

// Derive PDAs
const [settingsPda] = smartAccount.getSettingsPda({ accountIndex });
const [walletPda] = smartAccount.getSmartAccountPda({
settingsPda,
accountIndex: 0,
});

// Create 1-of-1 smart account (timeLock=0 enables sync execution)
await smartAccount.rpc.createSmartAccount({
connection: rpc,
treasury: programConfig.treasury,
creator: payer,
settings: settingsPda,
settingsAuthority: null,
threshold: 1,
signers: [
{ key: payer.publicKey, permissions: smartAccount.types.Permissions.all() },
],
timeLock: 0,
rentCollector: null,
});
```

For multi-party governance, add more signers and increase the threshold:

```typescript
const { Permission, Permissions } = smartAccount.types;

signers: [
{ key: admin.publicKey, permissions: Permissions.all() },
{ key: signer2.publicKey, permissions: Permissions.fromPermissions([Permission.Vote]) },
{ key: signer3.publicKey, permissions: Permissions.fromPermissions([Permission.Vote]) },
],
threshold: 2,
```

## Off-Curve PDA Transfers

The wallet PDA is off-curve (not a valid keypair). The high-level SDK functions `transferInterface()` and `createTransferInterfaceInstructions()` reject off-curve addresses.

Use `createLightTokenTransferInstruction()` instead — it accepts any `PublicKey`:

```typescript
import { createLightTokenTransferInstruction } from "@lightprotocol/compressed-token";

const ix = createLightTokenTransferInstruction(
sourceAta, // source Light Token ATA
destAta, // destination Light Token ATA
ownerPubkey, // owner of source ATA (can be off-curve PDA)
amount,
feePayer // optional, defaults to owner
);
```

## Fund the Smart Wallet

Anyone can send Light Tokens to a smart wallet — no approval needed.

```typescript
// Create wallet's Light Token ATA (allowOwnerOffCurve=true for PDAs)
await createAtaInterface(rpc, payer, mint, walletPda, true);
const walletAta = getAssociatedTokenAddressInterface(mint, walletPda, true);

// Transfer
const ix = createLightTokenTransferInstruction(
payerAta, walletAta, payer.publicKey, amount
);
const { blockhash } = await rpc.getLatestBlockhash();
const tx = buildAndSignTx([ix], payer, blockhash, []);
await sendAndConfirmTx(rpc, tx);
```

See `fund-wallet.ts` for a complete example.

## Smart Wallet Sends — Sync Execution

Single transaction, immediate execution. The smart account program executes the inner instruction via CPI, signing with the wallet PDA's seeds.

```typescript
// Build Light Token transfer instruction
const transferIx = createLightTokenTransferInstruction(
walletAta, recipientAta, walletPda, amount, walletPda
);

// Compile for synchronous execution
const { instructions, accounts } =
smartAccount.utils.instructionsToSynchronousTransactionDetails({
vaultPda: walletPda,
members: [payer.publicKey],
transaction_instructions: [transferIx],
});

// Build sync execution instruction
const syncIx = smartAccount.instructions.executeTransactionSync({
settingsPda,
numSigners: 1,
accountIndex: 0,
instructions,
instruction_accounts: accounts,
});

// Send as a single transaction
const msg = new TransactionMessage({
payerKey: payer.publicKey,
recentBlockhash: blockhash,
instructions: [syncIx],
}).compileToV0Message();
const tx = new VersionedTransaction(msg);
tx.sign([payer]);
await rpc.sendRawTransaction(tx.serialize());
```

See `wallet-send-sync.ts` for a complete example.

## Smart Wallet Sends — Async Proposal Flow

Multi-step governance flow. Each step must be confirmed before the next.

```typescript
// Read current transaction index
const settings = await smartAccount.accounts.Settings.fromAccountAddress(
rpc, settingsPda
);
const txIndex = BigInt(settings.transactionIndex.toString()) + 1n;

// 1. Create transaction
await smartAccount.rpc.createTransaction({
connection: rpc, feePayer: payer, settingsPda,
transactionIndex: txIndex, creator: payer.publicKey,
accountIndex: 0, ephemeralSigners: 0,
transactionMessage: new TransactionMessage({
payerKey: walletPda,
recentBlockhash: blockhash,
instructions: [transferIx],
}),
});

// 2. Create proposal
await smartAccount.rpc.createProposal({
connection: rpc, feePayer: payer, settingsPda,
transactionIndex: txIndex, creator: payer,
});

// 3. Approve (repeat for each signer up to threshold)
await smartAccount.rpc.approveProposal({
connection: rpc, feePayer: payer, settingsPda,
transactionIndex: txIndex, signer: payer,
});

// 4. Execute
await smartAccount.rpc.executeTransaction({
connection: rpc, feePayer: payer, settingsPda,
transactionIndex: txIndex, signer: payer.publicKey,
signers: [payer],
});
```

See `wallet-send-async.ts` for a complete example.

## Running the Examples

```bash
npm install

# Set RPC endpoint (defaults to localhost)
export RPC_URL="https://devnet.helius-rpc.com?api-key=YOUR_KEY"

# Fund a smart wallet with Light Tokens
npx tsx fund-wallet.ts

# Smart wallet sends LTs (sync — single transaction)
npx tsx wallet-send-sync.ts

# Smart wallet sends LTs (async — proposal flow)
npx tsx wallet-send-async.ts

# Run full integration test (all 3 flows)
npx tsx squads-light-token.test.ts
```

## Dependencies

- `@lightprotocol/compressed-token` — Light Token SDK
- `@lightprotocol/stateless.js` — Light Protocol RPC client
- `@sqds/smart-account` — Squads Smart Account SDK
- `@solana/web3.js` — Solana web3 (peer dependency)

## Program IDs

| Program | ID |
|---------|-----|
| Squads Smart Account | `SMRTzfY6DfH5ik3TKiyLFfXexV8uSG3d2UksSCYdunG` |
| Light Compressed Token | `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` |
| Light System Program | `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` |
17 changes: 17 additions & 0 deletions toolkits/squads-smart-wallet/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "light-token-toolkit-squads",
"version": "1.0.0",
"description": "Light Token Squads Smart Wallet Integration",
"type": "module",
"scripts": {
"fund-wallet": "tsx fund-wallet.ts",
"wallet-send-sync": "tsx wallet-send-sync.ts",
"wallet-send-async": "tsx wallet-send-async.ts",
"test": "tsx squads-light-token.test.ts"
},
"dependencies": {
"@lightprotocol/compressed-token": "^0.23.0-beta.9",
"@lightprotocol/stateless.js": "^0.23.0-beta.9",
"@sqds/smart-account": "*"
}
}
Loading