-
Notifications
You must be signed in to change notification settings - Fork 0
Add sponsor-rent-top-ups toolkit #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tilo-14
wants to merge
1
commit into
main
Choose a base branch
from
sponsor-rent-top-ups
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<SetupResult, Box<dyn std::error::Error>> { | ||
| 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 }) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<dyn std::error::Error>> { | ||
| 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(()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 }; | ||
| } |
49 changes: 49 additions & 0 deletions
49
toolkits/sponsor-rent-top-ups/typescript/sponsor-top-ups.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"] | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Missing workspace entry causes unresolvable dependencies at install time
The new
toolkits/sponsor-rent-top-ups/typescriptpackage is not added to the rootpackage.jsonworkspacesarray, unlike every other toolkit (e.g.toolkits/payments-and-wallets,toolkits/sign-with-privy/*). This meansnpm installfrom the repo root will not link this package into the workspace and will not hoist shared dependencies to it.Root cause and impact
The root
package.jsonlists shared dependencies (dotenv,@solana/web3.js,@solana/spl-token) and devDependencies (tsx) that all workspace toolkits rely on via hoisting. The existingtoolkits/payments-and-walletsworks because it's listed inworkspacesand inherits these.The new toolkit's own
package.jsononly declares@lightprotocol/compressed-tokenand@lightprotocol/stateless.js, but the code imports:dotenv/config(sponsor-top-ups.ts:1)@solana/web3.js(sponsor-top-ups.ts:2,setup.ts:6)@solana/spl-token(setup.ts:14-18)And the npm script uses
tsx(package.json:7).Without the workspace entry, running
npm installat the root won't set up this package, and runningcd typescript && npm installstandalone will only install the two declared@lightprotocolpackages. Theimport "dotenv/config"atsponsor-top-ups.ts:1will crash immediately withERR_MODULE_NOT_FOUND, andnpm run sponsor-top-upswill fail withtsx: command not found.Impact: The toolkit is broken out of the box for anyone following the README instructions (
cd typescript && npm install && npm run sponsor-top-ups).Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.