閃電網路支付實戰:LNURLw 完整教學

深入理解 LNURLw 標準,包含 Channels.open()、Invoice.create() 等實際操作流程。

閃電網路支付實戰:LNURLw 完整教學

LNURLw 是閃電網路的重要標準之一,它提供了一種標準化的方式來發起和接收閃電支付。本教學將深入解析 LNURLw 標準,並說明 Channels.open()、Invoice.create() 等實際操作流程。

什麼是 LNURLw?

LNURLw (Lightning URL with Withdrawal) 是一個基於 LNURL 的協議擴展,允許:

LNURLw 工作流程

LNURLw 的完整工作流程如下:

┌─────────────┐     1. GET     ┌─────────────┐
│   用戶端    │ ──────────────→│   服務端    │
│ (錢包)     │                 │  (LNURLw)   │
│             │ ←────────────── │             │
└─────────────┘    2. JSON     └─────────────┘
                        │
                        │ 3. 解析 LNURLw
                        ↓
                 ┌─────────────┐
                 │  比特幣網路 │
                 └─────────────┘
                        │
                        │ 4. 發起閃電支付
                        ↓
┌─────────────┐     5. Invoice ┌─────────────┐
│   用戶端    │ ←─────────────│   服務端    │
│             │               │             │
└─────────────┘               └─────────────┘

LNURLw 格式

基礎 LNURLw

LNURLw 本質上是一個編碼的 URL:

// 原始 URL
https://example.com/lnurlw?tag=withdrawal& idempotency_key=abc123& min=1000& max=50000

// LNURLw 編碼(Base64url)
lnurl1EXAMPLE...

典型回應

當錢包請求 LNURLw 時,服務端返回 JSON:

{
  "tag": "withdrawal",
  "callback": "https://example.com/lnurlw/callback",
  "k1": "random_idempotency_key_12345",
  "min": 1000,
  "max": 50000,
  "defaultDescription": "Withdraw from Example Service"
}

Channels.open() 詳解

Channels.open() 是 LND (Lightning Network Daemon) API 的一部分,用於打開新的閃電通道。

基本語法

// LND gRPC 調用
const lnd = require('@lightninglabs/ln-rpc');
const rpc = lnd();

async function openChannel() {
  const response = await rpc.channelOpen({
    local_funding_amount: '50000',  // 頻道金額(satoshis)
    push_sats: '0',                  // 給對方的初始餘額
    target_conf: 6,                   // 目標區塊確認數
    sat_per_byte: '10',               // 每字節手續費
    private: false,                   // 是否私有頻道
    min_htlc_msat: '1',               // 最小 HTLC 金額
    remote_csv_delay: 144,            // 對方的 CSV 延遲
  });
  return response;
}

回應格式

{
  "funding_txid": "abc123...",
  "output_index": 0,
  "channel_point": "abc123...:0"
}

Invoice.create() 詳解

Invoice.create() 用於創建閃電發票(Payment Request)。

基本語法

async function createInvoice() {
  const response = await rpc.addInvoice({
    value: '1000',                    // 金額(satoshis)
    memo: 'Payment for Coffee',        // 備註
    expiry: '3600',                   // 過期時間(秒)
    route_hints: [],                  // 路由提示
    private: false,                    // 是否私有
  });
  return response;
}

回應格式

{
  "payment_request": "lnbc10n1...",
  "add_index": "12345",
  "payment_addr": "abcdef..."
}

實戰:實現 LNURLw 提現服務

後端實現 (```javascript

const crypto = require('Node.js)

crypto');

const Lnurl = require('lnurl');

// 1. 生成 LNURLw 端點

app.get('/api/lnurlw/withdraw', async (req, res) => {

const idempotencyKey = crypto.randomBytes(16).toString('hex');

const k1 = crypto.randomBytes(32).toString('hex');

// 儲存提現請求

await redis.setex(

lnurlw:${k1},

3600, // 1小時過期

JSON.stringify({

idempotencyKey,

maxAmount: 50000,

minAmount: 1000,

used: false

})

);

// 生成 LNURLw

const lnurlw = https://${req.host}/api/lnurlw/callback?tag=withdrawal&k1=${k1};

const encoded = Lnurl.encode(lnurlw);

res.json({

lnurl: encoded,

min: 1000,

max: 50000

});

});

// 2. 處理回調

app.get('/api/lnurlw/callback', async (req, res) => {

const { k1, pr } = req.query;

// 驗證請求

const withdrawRequest = JSON.parse(

await redis.get(lnurlw:${k1})

);

if (!withdrawRequest || withdrawRequest.used) {

return res.json({ status: 'ERROR', reason: 'Invalid or used LNURLw' });

}

// 解析支付請求

const decoded = Lnurl.decode(pr);

const invoice = await decodeInvoice(decoded);

// 驗證金額

if (invoice.satoshis > withdrawRequest.maxAmount ||

invoice.satoshis < withdrawRequest.minAmount) {

return res.json({ status: 'ERROR', reason: 'Amount out of range' });

}

// 標記為已使用

await redis.setex(

lnurlw:${k1},

60, // 短過期

JSON.stringify({ ...withdrawRequest, used: true })

);

// 廣播閃電支付

await sendPayment(pr);

res.json({ status: 'OK' });

});


### 前端實現 (錢包端)

// 1. 解析 LNURLw

async function handleLNURLw(lnurlwString) {

const decodedUrl = Lnurl.decode(lnurlwString);

// 請求服務端

const response = await fetch(decodedUrl);

const data = await response.json();

// 顯示提現金額選擇 UI

showWithdrawalUI(data.min, data.max, data.callback, data.k1);

}

// 2. 發起支付

async function initiateWithdrawal(callback, k1, amount) {

// 創建發票

const invoice = await createInvoice(amount);

// 發送給服務端

const response = await fetch(${callback}&amount=${amount}&pr=${encodeURIComponent(invoice)});

const result = await response.json();

if (result.status === 'OK') {

showSuccess('提現成功!');

} else {

showError(result.reason);

}

}


## LNURLw 安全考量

### 服務端安全

1. **HTTPS 強制**:所有 LNURLw 通信必須使用 HTTPS
2. **速率限制**:防止濫用和暴力攻擊
3. **金額驗證**:嚴格驗證請求金額範圍
4. ** idempotency key**:防止重放攻擊

### 客戶端安全

1. **驗證服務身份**:確認 LNURLw 來自可信服務
2. **金額確認**:支付前確認金額正確
3. **過期時間**:注意發票的過期時間

## 常見問題

### Q: LNURLw 和 LNURL 有什麼區別?
A: LNURLw 是 LNURL 的擴展,專門用於提現場景。LNURL 更通用,可以用於登入、認證等場景。

### Q: 為什麼我的 LNURLw 支付失敗?
A: 常見原因包括:頻道餘額不足、網路擁塞、發票過期、金額超出範圍等。

### Q: LNURLw 支付有手續費嗎?
A: 是的,閃電網路支付會有路由費用,通常遠低於鏈上交易。

### Q: 如何確保 LNURLw 的安全性?
A: 始終通過 HTTPS 連接、驗證服務端身份、使用知名錢包軟體。

## 結論

LNURLw 為閃電網路支付提供了標準化且用戶友好的方式。通過理解其工作原理和實際操作流程,開發者可以構建各種閃電網路應用,用戶也能更安全地使用閃電支付功能。

## 相關資源

- [LNURL 規範](https://github.com/lnurl/luds)
- [LND API 文檔](https://api.lightning.community/)
- [閃電網路官方網站](https://lightning.network/)

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!