keysend_test.go raw

   1  package transactions
   2  
   3  import (
   4  	"context"
   5  	"encoding/hex"
   6  	"encoding/json"
   7  	"errors"
   8  	"strconv"
   9  	"testing"
  10  
  11  	"github.com/stretchr/testify/assert"
  12  	"github.com/stretchr/testify/require"
  13  
  14  	"github.com/getAlby/hub/constants"
  15  	"github.com/getAlby/hub/db"
  16  	"github.com/getAlby/hub/db/queries"
  17  	"github.com/getAlby/hub/lnclient"
  18  	"github.com/getAlby/hub/tests"
  19  )
  20  
  21  func TestSendKeysend(t *testing.T) {
  22  	svc, err := tests.CreateTestService(t)
  23  	require.NoError(t, err)
  24  	defer svc.Remove()
  25  
  26  	mockEventConsumer := tests.NewMockEventConsumer()
  27  	svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
  28  
  29  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  30  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, nil, nil)
  31  	assert.NoError(t, err)
  32  
  33  	var metadata lnclient.Metadata
  34  	err = json.Unmarshal(transaction.Metadata, &metadata)
  35  	assert.NoError(t, err)
  36  
  37  	assert.Equal(t, "fake destination", metadata["destination"])
  38  	assert.Nil(t, metadata["tlv_records"])
  39  	assert.Equal(t, uint64(1000), transaction.AmountMsat)
  40  	assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
  41  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
  42  	assert.Zero(t, transaction.FeeReserveMsat)
  43  	assert.NotNil(t, transaction.Preimage)
  44  	assert.Equal(t, 64, len(*transaction.Preimage))
  45  
  46  	consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
  47  	assert.Equal(t, 1, len(consumedEvents))
  48  	assert.Equal(t, "nwc_payment_sent", consumedEvents[0].Event)
  49  	settledTransaction := consumedEvents[0].Properties.(*db.Transaction)
  50  	assert.Equal(t, transaction, settledTransaction)
  51  }
  52  func TestSendKeysend_FailedRemovesFeeReserve(t *testing.T) {
  53  	svc, err := tests.CreateTestService(t)
  54  	require.NoError(t, err)
  55  	defer svc.Remove()
  56  
  57  	svc.LNClient.(*tests.MockLn).PayKeysendErrors = append(svc.LNClient.(*tests.MockLn).PayKeysendErrors, errors.New("Some error"))
  58  	svc.LNClient.(*tests.MockLn).PayKeysendResponses = append(svc.LNClient.(*tests.MockLn).PayKeysendResponses, nil)
  59  
  60  	mockEventConsumer := tests.NewMockEventConsumer()
  61  	svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
  62  
  63  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  64  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, nil, nil)
  65  
  66  	assert.Error(t, err)
  67  	assert.Nil(t, transaction)
  68  
  69  	failedTransaction := db.Transaction{}
  70  	require.NoError(t, svc.DB.Where("type = ? AND state = ?", constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_FAILED).First(&failedTransaction).Error)
  71  
  72  	assert.Equal(t, uint64(1000), failedTransaction.AmountMsat)
  73  	assert.Zero(t, failedTransaction.FeeReserveMsat)
  74  	assert.Equal(t, "Some error", failedTransaction.FailureReason)
  75  
  76  	consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
  77  	assert.Equal(t, 1, len(consumedEvents))
  78  	assert.Equal(t, "nwc_payment_failed", consumedEvents[0].Event)
  79  }
  80  
  81  func TestSendKeysend_CustomPreimage(t *testing.T) {
  82  	svc, err := tests.CreateTestService(t)
  83  	require.NoError(t, err)
  84  	defer svc.Remove()
  85  
  86  	customPreimage := "018465013e2337234a7e5530a21c4a8cf70d84231f4a8ff0b1e2cce3cb2bd03b"
  87  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  88  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, customPreimage, svc.LNClient, nil, nil)
  89  	assert.NoError(t, err)
  90  
  91  	var metadata lnclient.Metadata
  92  	err = json.Unmarshal(transaction.Metadata, &metadata)
  93  	assert.NoError(t, err)
  94  
  95  	assert.Equal(t, "fake destination", metadata["destination"])
  96  	assert.Nil(t, metadata["tlv_records"])
  97  	assert.Equal(t, uint64(1000), transaction.AmountMsat)
  98  	assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
  99  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 100  	assert.Zero(t, transaction.FeeReserveMsat)
 101  	assert.NotNil(t, transaction.Preimage)
 102  	assert.Equal(t, customPreimage, *transaction.Preimage)
 103  }
 104  
 105  func TestSendKeysend_App_NoPermission(t *testing.T) {
 106  	svc, err := tests.CreateTestService(t)
 107  	require.NoError(t, err)
 108  	defer svc.Remove()
 109  
 110  	app, _, err := tests.CreateApp(svc)
 111  	assert.NoError(t, err)
 112  
 113  	dbRequestEvent := &db.RequestEvent{}
 114  	err = svc.DB.Create(&dbRequestEvent).Error
 115  	assert.NoError(t, err)
 116  
 117  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 118  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
 119  
 120  	assert.Error(t, err)
 121  	assert.Equal(t, "app does not have pay_invoice scope", err.Error())
 122  	assert.Nil(t, transaction)
 123  }
 124  
 125  func TestSendKeysend_App_WithPermission(t *testing.T) {
 126  	svc, err := tests.CreateTestService(t)
 127  	require.NoError(t, err)
 128  	defer svc.Remove()
 129  
 130  	app, _, err := tests.CreateApp(svc)
 131  	assert.NoError(t, err)
 132  
 133  	dbRequestEvent := &db.RequestEvent{}
 134  	err = svc.DB.Create(&dbRequestEvent).Error
 135  	assert.NoError(t, err)
 136  
 137  	appPermission := &db.AppPermission{
 138  		AppId: app.ID,
 139  		App:   *app,
 140  		Scope: constants.PAY_INVOICE_SCOPE,
 141  	}
 142  	err = svc.DB.Create(appPermission).Error
 143  	assert.NoError(t, err)
 144  
 145  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 146  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
 147  	assert.NoError(t, err)
 148  
 149  	var metadata lnclient.Metadata
 150  	err = json.Unmarshal(transaction.Metadata, &metadata)
 151  	assert.NoError(t, err)
 152  
 153  	assert.Equal(t, "fake destination", metadata["destination"])
 154  	assert.Nil(t, metadata["tlv_records"])
 155  	assert.Equal(t, uint64(1000), transaction.AmountMsat)
 156  	assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
 157  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 158  	assert.Equal(t, app.ID, *transaction.AppId)
 159  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 160  	assert.Zero(t, transaction.FeeReserveMsat)
 161  	assert.NotNil(t, transaction.Preimage)
 162  	assert.Equal(t, 64, len(*transaction.Preimage))
 163  }
 164  
 165  func TestSendKeysend_App_BudgetExceeded(t *testing.T) {
 166  	svc, err := tests.CreateTestService(t)
 167  	require.NoError(t, err)
 168  	defer svc.Remove()
 169  
 170  	app, _, err := tests.CreateApp(svc)
 171  	assert.NoError(t, err)
 172  
 173  	dbRequestEvent := &db.RequestEvent{}
 174  	err = svc.DB.Create(&dbRequestEvent).Error
 175  	assert.NoError(t, err)
 176  
 177  	appPermission := &db.AppPermission{
 178  		AppId:        app.ID,
 179  		App:          *app,
 180  		Scope:        constants.PAY_INVOICE_SCOPE,
 181  		MaxAmountSat: 10, // not enough for the fee reserve
 182  	}
 183  	err = svc.DB.Create(appPermission).Error
 184  	assert.NoError(t, err)
 185  
 186  	mockEventConsumer := tests.NewMockEventConsumer()
 187  	svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
 188  
 189  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 190  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
 191  
 192  	assert.ErrorIs(t, err, NewQuotaExceededError())
 193  	assert.Nil(t, transaction)
 194  
 195  	consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
 196  	assert.Equal(t, 1, len(consumedEvents))
 197  	assert.Equal(t, "nwc_permission_denied", consumedEvents[0].Event)
 198  	assert.Equal(t, app.Name, consumedEvents[0].Properties.(map[string]interface{})["app_name"])
 199  	assert.Equal(t, constants.ERROR_QUOTA_EXCEEDED, consumedEvents[0].Properties.(map[string]interface{})["code"])
 200  	assert.Equal(t, NewQuotaExceededError().Error(), consumedEvents[0].Properties.(map[string]interface{})["message"])
 201  }
 202  func TestSendKeysend_App_BudgetNotExceeded(t *testing.T) {
 203  	svc, err := tests.CreateTestService(t)
 204  	require.NoError(t, err)
 205  	defer svc.Remove()
 206  
 207  	app, _, err := tests.CreateApp(svc)
 208  	assert.NoError(t, err)
 209  
 210  	dbRequestEvent := &db.RequestEvent{}
 211  	err = svc.DB.Create(&dbRequestEvent).Error
 212  	assert.NoError(t, err)
 213  
 214  	appPermission := &db.AppPermission{
 215  		AppId:        app.ID,
 216  		App:          *app,
 217  		Scope:        constants.PAY_INVOICE_SCOPE,
 218  		MaxAmountSat: 11, // fee reserve (10) + keysend amount (1)
 219  	}
 220  	err = svc.DB.Create(appPermission).Error
 221  	assert.NoError(t, err)
 222  
 223  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 224  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
 225  	assert.NoError(t, err)
 226  
 227  	var metadata lnclient.Metadata
 228  	err = json.Unmarshal(transaction.Metadata, &metadata)
 229  	assert.NoError(t, err)
 230  
 231  	assert.Equal(t, "fake destination", metadata["destination"])
 232  	assert.Nil(t, metadata["tlv_records"])
 233  	assert.Equal(t, uint64(1000), transaction.AmountMsat)
 234  	assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
 235  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 236  	assert.Equal(t, app.ID, *transaction.AppId)
 237  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 238  	assert.Zero(t, transaction.FeeReserveMsat)
 239  	assert.NotNil(t, transaction.Preimage)
 240  	assert.Equal(t, 64, len(*transaction.Preimage))
 241  }
 242  
 243  func TestSendKeysend_App_BalanceExceeded(t *testing.T) {
 244  	svc, err := tests.CreateTestService(t)
 245  	require.NoError(t, err)
 246  	defer svc.Remove()
 247  
 248  	app, _, err := tests.CreateApp(svc)
 249  	assert.NoError(t, err)
 250  
 251  	dbRequestEvent := &db.RequestEvent{}
 252  	err = svc.DB.Create(&dbRequestEvent).Error
 253  	assert.NoError(t, err)
 254  	app.Isolated = true
 255  	svc.DB.Save(&app)
 256  
 257  	appPermission := &db.AppPermission{
 258  		AppId: app.ID,
 259  		App:   *app,
 260  		Scope: constants.PAY_INVOICE_SCOPE,
 261  	}
 262  	err = svc.DB.Create(appPermission).Error
 263  	assert.NoError(t, err)
 264  
 265  	svc.DB.Create(&db.Transaction{
 266  		AppId:      &app.ID,
 267  		State:      constants.TRANSACTION_STATE_SETTLED,
 268  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 269  		AmountMsat: 10000, // invoice is 1000 msat, but we also calculate fee reserves max of(10 sats or 1%)
 270  	})
 271  
 272  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 273  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
 274  
 275  	assert.ErrorIs(t, err, NewInsufficientBalanceError())
 276  	assert.Nil(t, transaction)
 277  }
 278  
 279  func TestSendKeysend_App_BalanceSufficient(t *testing.T) {
 280  	svc, err := tests.CreateTestService(t)
 281  	require.NoError(t, err)
 282  	defer svc.Remove()
 283  
 284  	app, _, err := tests.CreateApp(svc)
 285  	assert.NoError(t, err)
 286  
 287  	dbRequestEvent := &db.RequestEvent{}
 288  	err = svc.DB.Create(&dbRequestEvent).Error
 289  	assert.NoError(t, err)
 290  	app.Isolated = true
 291  	svc.DB.Save(&app)
 292  
 293  	appPermission := &db.AppPermission{
 294  		AppId: app.ID,
 295  		App:   *app,
 296  		Scope: constants.PAY_INVOICE_SCOPE,
 297  	}
 298  	err = svc.DB.Create(appPermission).Error
 299  	assert.NoError(t, err)
 300  
 301  	svc.DB.Create(&db.Transaction{
 302  		AppId:      &app.ID,
 303  		State:      constants.TRANSACTION_STATE_SETTLED,
 304  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 305  		AmountMsat: 11000, // invoice is 1000 msat, but we also calculate fee reserves max of(10 sats or 1%)
 306  	})
 307  
 308  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 309  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
 310  	assert.NoError(t, err)
 311  
 312  	var metadata lnclient.Metadata
 313  	err = json.Unmarshal(transaction.Metadata, &metadata)
 314  	assert.NoError(t, err)
 315  
 316  	assert.Equal(t, "fake destination", metadata["destination"])
 317  	assert.Nil(t, metadata["tlv_records"])
 318  	assert.Equal(t, uint64(1000), transaction.AmountMsat)
 319  	assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
 320  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 321  	assert.Equal(t, app.ID, *transaction.AppId)
 322  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 323  	assert.NotNil(t, transaction.Preimage)
 324  	assert.Equal(t, 64, len(*transaction.Preimage))
 325  	assert.Zero(t, transaction.FeeReserveMsat)
 326  }
 327  
 328  func TestSendKeysend_TLVs(t *testing.T) {
 329  	svc, err := tests.CreateTestService(t)
 330  	require.NoError(t, err)
 331  	defer svc.Remove()
 332  
 333  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 334  	transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", []lnclient.TLVRecord{
 335  		{
 336  			Type:  7629169,
 337  			Value: "7b22616374696f6e223a22626f6f7374222c2276616c75655f6d736174223a313030302c2276616c75655f6d7361745f746f74616c223a313030302c226170705f6e616d65223a22e29aa1205765624c4e2044656d6f222c226170705f76657273696f6e223a22312e30222c22666565644944223a2268747470733a2f2f66656564732e706f6463617374696e6465782e6f72672f706332302e786d6c222c22706f6463617374223a22506f6463617374696e6720322e30222c22657069736f6465223a22457069736f6465203130343a2041204e65772044756d70222c227473223a32312c226e616d65223a22e29aa1205765624c4e2044656d6f222c2273656e6465725f6e616d65223a225361746f736869204e616b616d6f746f222c226d657373616765223a22476f20706f6463617374696e6721227d",
 338  		},
 339  	}, "", svc.LNClient, nil, nil)
 340  	assert.NoError(t, err)
 341  
 342  	var metadata lnclient.Metadata
 343  	err = json.Unmarshal(transaction.Metadata, &metadata)
 344  	assert.NoError(t, err)
 345  
 346  	assert.Equal(t, "fake destination", metadata["destination"])
 347  	assert.NotNil(t, metadata["tlv_records"])
 348  
 349  	var boostagram Boostagram
 350  	err = json.Unmarshal(transaction.Boostagram, &boostagram)
 351  	assert.NoError(t, err)
 352  
 353  	assert.Equal(t, "⚡ WebLN Demo", boostagram.AppName)
 354  	assert.Equal(t, "⚡ WebLN Demo", boostagram.Name)
 355  	assert.Equal(t, "Podcasting 2.0", boostagram.Podcast)
 356  	assert.Equal(t, "Episode 104: A New Dump", boostagram.Episode.String())
 357  	assert.Equal(t, "https://feeds.podcastindex.org/pc20.xml", boostagram.FeedId.String())
 358  	assert.Equal(t, int64(21), boostagram.Timestamp)
 359  	assert.Equal(t, "Go podcasting!", boostagram.Message)
 360  	assert.Equal(t, "Satoshi Nakamoto", boostagram.SenderName)
 361  	assert.Equal(t, "boost", boostagram.Action)
 362  	assert.Equal(t, int64(1000), boostagram.ValueMsatTotal)
 363  
 364  	assert.Equal(t, "Go podcasting!", transaction.Description)
 365  
 366  	assert.Equal(t, uint64(1000), transaction.AmountMsat)
 367  	assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
 368  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 369  	assert.NotNil(t, transaction.Preimage)
 370  	assert.Equal(t, 64, len(*transaction.Preimage))
 371  	assert.Zero(t, transaction.FeeReserveMsat)
 372  }
 373  
 374  func TestSendKeysend_IsolatedAppToNoApp(t *testing.T) {
 375  	svc, err := tests.CreateTestService(t)
 376  	require.NoError(t, err)
 377  	defer svc.Remove()
 378  
 379  	// setup for self payment
 380  	svc.LNClient.(*tests.MockLn).Pubkey = "03cbd788f5b22bd56e2714bff756372d2293504c064e03250ed16a4dd80ad70e2c"
 381  
 382  	app, _, err := tests.CreateApp(svc)
 383  	assert.NoError(t, err)
 384  	app.Isolated = true
 385  	err = svc.DB.Save(&app).Error
 386  	assert.NoError(t, err)
 387  
 388  	appPermission := &db.AppPermission{
 389  		AppId: app.ID,
 390  		App:   *app,
 391  		Scope: constants.PAY_INVOICE_SCOPE,
 392  	}
 393  	err = svc.DB.Create(appPermission).Error
 394  	assert.NoError(t, err)
 395  
 396  	// give the isolated app 133 sats
 397  	svc.DB.Create(&db.Transaction{
 398  		AppId:      &app.ID,
 399  		State:      constants.TRANSACTION_STATE_SETTLED,
 400  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 401  		AmountMsat: 133000, // payment is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
 402  	})
 403  
 404  	dbRequestEvent := &db.RequestEvent{}
 405  	err = svc.DB.Create(&dbRequestEvent).Error
 406  	assert.NoError(t, err)
 407  
 408  	mockPreimage := "c8aeb44ae8eb269c8dbfb7ec5c263f0bfa3d755bc0ca641b8ee118673afda657"
 409  
 410  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 411  	transaction, err := transactionsService.SendKeysend(123000, "03cbd788f5b22bd56e2714bff756372d2293504c064e03250ed16a4dd80ad70e2c", []lnclient.TLVRecord{}, mockPreimage, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 412  
 413  	assert.NoError(t, err)
 414  	assert.NotNil(t, transaction)
 415  	assert.Equal(t, uint64(123000), transaction.AmountMsat)
 416  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 417  	assert.Equal(t, mockPreimage, *transaction.Preimage)
 418  	assert.Equal(t, app.ID, *transaction.AppId)
 419  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 420  	assert.True(t, transaction.SelfPayment)
 421  
 422  	transactionType := constants.TRANSACTION_TYPE_INCOMING
 423  	incomingTransaction, err := transactionsService.LookupTransaction(context.TODO(), transaction.PaymentHash, &transactionType, svc.LNClient, nil)
 424  	assert.NoError(t, err)
 425  	assert.Equal(t, uint64(123000), incomingTransaction.AmountMsat)
 426  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, incomingTransaction.State)
 427  	assert.Equal(t, mockPreimage, *incomingTransaction.Preimage)
 428  	assert.True(t, incomingTransaction.SelfPayment)
 429  
 430  	transactions := []db.Transaction{}
 431  	result := svc.DB.Find(&transactions)
 432  	assert.Equal(t, int64(3), result.RowsAffected)
 433  	// expect balance to be decreased
 434  	balanceMsat, err := queries.GetIsolatedBalanceMsat(svc.DB, app.ID)
 435  	assert.NoError(t, err)
 436  	assert.Equal(t, int64(10000), balanceMsat)
 437  }
 438  
 439  func TestSendKeysend_IsolatedAppToIsolatedApp(t *testing.T) {
 440  	svc, err := tests.CreateTestService(t)
 441  	require.NoError(t, err)
 442  	defer svc.Remove()
 443  
 444  	// setup for self payment
 445  	svc.LNClient.(*tests.MockLn).Pubkey = "03cbd788f5b22bd56e2714bff756372d2293504c064e03250ed16a4dd80ad70e2c"
 446  
 447  	app, _, err := tests.CreateApp(svc)
 448  	assert.NoError(t, err)
 449  	app.Isolated = true
 450  	err = svc.DB.Save(&app).Error
 451  	assert.NoError(t, err)
 452  
 453  	app2, _, err := tests.CreateApp(svc)
 454  	assert.NoError(t, err)
 455  	app2.Isolated = true
 456  	err = svc.DB.Save(&app2).Error
 457  	assert.NoError(t, err)
 458  
 459  	appPermission := &db.AppPermission{
 460  		AppId: app.ID,
 461  		App:   *app,
 462  		Scope: constants.PAY_INVOICE_SCOPE,
 463  	}
 464  	err = svc.DB.Create(appPermission).Error
 465  	assert.NoError(t, err)
 466  
 467  	// give the isolated app 133 sats
 468  	svc.DB.Create(&db.Transaction{
 469  		AppId:      &app.ID,
 470  		State:      constants.TRANSACTION_STATE_SETTLED,
 471  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 472  		AmountMsat: 133000, // payment is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
 473  	})
 474  
 475  	dbRequestEvent := &db.RequestEvent{}
 476  	err = svc.DB.Create(&dbRequestEvent).Error
 477  	assert.NoError(t, err)
 478  
 479  	mockPreimage := "c8aeb44ae8eb269c8dbfb7ec5c263f0bfa3d755bc0ca641b8ee118673afda657"
 480  
 481  	// Keysend from app 1 to app 2
 482  	tlvRecords := []lnclient.TLVRecord{
 483  		{
 484  			Type:  696969,
 485  			Value: hex.EncodeToString([]byte(strconv.FormatUint(uint64(app2.ID), 10))),
 486  		},
 487  		{
 488  			Type:  7629169,
 489  			Value: "7b22616374696f6e223a22626f6f7374222c2276616c75655f6d736174223a313030302c2276616c75655f6d7361745f746f74616c223a313030302c226170705f6e616d65223a22e29aa1205765624c4e2044656d6f222c226170705f76657273696f6e223a22312e30222c22666565644944223a2268747470733a2f2f66656564732e706f6463617374696e6465782e6f72672f706332302e786d6c222c22706f6463617374223a22506f6463617374696e6720322e30222c22657069736f6465223a22457069736f6465203130343a2041204e65772044756d70222c227473223a32312c226e616d65223a22e29aa1205765624c4e2044656d6f222c2273656e6465725f6e616d65223a225361746f736869204e616b616d6f746f222c226d657373616765223a22476f20706f6463617374696e6721227d",
 490  		},
 491  	}
 492  
 493  	mockEventConsumer := tests.NewMockEventConsumer()
 494  	svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
 495  
 496  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 497  	transaction, err := transactionsService.SendKeysend(123000, "03cbd788f5b22bd56e2714bff756372d2293504c064e03250ed16a4dd80ad70e2c", tlvRecords, mockPreimage, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 498  
 499  	assert.NoError(t, err)
 500  	assert.NotNil(t, transaction)
 501  	assert.Equal(t, uint64(123000), transaction.AmountMsat)
 502  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 503  	assert.Equal(t, mockPreimage, *transaction.Preimage)
 504  	assert.Equal(t, app.ID, *transaction.AppId)
 505  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 506  	assert.True(t, transaction.SelfPayment)
 507  
 508  	transactionType := constants.TRANSACTION_TYPE_INCOMING
 509  	incomingTransaction, err := transactionsService.LookupTransaction(context.TODO(), transaction.PaymentHash, &transactionType, svc.LNClient, &app2.ID)
 510  	assert.NoError(t, err)
 511  	assert.Equal(t, uint64(123000), incomingTransaction.AmountMsat)
 512  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, incomingTransaction.State)
 513  	assert.Equal(t, mockPreimage, *incomingTransaction.Preimage)
 514  	assert.Equal(t, app2.ID, *incomingTransaction.AppId)
 515  	assert.True(t, incomingTransaction.SelfPayment)
 516  
 517  	// receiving app should have the same data as what was sent
 518  	assert.Equal(t, transaction.Description, incomingTransaction.Description)
 519  	assert.Equal(t, transaction.Metadata, incomingTransaction.Metadata)
 520  	assert.Equal(t, transaction.Boostagram, incomingTransaction.Boostagram)
 521  
 522  	transactions := []db.Transaction{}
 523  	result := svc.DB.Find(&transactions)
 524  	assert.Equal(t, int64(3), result.RowsAffected)
 525  	// expect balance to be decreased
 526  	balanceMsat, err := queries.GetIsolatedBalanceMsat(svc.DB, app.ID)
 527  	assert.NoError(t, err)
 528  	assert.Equal(t, int64(10000), balanceMsat)
 529  
 530  	// expect app2 to receive the payment
 531  	balanceMsat, err = queries.GetIsolatedBalanceMsat(svc.DB, app2.ID)
 532  	assert.NoError(t, err)
 533  	assert.Equal(t, int64(123000), balanceMsat)
 534  
 535  	// check notifications
 536  	consumedEvents := mockEventConsumer.WaitForConsumedEvents(2)
 537  	assert.Equal(t, 2, len(consumedEvents))
 538  
 539  	// we can't guarantee which notification was processed first because events are published async
 540  	// so swap them if they are back to front
 541  	if consumedEvents[1].Event == "nwc_payment_received" {
 542  		consumedEvents[0], consumedEvents[1] = consumedEvents[1], consumedEvents[0]
 543  	}
 544  
 545  	assert.Equal(t, "nwc_payment_sent", consumedEvents[1].Event)
 546  	settledTransaction := consumedEvents[1].Properties.(*db.Transaction)
 547  	assert.Equal(t, transaction.ID, settledTransaction.ID)
 548  
 549  	assert.Equal(t, "nwc_payment_received", consumedEvents[0].Event)
 550  	receivedTransaction := consumedEvents[0].Properties.(*db.Transaction)
 551  	assert.Equal(t, incomingTransaction.ID, receivedTransaction.ID)
 552  }
 553