Skip to content

Commit 27eb1e3

Browse files
author
tilo-14
committed
Add SPL vs Compressed comparison for Privy
1 parent a6f5dba commit 27eb1e3

1 file changed

Lines changed: 312 additions & 0 deletions

File tree

privy/COMPARISON.md

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
# SPL vs Compressed Token Code Comparison
2+
3+
### 1. Setup
4+
5+
**SPL:**
6+
7+
```typescript
8+
import { Connection } from "@solana/web3.js";
9+
10+
const connection = new Connection("https://api.mainnet-beta.solana.com");
11+
```
12+
13+
**Compressed:**
14+
15+
```typescript
16+
import { createRpc } from "@lightprotocol/stateless.js";
17+
18+
// Requires ZK Compression RPC (Helius, Triton)
19+
const rpc = createRpc(process.env.HELIUS_RPC_URL!);
20+
```
21+
22+
---
23+
24+
### 2. Get Balance
25+
26+
**SPL:**
27+
28+
```typescript
29+
import { getAssociatedTokenAddress, getAccount } from "@solana/spl-token";
30+
31+
const ata = await getAssociatedTokenAddress(mintPubkey, ownerPubkey);
32+
const account = await getAccount(connection, ata);
33+
console.log(account.amount);
34+
```
35+
36+
**Compressed:**
37+
38+
```typescript
39+
const accounts = await rpc.getCompressedTokenAccountsByOwner(ownerPubkey, {
40+
mint: mintPubkey,
41+
});
42+
const validItems = (accounts.items || []).filter(
43+
(item): item is NonNullable<typeof item> => item !== null
44+
);
45+
46+
// Aggregate balance across all compressed accounts
47+
const balance = validItems.reduce(
48+
(sum, acc) => sum + BigInt(acc.parsed.amount.toString()),
49+
0n
50+
);
51+
console.log(balance);
52+
```
53+
54+
---
55+
56+
### 3. Transfer
57+
58+
**SPL:**
59+
60+
```typescript
61+
import { getAssociatedTokenAddress, createTransferInstruction } from "@solana/spl-token";
62+
import { Transaction, PublicKey } from "@solana/web3.js";
63+
64+
const fromAta = await getAssociatedTokenAddress(mintPubkey, fromPubkey);
65+
const toAta = await getAssociatedTokenAddress(mintPubkey, toPubkey);
66+
67+
const instruction = createTransferInstruction(
68+
fromAta,
69+
toAta,
70+
fromPubkey,
71+
amount
72+
);
73+
74+
const transaction = new Transaction().add(instruction);
75+
const { blockhash } = await connection.getLatestBlockhash();
76+
transaction.recentBlockhash = blockhash;
77+
transaction.feePayer = fromPubkey;
78+
```
79+
80+
**Compressed:**
81+
82+
```typescript
83+
import { createRpc, bn } from "@lightprotocol/stateless.js";
84+
import {
85+
CompressedTokenProgram,
86+
selectMinCompressedTokenAccountsForTransfer,
87+
} from "@lightprotocol/compressed-token";
88+
import { Transaction, ComputeBudgetProgram } from "@solana/web3.js";
89+
90+
// 1. Get compressed token accounts
91+
const accounts = await rpc.getCompressedTokenAccountsByOwner(fromPubkey, {
92+
mint: mintPubkey,
93+
});
94+
const validItems = (accounts.items || []).filter(
95+
(item): item is NonNullable<typeof item> => item !== null
96+
);
97+
98+
// 2. Select minimum accounts needed
99+
const tokenAmount = bn(amount);
100+
const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
101+
validItems,
102+
tokenAmount
103+
);
104+
105+
// 3. Get validity proof
106+
const proof = await rpc.getValidityProof(
107+
inputAccounts.map((acc) => bn(acc.compressedAccount.hash))
108+
);
109+
110+
// 4. Build transfer instruction
111+
const instruction = await CompressedTokenProgram.transfer({
112+
payer: fromPubkey,
113+
inputCompressedTokenAccounts: inputAccounts,
114+
toAddress: toPubkey,
115+
amount: tokenAmount,
116+
recentInputStateRootIndices: proof.rootIndices,
117+
recentValidityProof: proof.compressedProof,
118+
});
119+
120+
// 5. Build transaction with compute budget
121+
const transaction = new Transaction();
122+
transaction.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }));
123+
transaction.add(instruction);
124+
125+
const { blockhash } = await rpc.getLatestBlockhash();
126+
transaction.recentBlockhash = blockhash;
127+
transaction.feePayer = fromPubkey;
128+
```
129+
130+
---
131+
132+
### 4. Compress
133+
134+
**SPL:**
135+
136+
N/A - SPL tokens are uncompressed by default.
137+
138+
**Compressed:**
139+
140+
```typescript
141+
import { createRpc, bn, selectStateTreeInfo } from "@lightprotocol/stateless.js";
142+
import {
143+
CompressedTokenProgram,
144+
getTokenPoolInfos,
145+
selectTokenPoolInfo,
146+
} from "@lightprotocol/compressed-token";
147+
import { getAssociatedTokenAddressSync, getAccount } from "@solana/spl-token";
148+
import { Transaction, ComputeBudgetProgram } from "@solana/web3.js";
149+
150+
// 1. Get source token account and verify balance
151+
const ownerAta = getAssociatedTokenAddressSync(mintPubkey, fromPubkey);
152+
const ataAccount = await getAccount(rpc, ownerAta);
153+
const tokenAmount = bn(amount);
154+
155+
if (ataAccount.amount < BigInt(tokenAmount.toString())) {
156+
throw new Error("Insufficient SPL balance");
157+
}
158+
159+
// 2. Get state tree and token pool info
160+
const stateTreeInfos = await rpc.getStateTreeInfos();
161+
const selectedTreeInfo = selectStateTreeInfo(stateTreeInfos);
162+
const tokenPoolInfos = await getTokenPoolInfos(rpc, mintPubkey);
163+
const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos);
164+
165+
// 3. Build compress instruction
166+
const instruction = await CompressedTokenProgram.compress({
167+
payer: fromPubkey,
168+
owner: fromPubkey,
169+
source: ownerAta,
170+
toAddress: toPubkey,
171+
mint: mintPubkey,
172+
amount: tokenAmount,
173+
outputStateTreeInfo: selectedTreeInfo,
174+
tokenPoolInfo,
175+
});
176+
177+
// 4. Build transaction with compute budget
178+
const transaction = new Transaction();
179+
transaction.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }));
180+
transaction.add(instruction);
181+
182+
const { blockhash } = await rpc.getLatestBlockhash();
183+
transaction.recentBlockhash = blockhash;
184+
transaction.feePayer = fromPubkey;
185+
```
186+
187+
---
188+
189+
### 5. Decompress
190+
191+
**SPL:**
192+
193+
N/A - SPL tokens are already uncompressed.
194+
195+
**Compressed:**
196+
197+
```typescript
198+
import { createRpc } from "@lightprotocol/stateless.js";
199+
import { decompress } from "@lightprotocol/compressed-token";
200+
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
201+
202+
// 1. Get destination ATA
203+
const ownerAta = getAssociatedTokenAddressSync(mintPubkey, fromPubkey);
204+
205+
// 2. Create dummy payer (only publicKey is used)
206+
const dummyPayer = {
207+
publicKey: fromPubkey,
208+
secretKey: new Uint8Array(64),
209+
} as any;
210+
211+
// 3. Intercept sendAndConfirmTransaction to use Privy signing
212+
const originalSendAndConfirm = (rpc as any).sendAndConfirmTransaction;
213+
(rpc as any).sendAndConfirmTransaction = async (tx: Transaction, signers: any[]) => {
214+
const signResult = await privy.wallets().solana().signTransaction(
215+
process.env.TREASURY_WALLET_ID!,
216+
{
217+
transaction: tx.serialize({ requireAllSignatures: false }),
218+
authorization_context: {
219+
authorization_private_keys: [process.env.TREASURY_AUTHORIZATION_KEY!],
220+
},
221+
}
222+
);
223+
224+
const signedTx = signResult.signed_transaction || signResult.signedTransaction;
225+
const signedTransaction = Buffer.from(signedTx, "base64");
226+
const signature = await rpc.sendRawTransaction(signedTransaction, {
227+
skipPreflight: false,
228+
preflightCommitment: "confirmed",
229+
});
230+
await rpc.confirmTransaction(signature, "confirmed");
231+
return signature;
232+
};
233+
234+
try {
235+
// 4. Use high-level decompress action
236+
const signature = await decompress(
237+
rpc,
238+
dummyPayer,
239+
mintPubkey,
240+
amount,
241+
dummyPayer,
242+
ownerAta
243+
);
244+
} finally {
245+
// Restore original function
246+
(rpc as any).sendAndConfirmTransaction = originalSendAndConfirm;
247+
}
248+
```
249+
250+
---
251+
252+
### 6. Sign with Privy
253+
254+
Privy signing works identically for both SPL and compressed:
255+
256+
```typescript
257+
import { PrivyClient } from "@privy-io/node";
258+
259+
const privy = new PrivyClient({
260+
appId: process.env.PRIVY_APP_ID!,
261+
appSecret: process.env.PRIVY_APP_SECRET!,
262+
});
263+
264+
const signResult = await privy.wallets().solana().signTransaction(
265+
process.env.TREASURY_WALLET_ID!,
266+
{
267+
transaction: transaction.serialize({ requireAllSignatures: false }),
268+
authorization_context: {
269+
authorization_private_keys: [process.env.TREASURY_AUTHORIZATION_KEY!],
270+
},
271+
}
272+
);
273+
274+
const signedTx = signResult.signed_transaction || signResult.signedTransaction;
275+
const signedTransaction = Buffer.from(signedTx, "base64");
276+
277+
// Send transaction
278+
const signature = await rpc.sendRawTransaction(signedTransaction, {
279+
skipPreflight: false,
280+
preflightCommitment: "confirmed",
281+
});
282+
await rpc.confirmTransaction(signature, "confirmed");
283+
```
284+
285+
---
286+
287+
### 7. Get Transaction History
288+
289+
**SPL:**
290+
291+
```typescript
292+
const signatures = await connection.getSignaturesForAddress(ownerPubkey, {
293+
limit: 10,
294+
});
295+
```
296+
297+
**Compressed:**
298+
299+
```typescript
300+
const signatures = await rpc.getCompressionSignaturesForTokenOwner(ownerPubkey);
301+
302+
const transactions = await Promise.all(
303+
signatures.items.slice(0, 10).map(async (sig) => {
304+
const txInfo = await rpc.getTransactionWithCompressionInfo(sig.signature);
305+
return {
306+
signature: sig.signature,
307+
slot: sig.slot,
308+
compressionInfo: txInfo?.compressionInfo,
309+
};
310+
})
311+
);
312+
```

0 commit comments

Comments
 (0)