l402_middleware 2.3.4

A middleware library for rust that provides handler functions to accept microtransactions before serving ad-free content or any paid APIs.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
name: Integration Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Docker Compose
        run: |
          sudo curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
          sudo chmod +x /usr/local/bin/docker-compose
          docker-compose -f docker-compose.yml up -d bitcoind tor lndnode

      - name: Set up Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          override: true

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y pkg-config libssl-dev

      - name: Create env file
        run: |
          touch .env
          echo ROOT_KEY=ABDEGHKLMPTC >> .env
          cat .env

      - name: Run tests for LNURL
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: LNURL
          LNURL_ADDRESS: hello@getalby.com

      - name: Run tests for LNURL for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env: 
          LN_CLIENT_TYPE: LNURL
          LNURL_ADDRESS: hello@getalby.com

      - name: Verify Bitcoin daemon Service and create wallet
        run: |
          wallet_name="new_wallet"
          docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass createwallet $wallet_name
          address=$(docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass getnewaddress $wallet_name)
          echo "New Wallet Address: $address"
          docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress 101 "$address"
          docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress bcrt1pcngfxjdkf4r2h26k52dh5nunxg8m68uf4lkfhmfjvjj6agfkm5jqmftw4e 0.0001
          docker logs bitcoind

      - name: Verify LND node
        run: |
          identity_pubkey_lndnode=$(docker exec lndnode lncli -n regtest getinfo | jq -r '.identity_pubkey')
          echo "Pubkey: $identity_pubkey_lndnode"
          docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -generate 5
          docker logs lndnode

      - name: Extract Macaroon and Cert Paths
        run: |
          CONTAINER_NAME="lndnode"
          
          # Wait for LND to be ready
          echo "Waiting for LND to initialize..."
          sleep 10

          MACAROON_PATH="/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon"
          CERT_PATH="/root/.lnd/tls.cert"
          
          # Wait a bit more for LND to fully start
          echo "Checking if LND is ready..."
          for i in {1..30}; do
            if docker exec ${CONTAINER_NAME} lncli -n regtest getinfo > /dev/null 2>&1; then
              echo "LND is ready"
              break
            fi
            if [ $i -eq 30 ]; then
              echo "Warning: LND may not be fully ready, but continuing..."
            fi
            sleep 2
          done
          
          # Copy macaroon
          if docker exec ${CONTAINER_NAME} test -f "$MACAROON_PATH" 2>/dev/null; then
            docker cp ${CONTAINER_NAME}:$MACAROON_PATH ./admin.macaroon
            echo "✓ Extracted macaroon from $MACAROON_PATH"
          else
            echo "Error: Macaroon not found at $MACAROON_PATH"
            echo "Debug: Listing /root/.lnd structure:"
            docker exec ${CONTAINER_NAME} find /root/.lnd -type f 2>/dev/null | head -10 || true
            exit 1
          fi
          
          # Copy certificate
          if docker exec ${CONTAINER_NAME} test -f "$CERT_PATH" 2>/dev/null; then
            docker cp ${CONTAINER_NAME}:$CERT_PATH ./tls.cert
            echo "✓ Extracted certificate from $CERT_PATH"
          else
            echo "Error: Certificate not found at $CERT_PATH"
            echo "Debug: Listing /root/.lnd structure:"
            docker exec ${CONTAINER_NAME} ls -la /root/.lnd/ 2>/dev/null || true
            exit 1
          fi
          
          # Verify files were copied
          if [ -f "./admin.macaroon" ] && [ -f "./tls.cert" ]; then
            echo "✓ Successfully extracted macaroon and certificate"
            echo "MACAROON_PATH=./admin.macaroon" >> $GITHUB_ENV
            echo "CERT_PATH=./tls.cert" >> $GITHUB_ENV
          else
            echo "Error: Files were not copied successfully"
            exit 1
          fi

      - name: Run tests for LND
        run: cargo test --verbose
        env:
          LN_CLIENT_TYPE: LND
          LND_ADDRESS: 0.0.0.0:10009
          MACAROON_FILE_PATH: ${{ env.MACAROON_PATH }}
          CERT_FILE_PATH: ${{ env.CERT_PATH }}

      - name: Run tests for LND for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env:
          LN_CLIENT_TYPE: LND
          LND_ADDRESS: 0.0.0.0:10009
          MACAROON_FILE_PATH: ${{ env.MACAROON_PATH }}
          CERT_FILE_PATH: ${{ env.CERT_PATH }}

      - name: Wait for Tor to generate onion address
        run: |
          echo "Waiting for Tor to start and generate hidden service..."
          sleep 15
          
          # Based on docker-compose.yml Tor configuration:
          # HiddenServiceDir /var/lib/tor/hidden_service/
          # So the hostname file should be at: /var/lib/tor/hidden_service/hostname
          TOR_HOSTNAME_PATH="/var/lib/tor/hidden_service/hostname"
          
          max_attempts=30
          attempt=0
          ONION_ADDRESS=""
          
          while [ $attempt -lt $max_attempts ] && [ -z "$ONION_ADDRESS" ]; do
            # Check if Tor container is running
            if ! docker ps | grep -q tor; then
              echo "Tor container is not running"
              break
            fi
            
            # Check for hostname file at the configured path
            if docker exec tor test -f "$TOR_HOSTNAME_PATH" 2>/dev/null; then
              ONION_ADDRESS=$(docker exec tor cat "$TOR_HOSTNAME_PATH" 2>/dev/null | tr -d '\n\r')
              if [ -n "$ONION_ADDRESS" ]; then
                echo "✓ Found onion address: $ONION_ADDRESS"
                echo "LND_ONION_ADDRESS=$ONION_ADDRESS:10009" >> $GITHUB_ENV
                break
              fi
            fi
            
            attempt=$((attempt + 1))
            echo "Attempt $attempt/$max_attempts: Waiting for onion address..."
            if [ $attempt -eq 10 ]; then
              echo "Debug: Checking Tor directory structure:"
              docker exec tor ls -la /var/lib/tor/ 2>/dev/null || true
            fi
            sleep 2
          done
          
          if [ -z "$LND_ONION_ADDRESS" ]; then
            echo "Warning: Could not retrieve onion address after $max_attempts attempts"
            echo "Tor hidden service may take longer to generate, or may not be configured"
            echo "SOCKS5 proxy tests will be skipped"
          else
            echo "✓ Onion address retrieved: $LND_ONION_ADDRESS"
          fi

      - name: Test LND with SOCKS5 proxy (Tor connection)
        run: |
          if [ -n "$LND_ONION_ADDRESS" ]; then
            echo "Testing LND connection through Tor SOCKS5 proxy..."
            echo "Onion address: $LND_ONION_ADDRESS"
            echo "SOCKS5 proxy: 127.0.0.1:9050"
            cargo test --verbose || echo "SOCKS5 test completed (expected limitation noted)"
          else
            echo "Skipping SOCKS5 test - onion address not available"
          fi
        env:
          LN_CLIENT_TYPE: LND
          LND_ADDRESS: ${{ env.LND_ONION_ADDRESS }}
          SOCKS5_PROXY: 127.0.0.1:9050
          MACAROON_FILE_PATH: ${{ env.MACAROON_PATH }}
          CERT_FILE_PATH: ${{ env.CERT_PATH }}

      - name: Verify SOCKS5 proxy is accessible
        run: |
          echo "Testing SOCKS5 proxy connectivity..."
          # Test if Tor SOCKS5 proxy is accessible
          curl --socks5-hostname 127.0.0.1:9050 http://example.com > /dev/null 2>&1 && \
            echo "✓ SOCKS5 proxy is working" || \
            echo "⚠ SOCKS5 proxy test failed (may be expected in CI environment)"
          # Check if Tor is running
          docker ps | grep tor && echo "✓ Tor container is running" || echo "✗ Tor container not found"

      - name: Start cln
        run: |
          # nostr-relay is listed explicitly: --no-deps skips depends_on, and
          # the NIP-47 plugin connects at startup, so it has to answer first.
          docker-compose -f docker-compose.yml up -d --no-deps nostr-relay
          for i in $(seq 1 60); do
            curl -sf -o /dev/null http://0.0.0.0:7000/ && break
            if [ "$i" -eq 60 ]; then docker logs nostr-relay; exit 1; fi
            sleep 1
          done

          docker-compose -f docker-compose.yml up -d --no-deps cln
          sleep 5

          docker logs cln
          # The URI names the relay by its compose hostname, which only resolves
          # inside the docker network. These tests run on the host, where the
          # relay is published on 7000.
          NWC_URI=$(docker exec cln lightning-cli --network=regtest nip47-create label=nwc-for-l402 budget_msat=0 | jq -r '.uri')
          NWC_URI=${NWC_URI//nostr-relay%3A8080/127.0.0.1%3A7000}
          echo "NWC_URI=$NWC_URI" >> $GITHUB_ENV
          echo "BOLT12_LN_OFFER=$(docker exec cln lightning-cli --network=regtest offer any "BOLT12 Test" | jq -r '.bolt12')" >> $GITHUB_ENV

          sudo chmod 777 /cln-socket/lightning-rpc

      - name: Run tests for CLN
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: CLN
          CLN_LIGHTNING_RPC_FILE_PATH: /cln-socket/lightning-rpc

      - name: Run tests for CLN for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env: 
          LN_CLIENT_TYPE: CLN
          CLN_LIGHTNING_RPC_FILE_PATH: /cln-socket/lightning-rpc

      - name: Run tests for BOLT12
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: BOLT12
          BOLT12_LN_OFFER: ${{ env.BOLT12_LN_OFFER }}
          CLN_LIGHTNING_RPC_FILE_PATH: /cln-socket/lightning-rpc

      - name: Run tests for BOLT12 for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env: 
          LN_CLIENT_TYPE: BOLT12
          BOLT12_LN_OFFER: ${{ env.BOLT12_LN_OFFER }}
          CLN_LIGHTNING_RPC_FILE_PATH: /cln-socket/lightning-rpc

      - name: Run tests for NWC
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: NWC
          NWC_URI: ${{ env.NWC_URI }}

      - name: Start Eclair
        run: |
          docker-compose -f docker-compose.yml up -d --no-deps eclair
          echo "Waiting for Eclair to start..."
          sleep 15
          docker logs eclair

      - name: Verify Eclair node
        run: |
          echo "Checking Eclair API connectivity..."
          max_attempts=30
          attempt=0
          
          while [ $attempt -lt $max_attempts ]; do
            if curl -s --user :eclairpass http://localhost:8282/getinfo > /dev/null 2>&1; then
              echo "✓ Eclair API is accessible"
              ECLAIR_INFO=$(curl -s --user :eclairpass -X POST http://localhost:8282/getinfo)
              echo "Eclair node info: $ECLAIR_INFO"
              break
            fi
            
            attempt=$((attempt + 1))
            echo "Attempt $attempt/$max_attempts: Waiting for Eclair API..."
            sleep 2
          done
          
          if [ $attempt -eq $max_attempts ]; then
            echo "Warning: Eclair API may not be fully ready"
            docker logs eclair
          fi

      - name: Run tests for ECLAIR
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: ECLAIR
          ECLAIR_API_URL: http://0.0.0.0:8282
          ECLAIR_PASSWORD: eclairpass

      - name: Run tests for ECLAIR for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env: 
          LN_CLIENT_TYPE: ECLAIR
          ECLAIR_API_URL: http://0.0.0.0:8282
          ECLAIR_PASSWORD: eclairpass

      - name: Start litd for LNC tests
        run: |
          docker-compose -f docker-compose.yml up -d --no-deps litd
          echo "Waiting for litd to start..."
          sleep 10
          docker logs litd

      - name: Generate LNC pairing phrase
        run: |
          echo "Generating LNC pairing phrase..."
          max_attempts=10
          attempt=0
          PAIRING_PHRASE=""
          
          while [ $attempt -lt $max_attempts ] && [ -z "$PAIRING_PHRASE" ]; do
            SESSION_OUTPUT=$(docker exec litd litcli --network=regtest sessions add --label="ci_test" --type=admin 2>&1 || true)
            
            echo "Debug: Session output:"
            echo "$SESSION_OUTPUT"
            
            if echo "$SESSION_OUTPUT" | grep -q "pairing_secret_mnemonic"; then
              # Extract the full mnemonic - it should be the value of pairing_secret_mnemonic field
              # The output is JSON-like, so we extract everything between the quotes after pairing_secret_mnemonic
              PAIRING_PHRASE=$(echo "$SESSION_OUTPUT" | grep "pairing_secret_mnemonic" | sed 's/.*"pairing_secret_mnemonic"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | tr -d '\n')
              
              # Count words to verify we got the full phrase
              WORD_COUNT=$(echo "$PAIRING_PHRASE" | wc -w)
              echo "Debug: Extracted phrase with $WORD_COUNT words: $PAIRING_PHRASE"
              
              if [ "$WORD_COUNT" -eq 10 ] && [ -n "$PAIRING_PHRASE" ]; then
                echo "✓ Generated pairing phrase with 10 words"
                echo "LNC_PAIRING_PHRASE=$PAIRING_PHRASE" >> $GITHUB_ENV
                break
              else
                echo "Warning: Got $WORD_COUNT words instead of 10, retrying..."
                PAIRING_PHRASE=""
              fi
            fi
            
            attempt=$((attempt + 1))
            echo "Attempt $attempt/$max_attempts: Waiting for litd to be ready..."
            sleep 3
          done
          
          if [ -z "$PAIRING_PHRASE" ]; then
            echo "Warning: Could not generate LNC pairing phrase"
            echo "LNC tests will be skipped"
          else
            echo "✓ LNC pairing phrase generated successfully"
          fi

      - name: Run tests for LND via LNC
        run: |
          if [ -n "$LNC_PAIRING_PHRASE" ]; then
            echo "Running LND tests via LNC (Lightning Node Connect)..."
            echo "LNC will be used instead of traditional gRPC connection"
            cargo test --verbose
          else
            echo "Skipping LNC tests - pairing phrase not available"
          fi
        env:
          LN_CLIENT_TYPE: LND
          LNC_PAIRING_PHRASE: ${{ env.LNC_PAIRING_PHRASE }}
          LNC_MAILBOX_SERVER: mailbox.terminal.lightning.today:443

  check-no-rocket:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y pkg-config libssl-dev

      - name: Check library without rocket feature (ngx_l402 consumption path)
        run: cargo check --no-default-features --all-targets

      - name: Check library without rocket, with no-accept-authenticate-required
        run: cargo check --no-default-features --all-targets --features "no-accept-authenticate-required"

      - name: Assert rocket stays out of the no-default tree
        # cargo tree -i fails when the package is absent; ! flips that into a pass.
        run: "! cargo tree --no-default-features -i rocket"