Interacting with L2PS
This guide covers sending private transactions and accessing transaction history through the L2PS SDK.
Sending Private Transactions
Basic Transaction
import { L2PSEncryption, L2PSService } from '@kynesyslabs/demosdk/l2ps'
import { Wallet } from '@kynesyslabs/demosdk'
// Initialize wallet and L2PS
const wallet = new Wallet(mnemonic)
const l2ps = new L2PSEncryption(aesKey, iv)
// Create inner transaction
const innerTx = {
type: "native",
from: wallet.address,
to: "0xRecipient...",
amount: 10,
nonce: await getNextNonce(wallet.address),
timestamp: Date.now()
}
// Sign the inner transaction
const signature = await wallet.signTransaction(innerTx)
// Encrypt the transaction
const encryptedBlob = l2ps.encrypt(innerTx)
// Send to L2PS network
const result = await L2PSService.sendTransaction({
nodeUrl: "http://127.0.0.1:53550",
l2psUid: "testnet_l2ps_001",
encryptedData: encryptedBlob,
signature: signature,
from: wallet.address
})
console.log("Transaction hash:", result.hash)
console.log("Status:", result.status) // "executed"
Transaction with Fee
Every L2PS transaction costs 1 DEM as a flat fee. Ensure the sender has amount + 1 DEM available.
// For a 10 DEM transfer, sender needs at least 11 DEM
const amount = 10
const fee = 1
const requiredBalance = amount + fee
// Check balance first
const balance = await getBalance(wallet.address)
if (balance < requiredBalance) {
throw new Error(`Insufficient balance: need ${requiredBalance}, have ${balance}`)
}
Fetching Transaction History
L2PS transaction history requires authenticated access - only the address owner can view their transactions.
Authenticated History Request
// Create authentication signature
const timestamp = Date.now()
const message = `getL2PSHistory:${wallet.address}:${timestamp}`
const authSignature = await wallet.signMessage(message)
// Request history
const history = await L2PSService.getAccountTransactions({
nodeUrl: "http://127.0.0.1:53550",
l2psUid: "testnet_l2ps_001",
address: wallet.address,
timestamp: timestamp,
signature: authSignature
})
// Process transactions
for (const tx of history) {
console.log(`${tx.hash}: ${tx.from} → ${tx.to} (${tx.amount} DEM) [${tx.status}]`)
}
If the signature doesn’t match the requested address, the node returns 403 Access Denied.
Checking Transaction Status
Status Polling
async function waitForConfirmation(txHash: string, maxAttempts = 30) {
for (let i = 0; i < maxAttempts; i++) {
const tx = await L2PSService.getTransaction(nodeUrl, l2psUid, txHash)
if (tx.status === 'confirmed') {
console.log("Transaction confirmed!")
return tx
}
console.log(`Status: ${tx.status} (attempt ${i + 1}/${maxAttempts})`)
await sleep(2000) // Wait 2 seconds between checks
}
throw new Error("Transaction not confirmed in time")
}
Status Meanings
| Status | Description | What It Means |
|---|
executed | Validated locally | Balance reserved, awaiting batch |
batched | In L1 batch | Included in ZK-proven batch, awaiting block |
confirmed | L1 finalized | Transaction is permanent and immutable |
Error Handling
Common Errors
try {
const result = await L2PSService.sendTransaction(...)
} catch (error) {
switch (error.code) {
case 'DECRYPTION_FAILED':
console.error("Key mismatch - check AES key and IV match node config")
break
case 'INSUFFICIENT_BALANCE':
console.error("Need amount + 1 DEM for fee")
break
case 'INVALID_NONCE':
console.error("Transaction nonce already used")
break
case 'SIGNATURE_INVALID':
console.error("Transaction signature verification failed")
break
case 'L2PS_NOT_FOUND':
console.error("L2PS network UID not recognized by node")
break
default:
console.error("Unknown error:", error.message)
}
}
Key Mismatch Detection
Debugging Decryption Errors
If you see “DECRYPTION_FAILED” errors:
- Verify key lengths: AES key should be 64 hex chars, IV should be 32 hex chars
- Check for whitespace: Keys should have no trailing newlines or spaces
- Compare with node: Copy keys directly from node config files
# Check key format
cat data/l2ps/testnet_l2ps_001/private_key.txt | wc -c # Should be 64
cat data/l2ps/testnet_l2ps_001/iv.txt | wc -c # Should be 32
Complete Example
import { L2PSEncryption, L2PSService } from '@kynesyslabs/demosdk/l2ps'
import { Wallet } from '@kynesyslabs/demosdk'
async function sendPrivateTransaction() {
// Setup
const wallet = new Wallet(process.env.MNEMONIC)
const l2ps = new L2PSEncryption(
process.env.L2PS_AES_KEY,
process.env.L2PS_IV
)
const nodeUrl = process.env.NODE_URL
const l2psUid = process.env.L2PS_UID
// Create and encrypt transaction
const innerTx = {
type: "native",
from: wallet.address,
to: "0xRecipient...",
amount: 5,
nonce: 1,
timestamp: Date.now()
}
const signature = await wallet.signTransaction(innerTx)
const encrypted = l2ps.encrypt(innerTx)
// Send
const result = await L2PSService.sendTransaction({
nodeUrl,
l2psUid,
encryptedData: encrypted,
signature,
from: wallet.address
})
console.log("Sent:", result.hash)
// Wait for confirmation
let status = result.status
while (status !== 'confirmed') {
await new Promise(r => setTimeout(r, 5000))
const tx = await L2PSService.getTransaction(nodeUrl, l2psUid, result.hash)
status = tx.status
console.log("Status:", status)
}
console.log("Confirmed!")
}
sendPrivateTransaction().catch(console.error)
Next Steps