From 877d5746d48a035cdcd184aff203c20a5c0a1795 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 23 Feb 2026 17:59:43 +0000 Subject: [PATCH] Add sponsor-rent-top-ups toolkit TypeScript and Rust examples showing rent top-up sponsorship where a fee payer covers rent-exemption so users hold no SOL. Fix dead-code crash in setup.ts (module-scope createRpc() call that fired on import before the symbol was available). --- toolkits/README.md | 6 ++ toolkits/sponsor-rent-top-ups/README.md | 39 +++++++++++ toolkits/sponsor-rent-top-ups/rust/setup.rs | 67 +++++++++++++++++++ .../rust/sponsor-top-ups.rs | 52 ++++++++++++++ .../typescript/package.json | 13 ++++ .../sponsor-rent-top-ups/typescript/setup.ts | 43 ++++++++++++ .../typescript/sponsor-top-ups.ts | 49 ++++++++++++++ .../typescript/tsconfig.json | 15 +++++ 8 files changed, 284 insertions(+) create mode 100644 toolkits/sponsor-rent-top-ups/README.md create mode 100644 toolkits/sponsor-rent-top-ups/rust/setup.rs create mode 100644 toolkits/sponsor-rent-top-ups/rust/sponsor-top-ups.rs create mode 100644 toolkits/sponsor-rent-top-ups/typescript/package.json create mode 100644 toolkits/sponsor-rent-top-ups/typescript/setup.ts create mode 100644 toolkits/sponsor-rent-top-ups/typescript/sponsor-top-ups.ts create mode 100644 toolkits/sponsor-rent-top-ups/typescript/tsconfig.json diff --git a/toolkits/README.md b/toolkits/README.md index 62bad80..8046ae2 100644 --- a/toolkits/README.md +++ b/toolkits/README.md @@ -23,6 +23,12 @@ Light-token operations signed with [Privy](https://privy.io) wallets. Server-sid [Rust program example to stream mint events](streaming-tokens/) of the Light-Token Program. +### Sponsor Rent Top-Ups + +Sponsor rent top-ups for users by setting your application as the fee payer. +- **[TypeScript](sponsor-rent-top-ups/typescript/)** - Sponsored Light transfer +- **[Rust](sponsor-rent-top-ups/rust/)** - Sponsored Light transfer + ## Documentation Learn more [about Light-Token here](https://www.zkcompression.com/light-token/welcome). diff --git a/toolkits/sponsor-rent-top-ups/README.md b/toolkits/sponsor-rent-top-ups/README.md new file mode 100644 index 0000000..689224b --- /dev/null +++ b/toolkits/sponsor-rent-top-ups/README.md @@ -0,0 +1,39 @@ +# Sponsor Rent Top-Ups + +Light Token sponsors rent-exemption for Solana accounts and keeps them active through periodic top-ups paid by the fee payer. Set your application as the fee payer to abstract away holding SOL from your users — a rent-free experience similar to transaction fee sponsorship. + +- **[TypeScript](typescript/)** — Sponsored transfer using `@lightprotocol/compressed-token` +- **[Rust](rust/)** — Sponsored transfer using `light-sdk-client` + +Set the `feePayer` field to the public key of the account that will pay the top-ups. Any account that signs the transaction can be the fee payer. + +1. Build the transfer instruction with your server as the `feePayer` +2. User signs to authorize the transfer (no SOL needed) +3. Fee payer covers rent top-ups when the transaction lands + +> You can set the fee payer to any signing account on any transaction with Light Token. + +## Source files + +- **[sponsor-top-ups.ts](typescript/sponsor-top-ups.ts)** / **[sponsor-top-ups.rs](rust/sponsor-top-ups.rs)** — Sponsored transfer: creates recipient ATA, transfers tokens with sponsor as fee payer +- **[setup.ts](typescript/setup.ts)** / **[setup.rs](rust/setup.rs)** — Test setup: creates SPL mint, mints tokens, wraps into Light + +## Setup + +```bash +cd typescript +npm install +``` + +For localnet: + +```bash +npm i -g @lightprotocol/zk-compression-cli@beta +light test-validator +npm run sponsor-top-ups +``` + +## Documentation + +- [Sponsor rent top-ups](https://www.zkcompression.com/light-token/toolkits/sponsor-top-ups) +- [Light Token overview](https://www.zkcompression.com/light-token/welcome) diff --git a/toolkits/sponsor-rent-top-ups/rust/setup.rs b/toolkits/sponsor-rent-top-ups/rust/setup.rs new file mode 100644 index 0000000..b0fbe9d --- /dev/null +++ b/toolkits/sponsor-rent-top-ups/rust/setup.rs @@ -0,0 +1,67 @@ +// Test setup: creates an SPL mint, funds the sponsor, wraps into Light. +// In production the mint already exists (e.g. USDC) and the sender +// already holds Light tokens. + +use anchor_spl::token::spl_token; +use light_client::rpc::Rpc; +use light_program_test::LightProgramTest; +use light_token::{ + instruction::{ + get_associated_token_address, CreateAssociatedTokenAccount, SplInterface, + TransferInterface, LIGHT_TOKEN_PROGRAM_ID, + }, + spl_interface::find_spl_interface_pda_with_index, +}; +use rust_client::{setup_spl_associated_token_account, setup_spl_mint}; +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; + +pub struct SetupResult { + pub mint: Pubkey, + pub sender_ata: Pubkey, +} + +pub async fn setup( + rpc: &mut LightProgramTest, + sponsor: &Keypair, + sender: &Keypair, +) -> Result> { + let decimals = 6u8; + let amount = 1_000_000u64; + + let mint = setup_spl_mint(rpc, sponsor, decimals).await; + let sponsor_spl_ata = setup_spl_associated_token_account( + rpc, sponsor, &mint, &sponsor.pubkey(), amount, + ).await; + let (interface_pda, interface_bump) = find_spl_interface_pda_with_index(&mint, 0, false); + + let sender_ata = get_associated_token_address(&sender.pubkey(), &mint); + let create_ata_ix = + CreateAssociatedTokenAccount::new(sponsor.pubkey(), sender.pubkey(), mint).instruction()?; + rpc.create_and_send_transaction(&[create_ata_ix], &sponsor.pubkey(), &[sponsor]) + .await?; + + let spl_interface = SplInterface { + mint, + spl_token_program: spl_token::ID, + spl_interface_pda: interface_pda, + spl_interface_pda_bump: interface_bump, + }; + + let wrap_ix = TransferInterface { + source: sponsor_spl_ata, + destination: sender_ata, + amount, + decimals, + authority: sponsor.pubkey(), + payer: sponsor.pubkey(), + spl_interface: Some(spl_interface), + source_owner: spl_token::ID, + destination_owner: LIGHT_TOKEN_PROGRAM_ID, + } + .instruction()?; + + rpc.create_and_send_transaction(&[wrap_ix], &sponsor.pubkey(), &[sponsor]) + .await?; + + Ok(SetupResult { mint, sender_ata }) +} diff --git a/toolkits/sponsor-rent-top-ups/rust/sponsor-top-ups.rs b/toolkits/sponsor-rent-top-ups/rust/sponsor-top-ups.rs new file mode 100644 index 0000000..2babc26 --- /dev/null +++ b/toolkits/sponsor-rent-top-ups/rust/sponsor-top-ups.rs @@ -0,0 +1,52 @@ +mod setup; + +use light_client::rpc::Rpc; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token::instruction::{ + get_associated_token_address, CreateAssociatedTokenAccount, + TransferInterface, LIGHT_TOKEN_PROGRAM_ID, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(true, None)).await?; + + // Top-Up Sponsor: your application server, pays SOL for rent top-ups + let sponsor = rpc.get_payer().insecure_clone(); + + // User: only signs to authorize the transfer + let sender = Keypair::new(); + + let setup::SetupResult { mint, sender_ata } = + setup::setup(&mut rpc, &sponsor, &sender).await?; + + // Create recipient associated token account + let recipient = Keypair::new(); + let recipient_ata = get_associated_token_address(&recipient.pubkey(), &mint); + let create_ata_ix = + CreateAssociatedTokenAccount::new(sponsor.pubkey(), recipient.pubkey(), mint).instruction()?; + rpc.create_and_send_transaction(&[create_ata_ix], &sponsor.pubkey(), &[&sponsor]) + .await?; + + let transfer_ix = TransferInterface { + source: sender_ata, + destination: recipient_ata, + amount: 500_000, + decimals: 6, + authority: sender.pubkey(), + payer: sponsor.pubkey(), + spl_interface: None, + source_owner: LIGHT_TOKEN_PROGRAM_ID, + destination_owner: LIGHT_TOKEN_PROGRAM_ID, + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[transfer_ix], &sponsor.pubkey(), &[&sponsor, &sender]) + .await?; + + println!("Tx: {sig}"); + + Ok(()) +} diff --git a/toolkits/sponsor-rent-top-ups/typescript/package.json b/toolkits/sponsor-rent-top-ups/typescript/package.json new file mode 100644 index 0000000..c198245 --- /dev/null +++ b/toolkits/sponsor-rent-top-ups/typescript/package.json @@ -0,0 +1,13 @@ +{ + "name": "light-token-toolkit-sponsor-rent-top-ups", + "version": "1.0.0", + "description": "Sponsor rent top-ups for users", + "type": "module", + "scripts": { + "sponsor-top-ups": "tsx sponsor-top-ups.ts" + }, + "dependencies": { + "@lightprotocol/compressed-token": "^0.23.0-beta.9", + "@lightprotocol/stateless.js": "^0.23.0-beta.9" + } +} diff --git a/toolkits/sponsor-rent-top-ups/typescript/setup.ts b/toolkits/sponsor-rent-top-ups/typescript/setup.ts new file mode 100644 index 0000000..f7ca7ea --- /dev/null +++ b/toolkits/sponsor-rent-top-ups/typescript/setup.ts @@ -0,0 +1,43 @@ +// Test setup: creates an SPL mint, funds the sponsor, wraps into Light. +// In production the mint already exists (e.g. USDC) and the sender +// already holds Light tokens. Use wrap.ts to wrap SPL tokens into Light tokens. +// Use unwrap.ts to unwrap Light tokens into SPL tokens. + +import { Keypair } from "@solana/web3.js"; +import { Rpc } from "@lightprotocol/stateless.js"; +import { + createMintInterface, + createAtaInterface, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; +import { wrap } from "@lightprotocol/compressed-token/unified"; +import { + TOKEN_PROGRAM_ID, + createAssociatedTokenAccount, + mintTo, +} from "@solana/spl-token"; + +export async function setup( + rpc: Rpc, + sponsor: Keypair, + sender: Keypair, +) { + const decimals = 6; + const amount = 1_000_000; + + const { mint } = await createMintInterface( + rpc, sponsor, sponsor, null, decimals, + undefined, undefined, TOKEN_PROGRAM_ID, + ); + + const sponsorSplAta = await createAssociatedTokenAccount( + rpc, sponsor, mint, sponsor.publicKey, undefined, TOKEN_PROGRAM_ID, + ); + await mintTo(rpc, sponsor, mint, sponsorSplAta, sponsor, amount); + + await createAtaInterface(rpc, sponsor, mint, sender.publicKey); + const senderAta = getAssociatedTokenAddressInterface(mint, sender.publicKey); + await wrap(rpc, sponsor, sponsorSplAta, senderAta, sponsor, mint, BigInt(amount)); + + return { mint, senderAta }; +} diff --git a/toolkits/sponsor-rent-top-ups/typescript/sponsor-top-ups.ts b/toolkits/sponsor-rent-top-ups/typescript/sponsor-top-ups.ts new file mode 100644 index 0000000..5f5466f --- /dev/null +++ b/toolkits/sponsor-rent-top-ups/typescript/sponsor-top-ups.ts @@ -0,0 +1,49 @@ +import "dotenv/config"; +import { Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { + createAtaInterface, + createLightTokenTransferInstruction, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; +import { setup } from "./setup.js"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +// Top-Up Sponsor: your application server, pays SOL for rent top-ups +const sponsor = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")), + ), +); + +// User: only signs to authorize the transfer +const sender = Keypair.generate(); + +(async function () { + const { mint, senderAta } = await setup(rpc, sponsor, sender); + + // Create recipient associated token account + const recipient = Keypair.generate(); + await createAtaInterface(rpc, sponsor, mint, recipient.publicKey); + const recipientAta = getAssociatedTokenAddressInterface(mint, recipient.publicKey); + + const ix = createLightTokenTransferInstruction( + senderAta, + recipientAta, + sender.publicKey, + 500_000, + sponsor.publicKey, + ); + + const tx = new Transaction().add(ix); + const sig = await sendAndConfirmTransaction(rpc, tx, [sponsor, sender]); + + console.log("Tx:", sig); +})(); diff --git a/toolkits/sponsor-rent-top-ups/typescript/tsconfig.json b/toolkits/sponsor-rent-top-ups/typescript/tsconfig.json new file mode 100644 index 0000000..de8ab73 --- /dev/null +++ b/toolkits/sponsor-rent-top-ups/typescript/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "Node16", + "moduleResolution": "Node16", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "./dist" + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +}