nip60: a helper to get amount from a bolt11 invoice.

This commit is contained in:
fiatjaf 2025-01-29 12:47:07 -03:00
parent 1e76aec884
commit 5e2d455b72
2 changed files with 93 additions and 0 deletions

View File

@ -7,6 +7,8 @@ import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
@ -201,3 +203,57 @@ func parseKeysetKeys(keys nut01.KeysMap) (map[uint64]*btcec.PublicKey, error) {
}
return parsedKeys, nil
}
func getSatoshisAmountFromBolt11(bolt11 string) (uint64, error) {
if len(bolt11) < 50 {
return 0, fmt.Errorf("invalid invoice, too short")
}
bolt11 = bolt11[0:50]
idx := strings.LastIndex(bolt11, "1")
if idx == -1 {
return 0, fmt.Errorf("invalid invoice")
}
hrp := bolt11[0:idx]
amount, ok := strings.CutPrefix(hrp, "lnbc")
if !ok {
return 0, fmt.Errorf("invalid invoice")
}
if len(amount) < 1 {
return 0, nil
}
// if last character is a digit, then the amount can just be interpreted as BTC
char := amount[len(amount)-1]
digit := char - '0'
isDigit := digit >= 0 && digit <= 9
cutPoint := len(amount) - 1
if isDigit {
cutPoint++
}
// if not a digit, it must be part of the known units
num := amount[:cutPoint]
if len(num) < 1 {
return 0, nil
}
am, err := strconv.ParseUint(num, 10, 64)
if err != nil {
return 0, err
}
switch char {
case 'm':
return am * 100000, nil
case 'u':
return am * 100, nil
case 'n':
return am / 10, nil
case 'p':
return am / 10000, nil
default:
// is BTC
return am * 100000000, nil
}
}

37
nip60/helpers_test.go Normal file
View File

@ -0,0 +1,37 @@
package nip60
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestBolt11Parsing(t *testing.T) {
for _, tc := range []struct {
bolt11 string
sats uint64
hasError bool
}{
{"", 0, true},
{"lnbc50n1pne523ypp57rn4l2ne673c093g72nm4pum2mkxjm0v2c0pjc9je909axyutdlqdq2f38xy6t5wvcqzpgxqrrssrzjq0nfr7qlprzkl2rke380tj0gkunm66pv7dtqt0396jrq02qz2fs98apyqqqqqqqqqgqqqqqqqqqqqzsqxgsp5rgp9fa9d8unmg949ztty48dgptke8f8zflt7af8d4yknamp2ymdq9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgqmatchtjjsyvk45mwwg2mvf8xshxhk2gveslvc27dda6dkp0kfc3hhs06e3c966h3z9j9aran0q8ncm6n458xdca3j5nsrke4saxrfmsqu7suv5", 5, false},
{"lnbc10500n1pne5tn3pp53wha2waf6g8zac853ay43uscfm9xy3k6ntz0lft72vajyna8695qdq2f38xy6t5wvcqzpgxqrrssrzjq0nfr7qlprzkl2rke380tj0gkunm66pv7dtqt0396jrq02qz2fs98apyqqqqqqqqqgqqqqqqqqqqqzsqxgsp5vapxxnr2wd3780qwmexe24hfcd2m87hsnn8z9xt9u0kmlwq084aq9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgq7vljmxfwsxsmf773ad5fhghe4s840t4set4ypyan2tnzwsldxktquw9lzhtlzj3zldma79hgr6ftp7hst8jh0km3p63vtsfugadtmkgp8wtkzp", 1050, false},
{"lnbc778970n1pne5t5jpp5yazceq5p3sr2dfvp283zqy4xd5jea9vwvc4ptw3m47qlkyzpzdtsdq2f38xy6t5wvcqzpgxqrrssrzjq0nfr7qlprzkl2rke380tj0gkunm66pv7dtqt0396jrq02qz2fs98apyqqqqqqqqqgqqqqqqqqqqqzsqxgsp5ssfackh9ys9ggc4h7ag3panc8wu3gqh84ghnkfd24khyy5pmjx6q9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgqdupshqq328498wk3evxh0ext6y5svfsnyauqvcse3l7u3lmk9s5yftmn7ec4l3d5aqd702je4g7aeng2nyjy0qcv23ue9ddn3ga3llsqzlnjv3", 77897, false},
{"lnbc700u1pne5t4upp5qkxcf9xsmn3p2r55dm386vlp52cvtgjyrv450z6ft2kj6lsmm6asdq2f38xy6t5wvcqzpgxqrrssrzjq0nfr7qlprzkl2rke380tj0gkunm66pv7dtqt0396jrq02qz2fs98apyqqqqqqqqqgqqqqqqqqqqqzsqxgsp5r2ynl9fycj5eega7tzuae5nu8umwawv30glw2c6gh45ymaryxdjs9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgqx8d2dr05fmaelmfmxyd6snlyenx25pl6jaxlcm9vqvq89t2smwv8au8j24fwxjswz4j2vcvnmhkaja4dhxf9y49jvwgd7xasgp3tppcp27zlr0", 70000, false},
{"lnbc2600u1pne5tkepp5q2g379zdf7k35ylf4r4f7hc47e9eazxlv3z55ydjnmtj8gt0vy8qdq2f38xy6t5wvcqzpgxqrrssrzjq0nfr7qlprzkl2rke380tj0gkunm66pv7dtqt0396jrq02qz2fs98apyqqqqqqqqqgqqqqqqqqqqqzsqxgsp5p45aam8azmegj747j8lwmdcjd6uj2f8mjpwh34qjye2h67g3azwq9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgq649a6ztfcqtalz3lfukesxep9tkaew90d80l6sds8cnppsqd0yx4jkttyuzw5k8erygzh36k6n3j5x4q3caqxsg3vy52j6va8879x4qqxlk7p5", 260000, false},
{"lnbc400m1pne5thjpp57slr93076zkczq08nvufgp6td6yafr9su3hqr2w2jl64p8e7k4jsdq2f38xy6t5wvcqzpgxqrrssrzjq0nfr7qlprzkl2rke380tj0gkunm66pv7dtqt0396jrq02qz2fs98apyqqqqqqqqqgqqqqqqqqqqqzsqxgsp55grhezefgcgjsddk4lp4jc564pwu5cp0zddg5f6tygt7xe57g47s9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgqf0xtyv62a4v9sjze3ftgdhwpgskhe3hlvwyjnhvt74grzsmpftvzdrlnh02cfg00st26pv7vgkxpknny52s3cuccwpm29l5yarq5fcqpfn7eh9", 40000000, false},
{"lnbc81pne5tcdpp59qnlzkzyjz0wy0z7fvjxe67u75cpknzfzahwqzm640ly0yrs982sdq2f38xy6t5wvcqzpgxqrrssrzjq0nfr7qlprzkl2rke380tj0gkunm66pv7dtqt0396jrq02qz2fs98apyqqqqqqqqqgqqqqqqqqqqqzsqxgsp5lrljv8ce4c64l77gj4gffg5gc8nlrnngk85twzvp35l92jwwravq9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgq5a8yyyreke9x5u4up22tzknum43hqyza9y808hmvftcvj7t7m5lzdrz53mhdh6snxxwd4r0x2cgw9crlzm4pdln80hphwfzpuj869nqq73w8r3", 800000000, false},
{"lnbc5n1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", 0, false},
{"lnbc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", 0, false},
} {
t.Run(fmt.Sprintf("%d = %s", tc.sats, tc.bolt11), func(t *testing.T) {
sats, err := getSatoshisAmountFromBolt11(tc.bolt11)
if tc.hasError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.Equal(t, int(tc.sats), int(sats))
})
}
}