Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: dynamic budgets #226

Merged
merged 55 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
120eb73
feat: add transactions table
rolznz Jul 1, 2024
458657e
feat: add transactions service with makeinvoice method
rolznz Jul 1, 2024
6e431ce
feat: use internal transactions WIP
rolznz Jul 2, 2024
04121ca
feat: check unsettled transactions
rolznz Jul 2, 2024
1a3479e
feat: use transactions service in nip47 package WIP
rolznz Jul 2, 2024
d8228ae
feat: use transactions service in NIP-47 handlers
rolznz Jul 3, 2024
e0d5980
fix: transaction list colors
rolznz Jul 3, 2024
d3027a1
fix: tests
rolznz Jul 3, 2024
32ad361
chore: remove old payments table
rolznz Jul 4, 2024
02d2027
fix: return budget usage as sats
rolznz Jul 4, 2024
d7b163d
Merge remote-tracking branch 'origin/master' into feat/dynamic-budgets
rolznz Jul 8, 2024
e95f1f5
feat: use internal transactions table for keysend payments
rolznz Jul 8, 2024
26f2840
feat: consume nwc_payment_received event in transaction service (WIP)
rolznz Jul 8, 2024
116a3bd
feat: update existing transaction from nwc_payment_received event in …
rolznz Jul 9, 2024
eb67f73
feat: update existing transaction from nwc_payment_sent event in tran…
rolznz Jul 9, 2024
1975d81
feat: handle async payment failed events in transactions service
rolznz Jul 9, 2024
5ad0e93
fix: tests
rolznz Jul 9, 2024
1b78f43
feat: correctly implement NIP-47 NOT_FOUND error code
rolznz Jul 9, 2024
a133ed5
feat: intercept self payments
rolznz Jul 9, 2024
10532a9
feat: isolated balance and visibility
rolznz Jul 9, 2024
f49caf1
Merge remote-tracking branch 'origin/master' into feat/dynamic-budgets
rolznz Jul 11, 2024
aefd4cf
feat: validate keysend payment does not exceed app internal balance
rolznz Jul 11, 2024
79895a9
feat: budget check in transactions service, correctly pass payment er…
rolznz Jul 11, 2024
7773186
fix: order transactions when looking up transaction
rolznz Jul 12, 2024
66a2795
feat: add fee reserves to unsettled outgoing transactions
rolznz Jul 12, 2024
6827da5
chore: rename app permissions max amount field
rolznz Jul 12, 2024
770b37f
chore: rename transaction amount values to be clearly millisats
rolznz Jul 12, 2024
4374577
chore: merge balance type and visibility into isolated property on ap…
rolznz Jul 12, 2024
4a35f16
chore: move duplicated permission check from nip-47 controllers to ha…
rolznz Jul 12, 2024
ae0a1a9
fix: app name in transaction list
rolznz Jul 13, 2024
e297251
Feat: permissions revamp v2 (#273)
rolznz Jul 14, 2024
68cb985
fix: make transactions table ID autoincrement
rolznz Jul 15, 2024
b5f56d7
fix: do not send unrelated notifications to isolated apps
rolznz Jul 16, 2024
7bc20c0
fix: nip-47 notifications not receiving updated transaction state
rolznz Jul 16, 2024
a224276
fix: migrate existing tables to have autoincrementing primary keys (#…
rolznz Jul 16, 2024
76961f9
feat: sqlite database config improvements
rolznz Jul 17, 2024
10b2a4e
fix: autoincrement migration to delete unlinked app permissions
rolznz Jul 17, 2024
9a8be5a
chore: update tests for dynamic budgets (WIP)
rolznz Jul 17, 2024
1c0b7e9
fix: get balance tests
rolznz Jul 18, 2024
abd72ef
chore: add extra event handler tests
rolznz Jul 18, 2024
4a93421
chore: add extra event handler test
rolznz Jul 18, 2024
03e23a0
chore: add extra multi_pay_invoice tests
rolznz Jul 18, 2024
40df3b6
chore: transactions service tests (WIP)
rolznz Jul 18, 2024
22fe5aa
chore: add transactions service payment tests
rolznz Jul 18, 2024
3f749bf
chore: add tests for self payments
rolznz Jul 19, 2024
82f0dc5
fix: notifications tests
rolznz Jul 19, 2024
6a2ce72
chore: add notifications tests for transactions service
rolznz Jul 19, 2024
30c1799
chore: add fee reserve tests for transactions service
rolznz Jul 19, 2024
51ef129
chore: add list transactions tests
rolznz Jul 19, 2024
986fe63
chore: add keysend tests for transactions service
rolznz Jul 19, 2024
ef79007
feat: subscribe for payments and invoices (#281)
im-adithya Jul 19, 2024
ef74c2e
Merge remote-tracking branch 'origin/master' into feat/dynamic-budgets
rolznz Jul 19, 2024
a64b2dc
fix: incorrect key on transaction list items
rolznz Jul 19, 2024
7108eb0
fix: test
rolznz Jul 19, 2024
5b6d949
fix: disable isolated app type on non-supported backends
rolznz Jul 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ frontend/src/utils/request.ts
frontend/src/utils/openLink.ts

# generated by rust go bindings for local development
glalby
glalby

*.db-shm
*.db-wal
*.db-journal
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ If the client creates the secret the client only needs to share the public key o
- `budget_renewal` (optional) reset the budget at the end of the given budget renewal. Can be `never` (default), `daily`, `weekly`, `monthly`, `yearly`
- `request_methods` (optional) url encoded, space separated list of request types that you need permission for: `pay_invoice` (default), `get_balance` (see NIP47). For example: `..&request_methods=pay_invoice%20get_balance`
- `notification_types` (optional) url encoded, space separated list of notification types that you need permission for: For example: `..&notification_types=payment_received%20payment_sent`
- `isolated` (optional) makes an isolated app connection with its own balance and only access to its own transaction list. e.g. `&isolated=true`. If using this option, you should not pass any custom request methods or notification types, nor set a budget or expiry.

Example:

Expand Down
66 changes: 50 additions & 16 deletions alby/alby_oauth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import (
"gorm.io/gorm"

"github.com/getAlby/hub/config"
"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/db"
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/logger"
"github.com/getAlby/hub/nip47/permissions"
"github.com/getAlby/hub/service/keys"
"github.com/getAlby/hub/transactions"
)

type albyOAuthService struct {
Expand Down Expand Up @@ -273,13 +275,13 @@ func (svc *albyOAuthService) DrainSharedWallet(ctx context.Context, lnClient lnc

logger.Logger.WithField("amount", amount).WithError(err).Error("Draining Alby shared wallet funds")

transaction, err := lnClient.MakeInvoice(ctx, amount, "Send shared wallet funds to Alby Hub", "", 120)
transaction, err := transactions.NewTransactionsService(svc.db).MakeInvoice(ctx, amount, "Send shared wallet funds to Alby Hub", "", 120, lnClient, nil, nil)
if err != nil {
logger.Logger.WithField("amount", amount).WithError(err).Error("Failed to make invoice")
return err
}

err = svc.SendPayment(ctx, transaction.Invoice)
err = svc.SendPayment(ctx, transaction.PaymentRequest)
if err != nil {
logger.Logger.WithField("amount", amount).WithError(err).Error("Failed to pay invoice from shared node")
return err
Expand Down Expand Up @@ -393,7 +395,7 @@ func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient.
}
notificationTypes := lnClient.GetSupportedNIP47NotificationTypes()
if len(notificationTypes) > 0 {
scopes = append(scopes, permissions.NOTIFICATIONS_SCOPE)
scopes = append(scopes, constants.NOTIFICATIONS_SCOPE)
}

app, _, err := db.NewDBService(svc.db, svc.eventPublisher).CreateApp(
Expand All @@ -403,6 +405,7 @@ func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient.
renewal,
nil,
scopes,
false,
)

if err != nil {
Expand All @@ -423,25 +426,58 @@ func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient.
return nil
}

func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) error {
func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) {
// run non-blocking
go svc.consumeEvent(ctx, event, globalProperties)
}

func (svc *albyOAuthService) consumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) {
// TODO: rename this config option to be specific to the alby API
if !svc.cfg.GetEnv().LogEvents {
logger.Logger.WithField("event", event).Debug("Skipped sending to alby events API")
return nil
return
}

if event.Event == "nwc_backup_channels" {
if err := svc.backupChannels(ctx, event); err != nil {
logger.Logger.WithError(err).Error("Failed to backup channels")
return err
}
return nil
return
}

if event.Event == "nwc_payment_received" {
type paymentReceivedEventProperties struct {
PaymentHash string `json:"payment_hash"`
}
// pass a new custom event with less detail
event = &events.Event{
Event: event.Event,
Properties: &paymentReceivedEventProperties{
PaymentHash: event.Properties.(*lnclient.Transaction).PaymentHash,
},
}
}

if event.Event == "nwc_payment_sent" {
type paymentSentEventProperties struct {
PaymentHash string `json:"payment_hash"`
Duration uint64 `json:"duration"`
}

// pass a new custom event with less detail
event = &events.Event{
Event: event.Event,
Properties: &paymentSentEventProperties{
PaymentHash: event.Properties.(*lnclient.Transaction).PaymentHash,
Duration: uint64(*event.Properties.(*lnclient.Transaction).SettledAt - event.Properties.(*lnclient.Transaction).CreatedAt),
},
}
}

token, err := svc.fetchUserToken(ctx)
if err != nil {
logger.Logger.WithError(err).Error("Failed to fetch user token")
return err
return
}

client := svc.oauthConf.Client(ctx, token)
Expand All @@ -452,7 +488,7 @@ func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Eve

if err != nil {
logger.Logger.WithError(err).Error("Failed to encode request payload")
return err
return
}

type eventWithPropertiesMap struct {
Expand All @@ -464,7 +500,7 @@ func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Eve
err = json.Unmarshal(originalEventBuffer.Bytes(), &eventWithGlobalProperties)
if err != nil {
logger.Logger.WithError(err).Error("Failed to decode request payload")
return err
return
}
if eventWithGlobalProperties.Properties == nil {
eventWithGlobalProperties.Properties = map[string]interface{}{}
Expand All @@ -485,13 +521,13 @@ func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Eve

if err != nil {
logger.Logger.WithError(err).Error("Failed to encode request payload")
return err
return
}

req, err := http.NewRequest("POST", fmt.Sprintf("%s/events", svc.cfg.GetEnv().AlbyAPIURL), body)
if err != nil {
logger.Logger.WithError(err).Error("Error creating request /events")
return err
return
}

req.Header.Set("User-Agent", "NWC-next")
Expand All @@ -502,18 +538,16 @@ func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Eve
logger.Logger.WithFields(logrus.Fields{
"event": eventWithGlobalProperties,
}).WithError(err).Error("Failed to send request to /events")
return err
return
}

if resp.StatusCode >= 300 {
logger.Logger.WithFields(logrus.Fields{
"event": eventWithGlobalProperties,
"status": resp.StatusCode,
}).Error("Request to /events returned non-success status")
return errors.New("request to /events returned non-success status")
return
}

return nil
}

func (svc *albyOAuthService) backupChannels(ctx context.Context, event *events.Event) error {
Expand Down
Loading
Loading