Skip to content
Merged
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 "toolkits/nullifier-program"]
path = toolkits/nullifier-program
url = https://github.com/Lightprotocol/nullifier-program.git
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ Light token is a high-performance token standard that reduces the cost of mint a
| | | | Description |
|---------|--------|-------------|-------------|
| create-mint | [Action](typescript-client/actions/create-mint.ts) | [Instruction](typescript-client/instructions/create-mint.ts) | Create a light-token mint with metadata |
| create-spl-mint | [Action](typescript-client/actions/create-spl-mint.ts) | [Instruction](typescript-client/instructions/create-spl-mint.ts) | Create an SPL mint with SPL interface PDA |
| create-t22-mint | [Action](typescript-client/actions/create-t22-mint.ts) | [Instruction](typescript-client/instructions/create-t22-mint.ts) | Create a Token 2022 mint with SPL interface PDA |
| create-spl-interface | [Action](typescript-client/actions/create-spl-interface.ts) | [Instruction](typescript-client/instructions/create-spl-interface.ts) | Register SPL interface PDA for an existing mint |
| create-ata | [Action](typescript-client/actions/create-ata.ts) | [Instruction](typescript-client/instructions/create-ata.ts) | Create an associated light-token account |
| create-ata-explicit-rent-sponsor | [Action](typescript-client/actions/create-ata-explicit-rent-sponsor.ts) | | Create an ATA with explicit rent sponsor |
| load-ata | [Action](typescript-client/actions/load-ata.ts) | [Instruction](typescript-client/instructions/load-ata.ts) | Load token accounts from light-token, compressed tokens, SPL/T22 to one unified balance |
| mint-to | [Action](typescript-client/actions/mint-to.ts) | [Instruction](typescript-client/instructions/mint-to.ts) | Mint tokens to a light-account |
| transfer-interface | [Action](typescript-client/actions/transfer-interface.ts) | [Instruction](typescript-client/instructions/transfer-interface.ts) | Transfer between light-token, T22, and SPL accounts |
| wrap | [Action](typescript-client/actions/wrap.ts) | [Instruction](typescript-client/instructions/wrap.ts) | Wrap SPL/T22 to light-token |
| unwrap | [Action](typescript-client/actions/unwrap.ts) | [Instruction](typescript-client/instructions/unwrap.ts) | Unwrap light-token to SPL/T22 |
| approve | [Action](typescript-client/actions/delegate-approve.ts) | | Approve delegate |
| revoke | [Action](typescript-client/actions/delegate-revoke.ts) | | Revoke delegate |
| delegate-transfer | [Action](typescript-client/actions/delegate-transfer.ts) | | Delegate transfers tokens on behalf of owner |

### Rust

Expand Down
4 changes: 2 additions & 2 deletions toolkits/gasless-transactions/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ edition = "2021"
publish = false

[[bin]]
name = "sponsor-top-ups"
path = "sponsor-top-ups.rs"
name = "gasless-transfer"
path = "gasless-transfer.rs"

[dependencies]
light-token = "0.23.0"
Expand Down
1 change: 1 addition & 0 deletions toolkits/nullifier-program
Submodule nullifier-program added at 8a5e0c
11 changes: 9 additions & 2 deletions toolkits/payments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Light Token reduces account creation cost by 200x compared to SPL. Transfer cost
| [delegate-approve.ts](spend-permissions/delegate-approve.ts) | Let a delegate spend tokens on your behalf. | `approveInterface` |
| [delegate-revoke.ts](spend-permissions/delegate-revoke.ts) | Revoke delegate access. | `revokeInterface` |
| [delegate-check.ts](spend-permissions/delegate-check.ts) | Check current delegation status and remaining allowance. | `getAtaInterface` |
| [delegate-full-flow.ts](spend-permissions/delegate-full-flow.ts) | Approve, check, and revoke in one script. | `approveInterface`, `revokeInterface`, `getAtaInterface` |
| [delegate-transfer.ts](spend-permissions/delegate-transfer.ts) | Delegate transfers tokens on behalf of the owner. | `transferInterface` |

### Interop (wrap and unwrap)

Expand Down Expand Up @@ -84,7 +84,7 @@ Run any script:
npx tsx send/send-action.ts
npx tsx send/payment-with-memo.ts
npx tsx send/batch-send.ts
npx tsx spend-permissions/delegate-full-flow.ts
npx tsx spend-permissions/delegate-approve.ts
```

### Devnet
Expand All @@ -104,6 +104,13 @@ export const rpc = createRpc(RPC_URL);
// export const rpc = createRpc();
```

### Nullifier Program

For some use cases, such as sending payments, you might want to prevent your onchain instruction from being executed more than once. The [nullifier program](../nullifier-program/) utility solves this for you. We also deployed a reference implementation to public networks so you can get started quickly.
- **[TypeScript example](../nullifier-program/examples/action-create-nullifier.ts)**
- **[Rust example](../nullifier-program/examples/rust/src/main.rs)**
- **[Documentation](https://www.zkcompression.com/compressed-pdas/guides/how-to-create-nullifier-pdas)**

## Documentation

- [Payment flows overview](https://www.zkcompression.com/light-token/payments/overview)
Expand Down
10 changes: 6 additions & 4 deletions toolkits/payments/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "Light Token Payments Toolkit Examples",
"type": "module",
"scripts": {
"test:all": "tsx send/send-action.ts && tsx send/send-instruction.ts && tsx send/payment-with-memo.ts && tsx send/batch-send.ts && tsx send/sign-all-transactions.ts && tsx receive/receive.ts && tsx spend-permissions/delegate-approve.ts && tsx spend-permissions/delegate-revoke.ts && tsx spend-permissions/delegate-check.ts && tsx spend-permissions/delegate-transfer.ts",
"send-action": "tsx send/send-action.ts",
"send-instruction": "tsx send/send-instruction.ts",
"payment-with-memo": "tsx send/payment-with-memo.ts",
Expand All @@ -19,11 +20,12 @@
"delegate-approve": "tsx spend-permissions/delegate-approve.ts",
"delegate-revoke": "tsx spend-permissions/delegate-revoke.ts",
"delegate-check": "tsx spend-permissions/delegate-check.ts",
"delegate-full-flow": "tsx spend-permissions/delegate-full-flow.ts"
"delegate-transfer": "tsx spend-permissions/delegate-transfer.ts"
},
"dependencies": {
"@lightprotocol/compressed-token": "^0.23.0-beta.10",
"@lightprotocol/stateless.js": "^0.23.0-beta.10",
"@solana/spl-memo": "^0.2.5"
"@lightprotocol/compressed-token": "^0.23.0-beta.12",
"@lightprotocol/stateless.js": "^0.23.0-beta.12",
"@solana/spl-memo": "^0.2.5",
"@solana/spl-token": "^0.4.13"
}
}
4 changes: 2 additions & 2 deletions toolkits/payments/receive/receive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import {
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
transferInterface,
getAssociatedTokenAddressInterface,
createLoadAtaInstructions,
} from "@lightprotocol/compressed-token";
import { transferInterface } from "@lightprotocol/compressed-token/unified";
} from "@lightprotocol/compressed-token/unified";
import { rpc, payer, setup } from "../setup.js";

(async function () {
Expand Down
62 changes: 23 additions & 39 deletions toolkits/payments/send/batch-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,68 +4,52 @@ import {
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
createAtaInterfaceIdempotent,
getAssociatedTokenAddressInterface,
createTransferInterfaceInstructions,
getAtaInterface,
} from "@lightprotocol/compressed-token";
import {
createTransferToAccountInterfaceInstructions,
createLoadAtaInstructions,
getAssociatedTokenAddressInterface,
} from "@lightprotocol/compressed-token/unified";
import { rpc, payer, setup } from "../setup.js";

(async function () {
// Step 1: Setup — create mint and fund sender
const { mint } = await setup();

const recipients = Array.from({ length: 5 }, (_, i) => ({
address: Keypair.generate().publicKey,
amount: (i + 1) * 100,
}));

// Step 2: Create ATAs idempotently for sender + all recipients
await createAtaInterfaceIdempotent(rpc, payer, mint, payer.publicKey);
for (const { address } of recipients) {
await createAtaInterfaceIdempotent(rpc, payer, mint, address);
}

// Step 3: Derive ATA addresses
const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey);
const recipientAtas = recipients.map(({ address }) =>
getAssociatedTokenAddressInterface(mint, address)
);
const recipients = [
{ address: Keypair.generate().publicKey, amount: 100 },
{ address: Keypair.generate().publicKey, amount: 200 },
{ address: Keypair.generate().publicKey, amount: 300 },
];

// Step 4: Create transfer instructions using explicit-account variant
const COMPUTE_BUDGET_ID =
"ComputeBudget111111111111111111111111111111";
// Build transfer instructions for each recipient
const COMPUTE_BUDGET = "ComputeBudget111111111111111111111111111111";
const allTransferIxs = [];
let isFirst = true;
let hasComputeBudget = false;

for (const { address, amount } of recipients) {
const destination = getAssociatedTokenAddressInterface(mint, address);
const ixs = await createTransferToAccountInterfaceInstructions(
const instructions = await createTransferInterfaceInstructions(
rpc,
payer.publicKey,
mint,
amount,
payer.publicKey,
destination
address
);
// Deduplicate ComputeBudget across transfers.
for (const ix of ixs[0]) {
if (!isFirst && ix.programId.toBase58() === COMPUTE_BUDGET_ID)
continue;

for (const ix of instructions[instructions.length - 1]) {
// Deduplicate ComputeBudget instructions
if (ix.programId.toBase58() === COMPUTE_BUDGET) {
if (hasComputeBudget) continue;
hasComputeBudget = true;
}
allTransferIxs.push(ix);
}
isFirst = false;
}

// Step 5: Batch into single transaction
const batchTx = new Transaction().add(...allTransferIxs);
const sig = await sendAndConfirmTransaction(rpc, batchTx, [payer]);
// Send all transfers in a single transaction
const tx = new Transaction().add(...allTransferIxs);
const sig = await sendAndConfirmTransaction(rpc, tx, [payer]);
console.log("Batch tx:", sig);

// Step 6: Verify balances
// Verify balances
for (const { address, amount } of recipients) {
const ata = getAssociatedTokenAddressInterface(mint, address);
const { parsed } = await getAtaInterface(rpc, ata, address, mint);
Expand Down
35 changes: 9 additions & 26 deletions toolkits/payments/send/payment-with-memo.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import {
Keypair,
Transaction,
TransactionInstruction,
PublicKey,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
createTransferInterfaceInstructions,
sliceLast,
} from "@lightprotocol/compressed-token/unified";
import { createTransferInterfaceInstructions } from "@lightprotocol/compressed-token/unified";
import { createMemoInstruction } from "@solana/spl-memo";
import { rpc, payer, setup } from "../setup.js";

const MEMO_PROGRAM_ID = new PublicKey(
"MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
);

(async function () {
const { mint } = await setup();
const recipient = Keypair.generate();
Expand All @@ -28,28 +20,19 @@ const MEMO_PROGRAM_ID = new PublicKey(
recipient.publicKey
);

const { rest: loadInstructions, last: transferInstructions } =
sliceLast(instructions);
// Append memo to the transfer transaction (last batch)
const memoIx = createMemoInstruction("INV-2024-001");
instructions[instructions.length - 1].push(memoIx);

// Send load transactions first (if any)
for (const ixs of loadInstructions) {
let signature;
for (const ixs of instructions) {
const tx = new Transaction().add(...ixs);
await sendAndConfirmTransaction(rpc, tx, [payer]);
signature = await sendAndConfirmTransaction(rpc, tx, [payer]);
}

// Add memo to the transfer transaction
const memoIx = new TransactionInstruction({
keys: [],
programId: MEMO_PROGRAM_ID,
data: Buffer.from("INV-2024-001"),
});

const transferTx = new Transaction().add(...transferInstructions, memoIx);
const signature = await sendAndConfirmTransaction(rpc, transferTx, [payer]);
console.log("Tx with memo:", signature);

// Read memo back from transaction logs
const txDetails = await rpc.getTransaction(signature, {
const txDetails = await rpc.getTransaction(signature!, {
maxSupportedTransactionVersion: 0,
});

Expand Down
1 change: 0 additions & 1 deletion toolkits/payments/send/send-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ import { rpc, payer, setup } from "../setup.js";
payer,
100
);

console.log("Tx:", sig);
})();
2 changes: 1 addition & 1 deletion toolkits/payments/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const payer = Keypair.fromSecretKey(
);

/** Create SPL mint, fund payer, wrap into light-token ATA. */
export async function setup(amount = 1_000_000) {
export async function setup(amount = 1_000_000_000) {
const { mint } = await createMintInterface(
rpc,
payer,
Expand Down
6 changes: 3 additions & 3 deletions toolkits/payments/spend-permissions/delegate-approve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import { rpc, payer, setup } from "../setup.js";
(async function () {
const { mint, senderAta } = await setup();

// Approve: grant delegate permission to spend up to 500,000 tokens
// Approve: grant delegate permission to spend up to 500,000,000 tokens
const delegate = Keypair.generate();
const tx = await approveInterface(
rpc,
payer,
senderAta,
mint,
delegate.publicKey,
500_000,
500_000_000,
payer
);

console.log("Approved delegate:", delegate.publicKey.toBase58());
console.log("Allowance: 500,000 tokens");
console.log("Allowance: 500,000,000 tokens");
console.log("Tx:", tx);
})();
8 changes: 5 additions & 3 deletions toolkits/payments/spend-permissions/delegate-check.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Keypair } from "@solana/web3.js";
import { getAtaInterface } from "@lightprotocol/compressed-token";
import { approveInterface } from "@lightprotocol/compressed-token/unified";
import {
getAtaInterface,
approveInterface,
} from "@lightprotocol/compressed-token/unified";
import { rpc, payer, setup } from "../setup.js";

(async function () {
Expand Down Expand Up @@ -31,7 +33,7 @@ import { rpc, payer, setup } from "../setup.js";
senderAta,
mint,
delegate.publicKey,
500_000,
500_000_000,
payer
);

Expand Down
63 changes: 0 additions & 63 deletions toolkits/payments/spend-permissions/delegate-full-flow.ts

This file was deleted.

2 changes: 1 addition & 1 deletion toolkits/payments/spend-permissions/delegate-revoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { rpc, payer, setup } from "../setup.js";
senderAta,
mint,
delegate.publicKey,
500_000,
500_000_000,
payer
);
console.log("Approved delegate:", delegate.publicKey.toBase58());
Expand Down
Loading
Loading