惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
GbyAI
GbyAI
SecWiki News
SecWiki News
Project Zero
Project Zero
C
Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
P
Privacy International News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Scott Helme
Scott Helme
A
Arctic Wolf
Security Latest
Security Latest
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy & Cybersecurity Law Blog
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
The Hacker News
The Hacker News
T
Tenable Blog
雷峰网
雷峰网
有赞技术团队
有赞技术团队
V
V2EX
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
T
Threatpost
AWS News Blog
AWS News Blog
L
LINUX DO - 热门话题
Application and Cybersecurity Blog
Application and Cybersecurity Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
SegmentFault 最新的问题
月光博客
月光博客
Spread Privacy
Spread Privacy
S
Secure Thoughts
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
Forbes - Security
Forbes - Security
T
The Exploit Database - CXSecurity.com
G
GRAHAM CLULEY
The Last Watchdog
The Last Watchdog
Y
Y Combinator Blog
I
Intezer
博客园 - 【当耐特】
B
Blog RSS Feed
Attack and Defense Labs
Attack and Defense Labs
I
InfoQ
博客园 - 叶小钗
Cyberwarzone
Cyberwarzone
V2EX - 技术
V2EX - 技术
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
H
Help Net Security
C
CERT Recently Published Vulnerability Notes

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
DUST Sponsorship on Midnight: How One Wallet Pays Fees for Another User's Transaction
Uroy Nwankwo · 2026-05-16 · via DEV Community

DUST sponsorship on Midnight lets a backend wallet pay transaction fees for users who do not have DUST yet. Every transaction on Midnight consumes DUST, a shielded, non-transferable capacity resource generated by holding NIGHT tokens. That design keeps fees predictable and privacy intact, but it creates onboarding friction: a brand-new wallet holds zero DUST. Without DUST, it cannot submit a single transaction, including the first one a user might want to make in your DApp.

DUST cannot be sent from one wallet to another. You cannot airdrop it, and there is no transferDUST() call.
The only way to cover fees for a wallet that has no DUST is through sponsorship. In this flow, a backend wallet with healthy DUST reserves pays the fees on behalf of the user without changing who signed the transaction or who owns its outputs.

Target audience: Developers building DApps on the Midnight network.

Prerequisites:

  • Familiarity with TypeScript
  • Basic understanding of the Midnight wallet SDK
  • A Midnight wallet setup with access to a proof server

Related reading: DUST architecture · Wallet SDK · Getting started

What you'll have by the end: A working model for DUST sponsorship, including the two-phase balancing flow, an Express sponsor service, the ownPublicKey() behavior in sponsored transactions, and the DUST monitoring rules needed to keep the sponsor wallet funded.


What DUST actually is

Before writing a single line of code, it helps to be precise about what you are working with.

DUST is not a token. It does not appear on a block explorer as a transferable asset. Think of it as a capacity resource, like bandwidth, generated continuously from your NIGHT holdings. The Midnight docs describe the analogy well: NIGHT is the solar panel, DUST is the electricity it produces.

A few key parameters on the Preview Testnet set the shape of that lifecycle:

Parameter Value What it means
night_dust_ratio 5,000,000,000 5 DUST generated per NIGHT held
generation_decay_rate 8,267 ~1 week to decay from max to zero
dust_grace_period 3 hours Final window after DUST hits zero

The DUST lifecycle moves through four phases:

  1. Generating: DUST accumulates toward a cap proportional to your NIGHT balance.
  2. Constant: DUST sits at its maximum while NIGHT remains unspent.
  3. Decaying: once the backing NIGHT UTXO is spent, DUST decays to zero over roughly one week.
  4. Grace: a 3-hour grace period allows one final transaction even at zero.

This lifecycle is entirely shielded. DUST UTXOs use commitments and nullifiers, just like Zswap. Spending one consumes the old UTXO and creates a new one with the updated value minus the fee.


The sponsorship flow

The SDK splits transaction balancing into independent phases, controlled by the tokenKindsToBalance parameter on balanceUnboundTransaction. This is the mechanism sponsorship relies on.

The flow has three steps:

Sequence diagram for DUST sponsorship

Step 1: The user balances their own tokens without DUST

The user calls balanceUnboundTransaction and explicitly excludes 'dust' from the tokenKindsToBalance array. This tells the SDK to settle shielded and unshielded inputs/outputs but leave the DUST portion open for someone else to fill.

import { WalletFacade } from '@midnight-ntwrk/wallet-sdk';

// userWallet is a WalletFacade instance, already synced
const transaction = await userWallet.transact(contractCall);

const userRecipe = await userWallet.balanceUnboundTransaction(
  transaction,
  {
    shieldedSecretKeys: userShieldedKeys,
    dustSecretKey: userDustKey,
  },
  {
    ttl: new Date(Date.now() + 30 * 60 * 1000), // 30-minute TTL
    tokenKindsToBalance: ['shielded', 'unshielded'], // 'dust' is deliberately omitted
  }
);

// Sign and finalize the user's portion
const userSigned = await userWallet.signRecipe(
  userRecipe,
  (payload) => userKeystore.signData(payload)
);
const userFinalized = await userWallet.finalizeRecipe(userSigned);

// Send userFinalized to the sponsor service
const response = await fetch('https://your-sponsor-service/sponsor', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ userFinalized }),
});

const { txHash } = await response.json();
console.log(`Transaction confirmed: ${txHash}`);

Enter fullscreen mode Exit fullscreen mode

The tokenKindsToBalance parameter is the key piece here. By leaving 'dust' out, the user is saying: "I'm settling my own tokens, but I'm not covering the fee. The sponsor will." The finalized transaction is complete from the user's perspective but is not yet submittable.

Step 2: The sponsor adds DUST fees

The sponsor service receives userFinalized and calls balanceFinalizedTransaction with tokenKindsToBalance: ['dust']. This time only DUST is balanced. The sponsor is not touching the user's shielded or unshielded tokens.

// sponsorWallet is a WalletFacade instance with healthy DUST reserves
const sponsorRecipe = await sponsorWallet.balanceFinalizedTransaction(
  userFinalized,
  {
    shieldedSecretKeys: sponsorShieldedKeys,
    dustSecretKey: sponsorDustKey,
  },
  {
    ttl: new Date(Date.now() + 30 * 60 * 1000),
    tokenKindsToBalance: ['dust'], // only DUST
  }
);

const sponsorSigned = await sponsorWallet.signRecipe(
  sponsorRecipe,
  (payload) => sponsorKeystore.signData(payload)
);
const sponsorFinalized = await sponsorWallet.finalizeRecipe(sponsorSigned);

Enter fullscreen mode Exit fullscreen mode

Step 3: The sponsor submits

const txHash = await sponsorWallet.submitTransaction(sponsorFinalized);
console.log(`Submitted: ${txHash}`);

Enter fullscreen mode Exit fullscreen mode

The transaction is now fully balanced. The user's tokens are accounted for, the sponsor's DUST covers the fee, and the combined transaction hits the network.


Full sponsor service implementation

Here is a complete Express-based sponsor service you can adapt for production.

// sponsor-service.ts
import express from 'express';
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk';
import type { FinalizedTransaction } from '@midnight-ntwrk/wallet-api';

const app = express();
app.use(express.json({ limit: '1mb' }));

// Sponsor wallet state

let sponsorWallet: WalletFacade;
let sponsorShieldedKeys: { secretKey: Uint8Array };
let sponsorDustKey: { secretKey: Uint8Array };
let sponsorKeystore: { signData: (payload: Uint8Array) => Promise<Uint8Array> };

async function initSponsorWallet(): Promise<void> {
  const seedPhrase = process.env.SPONSOR_SEED_PHRASE;
  if (!seedPhrase) throw new Error('SPONSOR_SEED_PHRASE is required');

  // Initialize the wallet facade with Preview Testnet config
  sponsorWallet = await WalletFacade.create({
    seedPhrase,
    networkId: process.env.NETWORK_ID ?? '0',
    rpcUrl: process.env.RPC_URL ?? 'https://rpc.midnight.network',
    indexerUrl: process.env.INDEXER_URL ?? 'https://indexer.midnight.network',
    proofServerUrl: process.env.PROOF_SERVER_URL ?? 'http://localhost:6300',
  });

  // Wait until the wallet has synced to the chain tip
  await sponsorWallet.waitForSync();

  // Derive keys. These come from your wallet initialization flow.
  sponsorShieldedKeys = { secretKey: sponsorWallet.shieldedSecretKey };
  sponsorDustKey = { secretKey: sponsorWallet.dustSecretKey };
  sponsorKeystore = {
    signData: (payload) => sponsorWallet.signWithUnshieldedKey(payload),
  };

  const state = await sponsorWallet.getState();
  console.log(`Sponsor wallet ready. DUST balance: ${state.dust.available}`);
}

// DUST monitoring

const DUST_LOW_WATERMARK = 100n; // alert threshold in DUST units

async function checkDustLevel(): Promise<void> {
  const state = await sponsorWallet.getState();
  const available = state.dust.available;

  if (available < DUST_LOW_WATERMARK) {
    console.warn(
      `Sponsor DUST low: ${available}. ` +
      `Add NIGHT to the sponsor wallet to regenerate.`
    );
    // In production: trigger a PagerDuty alert, Slack message, etc.
  }
}

// Sponsorship endpoint

app.post('/sponsor', async (req, res) => {
  const { userFinalized } = req.body as { userFinalized: FinalizedTransaction };

  if (!userFinalized) {
    return res.status(400).json({ error: 'userFinalized is required' });
  }

  try {
    await checkDustLevel();

    // Balance only the DUST portion. The user already balanced everything else.
    const sponsorRecipe = await sponsorWallet.balanceFinalizedTransaction(
      userFinalized,
      {
        shieldedSecretKeys: sponsorShieldedKeys,
        dustSecretKey: sponsorDustKey,
      },
      {
        ttl: new Date(Date.now() + 30 * 60 * 1000),
        tokenKindsToBalance: ['dust'],
      }
    );

    const sponsorSigned = await sponsorWallet.signRecipe(
      sponsorRecipe,
      (payload) => sponsorKeystore.signData(payload)
    );
    const sponsorFinalized = await sponsorWallet.finalizeRecipe(sponsorSigned);

    // Submit the fully balanced transaction
    const txHash = await sponsorWallet.submitTransaction(sponsorFinalized);

    res.json({ success: true, txHash });
  } catch (error) {
    console.error('Sponsorship failed:', error);
    res.status(500).json({ success: false, error: String(error) });
  }
});

// Health check 

app.get('/health', async (_req, res) => {
  const state = await sponsorWallet.getState();
  res.json({ dust: String(state.dust.available), synced: state.isSynced });
});

// Startup 

initSponsorWallet()
  .then(() => {
    app.listen(3001, () =>
      console.log('Sponsor service running on port 3001')
    );
  })
  .catch((err) => {
    console.error('Failed to initialize sponsor wallet:', err);
    process.exit(1);
  });

Enter fullscreen mode Exit fullscreen mode

Environment variables:

export SPONSOR_SEED_PHRASE="your twelve word seed phrase here"
export NETWORK_ID="0"                                 # Preview Testnet
export RPC_URL="https://rpc.midnight.network"
export INDEXER_URL="https://indexer.midnight.network"
export PROOF_SERVER_URL="http://localhost:6300"

Enter fullscreen mode Exit fullscreen mode

Install dependencies:

npm install @midnight-ntwrk/wallet-sdk @midnight-ntwrk/wallet-api express
npm install --save-dev @types/express tsx typescript

Enter fullscreen mode Exit fullscreen mode

Run the service:

npx tsx src/sponsor-service.ts

Enter fullscreen mode Exit fullscreen mode


ownPublicKey() in sponsored transactions

One question comes up consistently when developers first work with sponsored transactions: whose key does ownPublicKey() return when the sponsor wallet is doing the balancing?

The answer is the prover's key, always. This is by design.

ownPublicKey() reflects whoever generated the ZK proof for the transaction, not whoever paid the DUST fee. In a standard sponsored flow, the user proves their own transaction before sending it to the sponsor, so ownPublicKey() returns the user's coinPublicKey. The sponsor's identity never leaks into the transaction's cryptographic identity.

This matters for three reasons:

  • Address derivation: outputs from the transaction are owned by the prover's address, not the sponsor's.
  • UTXO ownership: the wallet that can later spend those outputs is the prover's wallet.
  • Balance queries: if you are tracking state after the transaction, query the prover's address.

Some advanced architectures use an explicit key override pattern, where a separate prover wallet generates ZK proofs and the sponsor handles only fee payment and submission. In that configuration, the override is active at balance time:

// With key override active on the sponsor wallet:
sponsorWallet.ownPublicKey();
// → returns overrideKeys.coinPublicKey (the prover's key)
// → NOT the sponsor wallet's own coinPublicKey

Enter fullscreen mode Exit fullscreen mode

The key override is useful when you have a dedicated proof-generation service that is separate from your fee-paying backend. The prover wallet handles cryptographic identity and proof generation; the sponsor wallet handles DUST and submission. ownPublicKey() always reflects the prover, regardless of which wallet calls it.


DUST regeneration vs depletion

Understanding the regeneration curve is essential for sizing your sponsor wallet correctly. Once you know the math, operating a sponsor service becomes straightforward.

The regeneration curve

Dust regeneration curve on Midnight Network

While NIGHT remains unspent, DUST generates toward its cap and stays there. The moment a backing NIGHT UTXO is spent, for example, if the sponsor wallet moves NIGHT to another address, the associated DUST begins decaying. The decay takes roughly one week.

Sizing your sponsor wallet

Variable Value (Preview Testnet)
DUST generated per NIGHT 5 DUST per generation cycle
Typical fee per transaction ~0.001–0.01 DUST
Decay time (max to zero) ~1 week
Grace period at zero 3 hours

As a rule of thumb: a wallet holding 100 NIGHT can sponsor tens of thousands of transactions before you need to think about replenishment. In practice, the constraint is more likely to be DUST decay from NIGHT movement than raw transaction volume.

Keeping the sponsor wallet healthy

Two practices keep a production sponsor service running smoothly:

Do not move NIGHT unless you need to. Every time you spend a backing NIGHT UTXO, even to consolidate, the associated DUST starts decaying. If you need to move NIGHT, do it during low-traffic periods and ensure the new NIGHT UTXO has time to build up DUST before the old one's decay expires.

Monitor DUST levels actively. The health endpoint in the service above returns the current DUST balance. Hook it up to whatever alerting you use, such as Datadog, PagerDuty, or a simple cron + Slack webhook, and set a low-watermark alert well before you hit the decay phase.

// Simple cron-style monitoring
setInterval(async () => {
  const state = await sponsorWallet.getState();
  const dustAvailable = state.dust.available;

  if (dustAvailable < DUST_LOW_WATERMARK) {
    await sendAlert(`Sponsor DUST at ${dustAvailable}. Replenish NIGHT.`);
  }
}, 5 * 60 * 1000); // check every 5 minutes

Enter fullscreen mode Exit fullscreen mode


Common mistakes

Mistake 1: Including 'dust' in the user's tokenKindsToBalance

// WRONG: fails if the user has no DUST
const recipe = await userWallet.balanceUnboundTransaction(
  transaction,
  keys,
  { tokenKindsToBalance: ['shielded', 'unshielded', 'dust'] }
  //                                                  ^^^^^ user has no DUST
);

Enter fullscreen mode Exit fullscreen mode

The fix: always omit 'dust' from the user's step and let the sponsor add it separately.

Mistake 2: The sponsor re-balances token types the user already settled

// WRONG: causes double-spend errors
const sponsorRecipe = await sponsorWallet.balanceFinalizedTransaction(
  userFinalized,
  keys,
  { tokenKindsToBalance: ['dust', 'shielded', 'unshielded'] }
  //                              ^^^^^^^^^^^^^^^^^^^^^^^^^ already done
);

Enter fullscreen mode Exit fullscreen mode

The fix: the sponsor's tokenKindsToBalance should be ['dust'] only.

Mistake 3: No TTL set

// WRONG: transaction may expire before submission
const recipe = await sponsorWallet.balanceFinalizedTransaction(
  userFinalized,
  keys,
  { tokenKindsToBalance: ['dust'] }
  // missing ttl
);

Enter fullscreen mode Exit fullscreen mode

The fix: always pass a TTL of 15–30 minutes for both the user step and the sponsor step. Without it, network congestion or a slow proof server can leave you with an expired transaction and a consumed DUST UTXO.

Mistake 4: Treating DUST as a transferable token

There is no transferDUST() call. DUST is generated from NIGHT and consumed as a resource. It exists only within the shielded context of the wallet that holds the corresponding NIGHT. The sponsorship flow described above is the only way to cover fees for another wallet.


Summary

Concept Key point
DUST Shielded capacity resource, not a token; generated from NIGHT holdings
Cold-start problem New wallets have zero DUST and cannot submit transactions
balanceUnboundTransaction User balances ['shielded', 'unshielded'] and omits 'dust'
balanceFinalizedTransaction Sponsor adds ['dust'] only
tokenKindsToBalance Controls which token types each party settles
ownPublicKey() Always returns the prover's key, not the sponsor's
DUST regeneration Grows while NIGHT is held; decays ~1 week after NIGHT is spent
Grace period 3 hours after DUST hits zero
Production pattern Express sponsor service + DUST monitoring + TTL on every step

DUST sponsorship solves the onboarding problem: new users can transact from their first session while your backend absorbs the fee cost. Once you understand the two-phase balancing approach, the implementation is straightforward. The operational overhead stays low as long as you monitor your sponsor wallet's DUST levels.


Next steps

You now have the sponsorship pattern and service structure. From here: