Skip to main content

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

StatusDescriptionWhat It Means
executedValidated locallyBalance reserved, awaiting batch
batchedIn L1 batchIncluded in ZK-proven batch, awaiting block
confirmedL1 finalizedTransaction 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

If you see “DECRYPTION_FAILED” errors:
  1. Verify key lengths: AES key should be 64 hex chars, IV should be 32 hex chars
  2. Check for whitespace: Keys should have no trailing newlines or spaces
  3. 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