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
23 changes: 10 additions & 13 deletions .github/workflows/typescript-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,32 @@ env:
NODE_VERSION: "22"

jobs:
typescript-tests:
name: TypeScript Tests
localnet-examples:
name: Localnet Examples (TypeScript)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup environment
uses: ./.github/actions/setup
with:
example: typescript-client
example: .
solana-cli-version: ${{ env.SOLANA_CLI_VERSION }}
photon-indexer: "true"

- name: Install dependencies
working-directory: typescript-client
- name: Install dependencies (workspaces)
run: npm install

- name: Enforce latest Light SDK dependency spec
run: npm run ci:deps:light-sdk

- name: Start test validator
run: |
light test-validator &
npx -y @lightprotocol/zk-compression-cli@beta test-validator &
for i in $(seq 1 60); do
curl -s http://127.0.0.1:8784/health && break
sleep 2
done

- name: Run actions
working-directory: typescript-client
run: npm run test:actions

- name: Run instructions
working-directory: typescript-client
run: npm run test:instructions
- name: Run localnet examples sweep
run: npm run ci:examples:localnet
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"toolkits/sponsor-rent-top-ups/typescript"
],
"scripts": {
"toolkit:payments": "npm run -w toolkits/payments-and-wallets"
"toolkit:payments": "npm run -w toolkits/payments-and-wallets",
"ci:deps:light-sdk": "node scripts/ci/verify-light-sdk-deps.mjs",
"ci:examples:localnet": "bash scripts/ci/run-localnet-examples.sh"
},
"dependencies": {
"@lightprotocol/compressed-token": "beta",
Expand Down
62 changes: 62 additions & 0 deletions scripts/ci/run-localnet-examples.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -euo pipefail

export LIGHT_PROTOCOL_BETA=true

PASS=0
FAIL=0

run_script() {
local workspace="$1"
local script="$2"

echo "=== RUN ${workspace} :: ${script} ==="
if npm run -w "${workspace}" "${script}"; then
PASS=$((PASS + 1))
echo "=== PASS ${workspace} :: ${script} ==="
else
FAIL=$((FAIL + 1))
echo "=== FAIL ${workspace} :: ${script} ==="
fi
}

# Localnet-only TypeScript examples (self-contained, no external credentials).
run_script "typescript-client" "create-mint:action"
run_script "typescript-client" "create-mint:instruction"
run_script "typescript-client" "create-spl-mint:action"
run_script "typescript-client" "create-spl-mint:instruction"
run_script "typescript-client" "create-t22-mint:action"
run_script "typescript-client" "create-t22-mint:instruction"
run_script "typescript-client" "create-interface-pda:action"
run_script "typescript-client" "create-interface-pda:instruction"
run_script "typescript-client" "create-ata:action"
run_script "typescript-client" "create-ata:instruction"
run_script "typescript-client" "mint-to:action"
run_script "typescript-client" "mint-to:instruction"
run_script "typescript-client" "transfer-interface:action"
run_script "typescript-client" "transfer-interface:instruction"
run_script "typescript-client" "wrap:action"
run_script "typescript-client" "wrap:instruction"
run_script "typescript-client" "unwrap:action"
run_script "typescript-client" "unwrap:instruction"
run_script "typescript-client" "load-ata:action"
run_script "typescript-client" "load-ata:instruction"
run_script "typescript-client" "delegate:approve"
run_script "typescript-client" "delegate:revoke"

run_script "toolkits/payments-and-wallets" "register-spl-mint"
run_script "toolkits/payments-and-wallets" "receive"
run_script "toolkits/payments-and-wallets" "send-instruction"
run_script "toolkits/payments-and-wallets" "send-action"
run_script "toolkits/payments-and-wallets" "send-and-receive"
run_script "toolkits/payments-and-wallets" "get-balance"
run_script "toolkits/payments-and-wallets" "get-history"
run_script "toolkits/payments-and-wallets" "wrap"
run_script "toolkits/payments-and-wallets" "unwrap"

run_script "toolkits/sponsor-rent-top-ups/typescript" "sponsor-top-ups"

echo "SUMMARY PASS=${PASS} FAIL=${FAIL}"
if [[ "${FAIL}" -ne 0 ]]; then
exit 1
fi
50 changes: 50 additions & 0 deletions scripts/ci/verify-light-sdk-deps.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env node

import { readFileSync } from "fs";
import path from "path";
import { fileURLToPath } from "url";

const root = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"..",
".."
);
const rootPkg = JSON.parse(
readFileSync(path.join(root, "package.json"), "utf8")
);

const targets = [".", ...(rootPkg.workspaces ?? [])];
const sdkDeps = [
"@lightprotocol/compressed-token",
"@lightprotocol/stateless.js",
];

const violations = [];

for (const target of targets) {
const pkgPath = path.join(root, target, "package.json");
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
const depSets = [
pkg.dependencies ?? {},
pkg.devDependencies ?? {},
pkg.peerDependencies ?? {},
];

for (const depName of sdkDeps) {
const current = depSets.find(set => depName in set)?.[depName];
if (!current) continue;
if (current !== "beta") {
violations.push(
`${target}/package.json -> ${depName} must be "beta", found "${current}"`,
);
}
}
}

if (violations.length > 0) {
console.error("Light SDK dependency policy violations:");
for (const msg of violations) console.error(`- ${msg}`);
process.exit(1);
}

console.log("Light SDK dependency policy check passed.");
11 changes: 9 additions & 2 deletions toolkits/payments-and-wallets/comparison-spl-light.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,10 @@ const tx = new Transaction().add(

```typescript
const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);
await getOrCreateAtaInterface(rpc, payer, mint, recipient);
const destination = getAssociatedTokenAddressInterface(mint, recipient);

await transferInterface(rpc, payer, sourceAta, mint, recipient, owner, amount);
await transferInterface(rpc, payer, sourceAta, mint, destination, owner, amount);
```

**Light (instruction-level):**
Expand All @@ -175,16 +177,21 @@ await transferInterface(rpc, payer, sourceAta, mint, recipient, owner, amount);
import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
import {
createTransferInterfaceInstructions,
getOrCreateAtaInterface,
getAssociatedTokenAddressInterface,
sliceLast,
} from "@lightprotocol/compressed-token/unified";

await getOrCreateAtaInterface(rpc, payer, mint, recipient);
const destination = getAssociatedTokenAddressInterface(mint, recipient);

const batches = await createTransferInterfaceInstructions(
rpc,
payer.publicKey,
mint,
amount,
owner.publicKey,
recipient
destination
);
const { rest: loadBatches, last: transferBatch } = sliceLast(batches);

Expand Down
12 changes: 7 additions & 5 deletions toolkits/payments-and-wallets/get-balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,29 @@ const payer = Keypair.fromSecretKey(
const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey);
await wrap(rpc, payer, splAta, senderAta, payer, mint, BigInt(1000));

// Transfer to recipient
const recipient = Keypair.generate();
await createAtaInterface(rpc, payer, mint, recipient.publicKey);
const recipientAta = getAssociatedTokenAddressInterface(
mint,
recipient.publicKey
);
await transferInterface(
rpc,
payer,
senderAta,
mint,
recipient.publicKey,
recipientAta,
payer,
100
);

// Get recipient's balance
const recipientAta = getAssociatedTokenAddressInterface(
const recipientAtaForBalance = getAssociatedTokenAddressInterface(
mint,
recipient.publicKey
);
const { parsed: account } = await getAtaInterface(
rpc,
recipientAta,
recipientAtaForBalance,
recipient.publicKey,
mint
);
Expand Down
8 changes: 6 additions & 2 deletions toolkits/payments-and-wallets/get-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,18 @@ const payer = Keypair.fromSecretKey(
const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey);
await wrap(rpc, payer, splAta, senderAta, payer, mint, BigInt(1000));

// Transfer to recipient
const recipient = Keypair.generate();
await createAtaInterface(rpc, payer, mint, recipient.publicKey);
const recipientAta = getAssociatedTokenAddressInterface(
mint,
recipient.publicKey
);
await transferInterface(
rpc,
payer,
senderAta,
mint,
recipient.publicKey,
recipientAta,
payer,
100
);
Expand Down
4 changes: 2 additions & 2 deletions toolkits/payments-and-wallets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"unwrap": "tsx unwrap.ts"
},
"dependencies": {
"@lightprotocol/compressed-token": "^0.23.0-beta.9",
"@lightprotocol/stateless.js": "^0.23.0-beta.9"
"@lightprotocol/compressed-token": "beta",
"@lightprotocol/stateless.js": "beta"
}
}
14 changes: 6 additions & 8 deletions toolkits/payments-and-wallets/receive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,22 @@ const payer = Keypair.fromSecretKey(
const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey);
await wrap(rpc, payer, splAta, senderAta, payer, mint, BigInt(1_000_000));

// Transfer to a fresh recipient so they have cold tokens
const recipient = Keypair.generate();
await createAtaInterface(rpc, payer, mint, recipient.publicKey);
const recipientAta = getAssociatedTokenAddressInterface(
mint,
recipient.publicKey
);
await transferInterface(
rpc,
payer,
senderAta,
mint,
recipient.publicKey,
recipientAta,
payer,
500
);

// --- Receive: load creates ATA if needed + pulls cold state to hot ---
const recipientAta = getAssociatedTokenAddressInterface(
mint,
recipient.publicKey
);

// Returns TransactionInstruction[][]. Each inner array is one txn.
// Almost always one. Empty = noop.
const instructions = await createLoadAtaInstructions(
Expand Down
15 changes: 11 additions & 4 deletions toolkits/payments-and-wallets/register-spl-mint.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import "dotenv/config";
import {
Keypair,
PublicKey,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { LightTokenProgram } from "@lightprotocol/compressed-token";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { TOKEN_PROGRAM_ID, createMint } from "@solana/spl-token";
import { homedir } from "os";
import { readFileSync } from "fs";

Expand All @@ -24,8 +23,16 @@ const payer = Keypair.fromSecretKey(
);

(async function () {
// Replace with your existing SPL mint (e.g. USDC)
const mint = new PublicKey("YOUR_EXISTING_MINT_ADDRESS");
const mint = await createMint(
rpc,
payer,
payer.publicKey,
null,
9,
undefined,
undefined,
TOKEN_PROGRAM_ID
);

// One-time: Register the SPL interface PDA for this mint.
// This creates the omnibus account that holds SPL tokens when wrapped to light-token.
Expand Down
8 changes: 6 additions & 2 deletions toolkits/payments-and-wallets/send-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,18 @@ const payer = Keypair.fromSecretKey(
await wrap(rpc, payer, splAta, cTokenAta, payer, mint, BigInt(1_000_000));

const recipient = Keypair.generate();
await createAtaInterface(rpc, payer, mint, recipient.publicKey);
const recipientAta = getAssociatedTokenAddressInterface(
mint,
recipient.publicKey
);

// Handles loading cold balances, creates recipient ATA, transfers.
const sig = await transferInterface(
rpc,
payer,
cTokenAta,
mint,
recipient.publicKey,
recipientAta,
payer,
100
);
Expand Down
7 changes: 6 additions & 1 deletion toolkits/payments-and-wallets/send-and-receive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,17 @@ const payer = Keypair.fromSecretKey(

// 4. Transfer from payer to recipient
const recipient = Keypair.generate();
await createAtaInterface(rpc, payer, mint, recipient.publicKey);
const recipientAta = getAssociatedTokenAddressInterface(
mint,
recipient.publicKey
);
const txId = await transferInterface(
rpc,
payer,
senderAta,
mint,
recipient.publicKey,
recipientAta,
payer,
100
);
Expand Down
9 changes: 6 additions & 3 deletions toolkits/payments-and-wallets/send-instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,19 @@ const payer = Keypair.fromSecretKey(
await wrap(rpc, payer, splAta, cTokenAta, payer, mint, BigInt(1_000_000));

const recipient = Keypair.generate();
await createAtaInterface(rpc, payer, mint, recipient.publicKey);
const destination = getAssociatedTokenAddressInterface(
mint,
recipient.publicKey
);

// Returns TransactionInstruction[][]. Each inner array is one txn.
// Almost always this returns one atomic transaction.
const instructions = await createTransferInterfaceInstructions(
rpc,
payer.publicKey,
mint,
100,
payer.publicKey,
recipient.publicKey
destination
);

for (const ixs of instructions) {
Expand Down
Loading
Loading