mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
feat: show tlv records in transaction popup (#365)
* feat: show tlv records in transaction popup * chore: add podcastinginfo component * chore: remove ? in podcasting info component * chore: decode tlv record values in backend * chore: add backup for tx description * feat: add boostagram column and use jsonb in metadata * chore: do not modify metadata while parsing * chore: combine boostagram and metadata migration * feat: extract description from custom records * chore: fix keysend tests * chore: change metadata param type in makeinvoice * chore: fix make invoice tests * chore: metadata fixes * fix: boostragram and metadata migration - only migrate values that are non-empty - wrap in db transaction * chore: set metadata json column to null if empty * fix: remove jsonb from migration * chore: code cleanup, rename api models, fix metadata usage * fix: metadata format * chore: remove unused contant * fix: tests * chore: improve tests --------- Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
This commit is contained in:
parent
7d4e7b60ee
commit
67ca173261
19 changed files with 544 additions and 171 deletions
|
|
@ -200,15 +200,35 @@ type Transaction struct {
|
|||
Type string `json:"type"`
|
||||
Invoice string `json:"invoice"`
|
||||
Description string `json:"description"`
|
||||
DescriptionHash string `json:"description_hash"`
|
||||
DescriptionHash string `json:"descriptionHash"`
|
||||
Preimage *string `json:"preimage"`
|
||||
PaymentHash string `json:"payment_hash"`
|
||||
PaymentHash string `json:"paymentHash"`
|
||||
Amount uint64 `json:"amount"`
|
||||
FeesPaid uint64 `json:"fees_paid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
SettledAt *string `json:"settled_at"`
|
||||
AppId *uint `json:"app_id"`
|
||||
Metadata interface{} `json:"metadata,omitempty"`
|
||||
FeesPaid uint64 `json:"feesPaid"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
SettledAt *string `json:"settledAt"`
|
||||
AppId *uint `json:"appId"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Boostagram *Boostagram `json:"boostagram,omitempty"`
|
||||
}
|
||||
|
||||
type Metadata = map[string]interface{}
|
||||
|
||||
type Boostagram struct {
|
||||
AppName string `json:"appName"`
|
||||
Name string `json:"name"`
|
||||
Podcast string `json:"podcast"`
|
||||
URL string `json:"url"`
|
||||
Episode string `json:"episode,omitempty"`
|
||||
FeedId string `json:"feedId,omitempty"`
|
||||
ItemId string `json:"itemId,omitempty"`
|
||||
Timestamp int64 `json:"ts,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
SenderId string `json:"senderId"`
|
||||
SenderName string `json:"senderName"`
|
||||
Time string `json:"time"`
|
||||
Action string `json:"action"`
|
||||
ValueMsatTotal int64 `json:"valueMsatTotal"`
|
||||
}
|
||||
|
||||
// debug api
|
||||
|
|
|
|||
|
|
@ -73,17 +73,30 @@ func toApiTransaction(transaction *transactions.Transaction) *Transaction {
|
|||
preimage = transaction.Preimage
|
||||
}
|
||||
|
||||
var metadata interface{}
|
||||
if transaction.Metadata != "" {
|
||||
jsonErr := json.Unmarshal([]byte(transaction.Metadata), &metadata)
|
||||
var metadata Metadata
|
||||
if transaction.Metadata != nil {
|
||||
jsonErr := json.Unmarshal(transaction.Metadata, &metadata)
|
||||
if jsonErr != nil {
|
||||
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
|
||||
"id": transaction.ID,
|
||||
"metadata": transaction.Metadata,
|
||||
"payment_hash": transaction.PaymentHash,
|
||||
"metadata": transaction.Metadata,
|
||||
}).Error("Failed to deserialize transaction metadata")
|
||||
}
|
||||
}
|
||||
|
||||
var boostagram *Boostagram
|
||||
if transaction.Boostagram != nil {
|
||||
var txBoostagram transactions.Boostagram
|
||||
jsonErr := json.Unmarshal(transaction.Boostagram, &txBoostagram)
|
||||
if jsonErr != nil {
|
||||
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
|
||||
"payment_hash": transaction.PaymentHash,
|
||||
"boostagram": transaction.Boostagram,
|
||||
}).Error("Failed to deserialize transaction boostagram info")
|
||||
}
|
||||
boostagram = toApiBoostagram(&txBoostagram)
|
||||
}
|
||||
|
||||
return &Transaction{
|
||||
Type: transaction.Type,
|
||||
Invoice: transaction.PaymentRequest,
|
||||
|
|
@ -97,5 +110,25 @@ func toApiTransaction(transaction *transactions.Transaction) *Transaction {
|
|||
CreatedAt: createdAt,
|
||||
SettledAt: settledAt,
|
||||
Metadata: metadata,
|
||||
Boostagram: boostagram,
|
||||
}
|
||||
}
|
||||
|
||||
func toApiBoostagram(boostagram *transactions.Boostagram) *Boostagram {
|
||||
return &Boostagram{
|
||||
AppName: boostagram.AppName,
|
||||
Name: boostagram.Name,
|
||||
Podcast: boostagram.Podcast,
|
||||
URL: boostagram.URL,
|
||||
Episode: boostagram.Episode,
|
||||
FeedId: boostagram.FeedId,
|
||||
ItemId: boostagram.ItemId,
|
||||
Timestamp: boostagram.Timestamp,
|
||||
Message: boostagram.Message,
|
||||
SenderId: boostagram.SenderId,
|
||||
SenderName: boostagram.SenderName,
|
||||
Time: boostagram.Time,
|
||||
Action: boostagram.Action,
|
||||
ValueMsatTotal: boostagram.ValueMsatTotal,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
db/migrations/202408061737_add_boostagrams_and_use_json.go
Normal file
29
db/migrations/202408061737_add_boostagrams_and_use_json.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// This migration adds boostagram column to transactions
|
||||
var _202408061737_add_boostagrams_and_use_json = &gormigrate.Migration{
|
||||
ID: "202408061737_add_boostagrams_and_use_json",
|
||||
Migrate: func(db *gorm.DB) error {
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
return tx.Exec(`
|
||||
ALTER TABLE transactions ADD COLUMN boostagram JSON;
|
||||
ALTER TABLE transactions ADD COLUMN metadata_temp JSON;
|
||||
UPDATE transactions SET metadata_temp = json(metadata) where metadata != "";
|
||||
ALTER TABLE transactions DROP COLUMN metadata;
|
||||
ALTER TABLE transactions RENAME COLUMN metadata_temp TO metadata;
|
||||
`).Error
|
||||
})
|
||||
|
||||
return err
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ func Migrate(gormDB *gorm.DB) error {
|
|||
_202407151352_autoincrement,
|
||||
_202407201604_transactions_indexes,
|
||||
_202407262257_remove_invalid_scopes,
|
||||
_202408061737_add_boostagrams_and_use_json,
|
||||
})
|
||||
|
||||
return m.Migrate()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package db
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
type UserConfig struct {
|
||||
ID uint
|
||||
|
|
@ -75,8 +79,9 @@ type Transaction struct {
|
|||
ExpiresAt *time.Time
|
||||
UpdatedAt time.Time
|
||||
SettledAt *time.Time
|
||||
Metadata string
|
||||
Metadata datatypes.JSON
|
||||
SelfPayment bool
|
||||
Boostagram datatypes.JSON
|
||||
}
|
||||
|
||||
type DBService interface {
|
||||
|
|
|
|||
63
frontend/src/components/PodcastingInfo.tsx
Normal file
63
frontend/src/components/PodcastingInfo.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { Boostagram } from "src/types";
|
||||
|
||||
function PodcastingInfo({ boost }: { boost: Boostagram }) {
|
||||
return (
|
||||
<>
|
||||
{boost.message && (
|
||||
<div className="mt-6">
|
||||
<p>Message</p>
|
||||
<p className="text-muted-foreground break-all">{boost.message}</p>
|
||||
</div>
|
||||
)}
|
||||
{boost.podcast && (
|
||||
<div className="mt-6">
|
||||
<p>Podcast</p>
|
||||
<p className="text-muted-foreground break-all">{boost.podcast}</p>
|
||||
</div>
|
||||
)}
|
||||
{boost.episode && (
|
||||
<div className="mt-6">
|
||||
<p>Episode</p>
|
||||
<p className="text-muted-foreground break-all">{boost.episode}</p>
|
||||
</div>
|
||||
)}
|
||||
{boost.action && (
|
||||
<div className="mt-6">
|
||||
<p>Action</p>
|
||||
<p className="text-muted-foreground break-all">{boost.action}</p>
|
||||
</div>
|
||||
)}
|
||||
{boost.ts && (
|
||||
<div className="mt-6">
|
||||
<p>Timestamp</p>
|
||||
<p className="text-muted-foreground break-all">{boost.ts}</p>
|
||||
</div>
|
||||
)}
|
||||
{boost.valueMsatTotal && (
|
||||
<div className="mt-6">
|
||||
<p>Total amount</p>
|
||||
<p className="text-muted-foreground break-all">
|
||||
{new Intl.NumberFormat(undefined, {}).format(
|
||||
Math.floor(boost.valueMsatTotal / 1000)
|
||||
)}{" "}
|
||||
{Math.floor(boost.valueMsatTotal / 1000) == 1 ? "sat" : "sats"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{boost.senderName && (
|
||||
<div className="mt-6">
|
||||
<p>Sender</p>
|
||||
<p className="text-muted-foreground break-all">{boost.senderName}</p>
|
||||
</div>
|
||||
)}
|
||||
{boost.appName && (
|
||||
<div className="mt-6">
|
||||
<p>App</p>
|
||||
<p className="text-muted-foreground break-all">{boost.appName}</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PodcastingInfo;
|
||||
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from "lucide-react";
|
||||
import React from "react";
|
||||
import AppAvatar from "src/components/AppAvatar";
|
||||
import PodcastingInfo from "src/components/PodcastingInfo";
|
||||
import {
|
||||
Credenza,
|
||||
CredenzaBody,
|
||||
|
|
@ -38,7 +39,7 @@ function TransactionItem({ tx }: Props) {
|
|||
const [showDetails, setShowDetails] = React.useState(false);
|
||||
const type = tx.type;
|
||||
const Icon = tx.type == "outgoing" ? ArrowUpIcon : ArrowDownIcon;
|
||||
const app = tx.app_id && apps?.find((app) => app.id === tx.app_id);
|
||||
const app = tx.appId && apps?.find((app) => app.id === tx.appId);
|
||||
|
||||
const copy = (text: string) => {
|
||||
copyToClipboard(text);
|
||||
|
|
@ -92,7 +93,7 @@ function TransactionItem({ tx }: Props) {
|
|||
{app ? app.name : type == "incoming" ? "Received" : "Sent"}
|
||||
</p>
|
||||
<p className="text-sm md:text-base truncate text-muted-foreground">
|
||||
{dayjs(tx.settled_at).fromNow()}
|
||||
{dayjs(tx.settledAt).fromNow()}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm md:text-base text-muted-foreground break-all">
|
||||
|
|
@ -166,7 +167,7 @@ function TransactionItem({ tx }: Props) {
|
|||
<div className="mt-8">
|
||||
<p>Date & Time</p>
|
||||
<p className="text-muted-foreground">
|
||||
{dayjs(tx.settled_at)
|
||||
{dayjs(tx.settledAt)
|
||||
.tz(dayjs.tz.guess())
|
||||
.format("D MMMM YYYY, HH:mm")}
|
||||
</p>
|
||||
|
|
@ -176,9 +177,9 @@ function TransactionItem({ tx }: Props) {
|
|||
<p>Fee</p>
|
||||
<p className="text-muted-foreground">
|
||||
{new Intl.NumberFormat(undefined, {}).format(
|
||||
Math.floor(tx.fees_paid / 1000)
|
||||
Math.floor(tx.feesPaid / 1000)
|
||||
)}{" "}
|
||||
{Math.floor(tx.fees_paid / 1000) == 1 ? "sat" : "sats"}
|
||||
{Math.floor(tx.feesPaid / 1000) == 1 ? "sat" : "sats"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -191,52 +192,55 @@ function TransactionItem({ tx }: Props) {
|
|||
</div>
|
||||
)}
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter className="!justify-start mt-4 !flex-col">
|
||||
<div
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
>
|
||||
Details
|
||||
{showDetails ? (
|
||||
<ChevronUp className="w-4 h-4" />
|
||||
) : (
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
<CredenzaFooter>
|
||||
<div className="mt-4 w-full">
|
||||
<div
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
>
|
||||
Details
|
||||
{showDetails ? (
|
||||
<ChevronUp className="w-4 h-4" />
|
||||
) : (
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
{showDetails && (
|
||||
<>
|
||||
{tx.boostagram && <PodcastingInfo boost={tx.boostagram} />}
|
||||
<div className="mt-6">
|
||||
<p>Preimage</p>
|
||||
<div className="flex items-center gap-4">
|
||||
<p className="text-muted-foreground break-all">
|
||||
{tx.preimage}
|
||||
</p>
|
||||
<CopyIcon
|
||||
className="cursor-pointer text-muted-foreground w-6 h-6"
|
||||
onClick={() => {
|
||||
if (tx.preimage) {
|
||||
copy(tx.preimage);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<p>Hash</p>
|
||||
<div className="flex items-center gap-4">
|
||||
<p className="text-muted-foreground break-all">
|
||||
{tx.paymentHash}
|
||||
</p>
|
||||
<CopyIcon
|
||||
className="cursor-pointer text-muted-foreground w-6 h-6"
|
||||
onClick={() => {
|
||||
copy(tx.paymentHash);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{showDetails && (
|
||||
<>
|
||||
<div className="mt-6 !ml-0">
|
||||
<p>Preimage</p>
|
||||
<div className="flex items-center gap-4">
|
||||
<p className="text-muted-foreground break-all">
|
||||
{tx.preimage}
|
||||
</p>
|
||||
<CopyIcon
|
||||
className="cursor-pointer text-muted-foreground w-6 h-6"
|
||||
onClick={() => {
|
||||
if (tx.preimage) {
|
||||
copy(tx.preimage);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 !ml-0">
|
||||
<p>Hash</p>
|
||||
<div className="flex items-center gap-4">
|
||||
<p className="text-muted-foreground break-all">
|
||||
{tx.payment_hash}
|
||||
</p>
|
||||
<CopyIcon
|
||||
className="cursor-pointer text-muted-foreground w-6 h-6"
|
||||
onClick={() => {
|
||||
copy(tx.payment_hash);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ function TransactionsList() {
|
|||
) : (
|
||||
<>
|
||||
{transactions?.map((tx) => {
|
||||
return <TransactionItem key={tx.payment_hash + tx.type} tx={tx} />;
|
||||
return <TransactionItem key={tx.paymentHash + tx.type} tx={tx} />;
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -43,12 +43,12 @@ export default function Receive() {
|
|||
);
|
||||
const [paymentDone, setPaymentDone] = React.useState(false);
|
||||
const { data: invoiceData } = useTransaction(
|
||||
transaction ? transaction.payment_hash : "",
|
||||
transaction ? transaction.paymentHash : "",
|
||||
true
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (invoiceData?.settled_at) {
|
||||
if (invoiceData?.settledAt) {
|
||||
setPaymentDone(true);
|
||||
popConfetti();
|
||||
toast({
|
||||
|
|
|
|||
|
|
@ -370,17 +370,35 @@ export type BalancesResponse = {
|
|||
|
||||
export type Transaction = {
|
||||
type: "incoming" | "outgoing";
|
||||
app_id: number | undefined;
|
||||
appId: number | undefined;
|
||||
invoice: string;
|
||||
description: string;
|
||||
description_hash: string;
|
||||
descriptionHash: string;
|
||||
preimage: string | undefined;
|
||||
payment_hash: string;
|
||||
paymentHash: string;
|
||||
amount: number;
|
||||
fees_paid: number;
|
||||
created_at: string;
|
||||
settled_at: string | undefined;
|
||||
metadata: unknown;
|
||||
feesPaid: number;
|
||||
createdAt: string;
|
||||
settledAt: string | undefined;
|
||||
metadata?: Record<string, unknown>;
|
||||
boostagram?: Boostagram;
|
||||
};
|
||||
|
||||
export type Boostagram = {
|
||||
appName: string;
|
||||
name: string;
|
||||
podcast: string;
|
||||
url: string;
|
||||
episode?: string;
|
||||
feedId?: string;
|
||||
itemId?: string;
|
||||
ts?: number;
|
||||
message?: string;
|
||||
senderId: string;
|
||||
senderName: string;
|
||||
time: string;
|
||||
action: "boost";
|
||||
valueMsatTotal: number;
|
||||
};
|
||||
|
||||
export type NewChannelOrderStatus = "pay" | "paid" | "success" | "opening";
|
||||
|
|
|
|||
6
go.mod
6
go.mod
|
|
@ -26,6 +26,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/DataDog/datadog-go/v5 v5.3.0 // indirect
|
||||
|
|
@ -70,6 +71,7 @@ require (
|
|||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-macaroon-bakery/macaroonpb v1.0.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.2.1 // indirect
|
||||
|
|
@ -100,7 +102,7 @@ require (
|
|||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jackc/pgtype v1.14.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.18.3 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||
|
|
@ -212,6 +214,7 @@ require (
|
|||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.5.6 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.49.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
|
|
@ -234,6 +237,7 @@ require (
|
|||
github.com/lightningnetwork/lnd v0.17.4-beta
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||
gorm.io/datatypes v1.2.1
|
||||
)
|
||||
|
||||
// See https://github.com/lightningnetwork/lnd/blob/v0.17.4-beta/go.mod#L12C58-L12C70
|
||||
|
|
|
|||
33
go.sum
33
go.sum
|
|
@ -2,6 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
|
|
@ -209,8 +211,9 @@ github.com/go-macaroon-bakery/macaroonpb v1.0.0 h1:It9exBaRMZ9iix1iJ6gwzfwsDE6Ex
|
|||
github.com/go-macaroon-bakery/macaroonpb v1.0.0/go.mod h1:UzrGOcbiwTXISFP2XDLDPjfhMINZa+fX/7A2lMd31zc=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
|
|
@ -233,6 +236,10 @@ github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5
|
|||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU=
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
|
|
@ -331,8 +338,8 @@ github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX
|
|||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
|
|
@ -345,9 +352,14 @@ github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQ
|
|||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
|
||||
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
|
|
@ -468,6 +480,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/microsoft/go-mssqldb v1.0.0 h1:k2p2uuG8T5T/7Hp7/e3vMGTnnR0sU4h8d1CcC71iLHU=
|
||||
github.com/microsoft/go-mssqldb v1.0.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
|
|
@ -919,6 +933,17 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/datatypes v1.2.1 h1:r+g0bk4LPCW2v4+Ls7aeNgGme7JYdNDQ2VtvlNUfBh0=
|
||||
gorm.io/datatypes v1.2.1/go.mod h1:hYK6OTb/1x+m96PgoZZq10UXJ6RvEBb9kRDQ2yyhzGs=
|
||||
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
|
||||
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
|
||||
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
|
||||
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
|
||||
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||
gorm.io/driver/sqlserver v1.4.2 h1:nMtEeKqv2R/vv9FoHUFWfXfP6SskAgRar0TPlZV1stk=
|
||||
gorm.io/driver/sqlserver v1.4.2/go.mod h1:XHwBuB4Tlh7DqO0x7Ema8dmyWsQW7wi38VQOAFkrbXY=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ type TLVRecord struct {
|
|||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Metadata = map[string]interface{}
|
||||
|
||||
type NodeInfo struct {
|
||||
Alias string
|
||||
Color string
|
||||
|
|
@ -34,7 +36,7 @@ type Transaction struct {
|
|||
CreatedAt int64
|
||||
ExpiresAt *int64
|
||||
SettledAt *int64
|
||||
Metadata interface{}
|
||||
Metadata Metadata
|
||||
}
|
||||
|
||||
type NodeConnectionInfo struct {
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ import (
|
|||
)
|
||||
|
||||
type makeInvoiceParams struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Description string `json:"description"`
|
||||
DescriptionHash string `json:"description_hash"`
|
||||
Expiry int64 `json:"expiry"`
|
||||
Metadata interface{} `json:"metadata,omitempty"`
|
||||
Amount int64 `json:"amount"`
|
||||
Description string `json:"description"`
|
||||
DescriptionHash string `json:"description_hash"`
|
||||
Expiry int64 `json:"expiry"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
type makeInvoiceResponse struct {
|
||||
models.Transaction
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ func ToNip47Transaction(transaction *transactions.Transaction) *Transaction {
|
|||
preimage = *transaction.Preimage
|
||||
}
|
||||
|
||||
var metadata interface{}
|
||||
if transaction.Metadata != "" {
|
||||
jsonErr := json.Unmarshal([]byte(transaction.Metadata), &metadata)
|
||||
var metadata map[string]interface{}
|
||||
if transaction.Metadata != nil {
|
||||
jsonErr := json.Unmarshal(transaction.Metadata, &metadata)
|
||||
if jsonErr != nil {
|
||||
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
|
||||
"id": transaction.ID,
|
||||
"metadata": transaction.Metadata,
|
||||
"payment_hash": transaction.PaymentHash,
|
||||
"metadata": transaction.Metadata,
|
||||
}).Error("Failed to deserialize transaction metadata")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package transactions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
|
|
@ -19,10 +20,15 @@ func TestSendKeysend(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", []lnclient.TLVRecord{}, "", svc.LNClient, nil, nil)
|
||||
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", nil, "", svc.LNClient, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"destination":"fake destination","tlv_records":[]}`, transaction.Metadata)
|
||||
|
||||
var metadata lnclient.Metadata
|
||||
err = json.Unmarshal(transaction.Metadata, &metadata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "fake destination", metadata["destination"])
|
||||
assert.Nil(t, metadata["tlv_records"])
|
||||
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
||||
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
||||
|
|
@ -39,10 +45,15 @@ func TestSendKeysend_CustomPreimage(t *testing.T) {
|
|||
|
||||
customPreimage := "018465013e2337234a7e5530a21c4a8cf70d84231f4a8ff0b1e2cce3cb2bd03b"
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", []lnclient.TLVRecord{}, customPreimage, svc.LNClient, nil, nil)
|
||||
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", nil, customPreimage, svc.LNClient, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"destination":"fake destination","tlv_records":[]}`, transaction.Metadata)
|
||||
|
||||
var metadata lnclient.Metadata
|
||||
err = json.Unmarshal(transaction.Metadata, &metadata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "fake destination", metadata["destination"])
|
||||
assert.Nil(t, metadata["tlv_records"])
|
||||
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
||||
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
||||
|
|
@ -66,7 +77,7 @@ func TestSendKeysend_App_NoPermission(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", []lnclient.TLVRecord{}, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "app does not have pay_invoice scope", err.Error())
|
||||
|
|
@ -96,10 +107,15 @@ func TestSendKeysend_App_WithPermission(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", []lnclient.TLVRecord{}, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"destination":"fake destination","tlv_records":[]}`, transaction.Metadata)
|
||||
|
||||
var metadata lnclient.Metadata
|
||||
err = json.Unmarshal(transaction.Metadata, &metadata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "fake destination", metadata["destination"])
|
||||
assert.Nil(t, metadata["tlv_records"])
|
||||
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
||||
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
||||
|
|
@ -134,7 +150,7 @@ func TestSendKeysend_App_BudgetExceeded(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", []lnclient.TLVRecord{}, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
|
||||
assert.ErrorIs(t, err, NewQuotaExceededError())
|
||||
assert.Nil(t, transaction)
|
||||
|
|
@ -163,10 +179,15 @@ func TestSendKeysend_App_BudgetNotExceeded(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", []lnclient.TLVRecord{}, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"destination":"fake destination","tlv_records":[]}`, transaction.Metadata)
|
||||
|
||||
var metadata lnclient.Metadata
|
||||
err = json.Unmarshal(transaction.Metadata, &metadata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "fake destination", metadata["destination"])
|
||||
assert.Nil(t, metadata["tlv_records"])
|
||||
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
||||
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
||||
|
|
@ -209,7 +230,7 @@ func TestSendKeysend_App_BalanceExceeded(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", []lnclient.TLVRecord{}, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
|
||||
assert.ErrorIs(t, err, NewInsufficientBalanceError())
|
||||
assert.Nil(t, transaction)
|
||||
|
|
@ -247,10 +268,15 @@ func TestSendKeysend_App_BalanceSufficient(t *testing.T) {
|
|||
})
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", []lnclient.TLVRecord{}, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
|
||||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"destination":"fake destination","tlv_records":[]}`, transaction.Metadata)
|
||||
|
||||
var metadata lnclient.Metadata
|
||||
err = json.Unmarshal(transaction.Metadata, &metadata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "fake destination", metadata["destination"])
|
||||
assert.Nil(t, metadata["tlv_records"])
|
||||
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
||||
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
||||
|
|
@ -272,12 +298,35 @@ func TestSendKeysend_TLVs(t *testing.T) {
|
|||
transaction, err := transactionsService.SendKeysend(ctx, uint64(1000), "fake destination", []lnclient.TLVRecord{
|
||||
{
|
||||
Type: 7629169,
|
||||
Value: "48656C6C6F2C20776F726C64",
|
||||
Value: "7b22616374696f6e223a22626f6f7374222c2276616c75655f6d736174223a313030302c2276616c75655f6d7361745f746f74616c223a313030302c226170705f6e616d65223a22e29aa1205765624c4e2044656d6f222c226170705f76657273696f6e223a22312e30222c22666565644944223a2268747470733a2f2f66656564732e706f6463617374696e6465782e6f72672f706332302e786d6c222c22706f6463617374223a22506f6463617374696e6720322e30222c22657069736f6465223a22457069736f6465203130343a2041204e65772044756d70222c227473223a32312c226e616d65223a22e29aa1205765624c4e2044656d6f222c2273656e6465725f6e616d65223a225361746f736869204e616b616d6f746f222c226d657373616765223a22476f20706f6463617374696e6721227d",
|
||||
},
|
||||
}, "", svc.LNClient, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"destination":"fake destination","tlv_records":[{"type":7629169,"value":"48656C6C6F2C20776F726C64"}]}`, transaction.Metadata)
|
||||
|
||||
var metadata lnclient.Metadata
|
||||
err = json.Unmarshal(transaction.Metadata, &metadata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "fake destination", metadata["destination"])
|
||||
assert.NotNil(t, metadata["tlv_records"])
|
||||
|
||||
var boostagram Boostagram
|
||||
err = json.Unmarshal(transaction.Boostagram, &boostagram)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "⚡ WebLN Demo", boostagram.AppName)
|
||||
assert.Equal(t, "⚡ WebLN Demo", boostagram.Name)
|
||||
assert.Equal(t, "Podcasting 2.0", boostagram.Podcast)
|
||||
assert.Equal(t, "Episode 104: A New Dump", boostagram.Episode)
|
||||
assert.Equal(t, "https://feeds.podcastindex.org/pc20.xml", boostagram.FeedId)
|
||||
assert.Equal(t, int64(21), boostagram.Timestamp)
|
||||
assert.Equal(t, "Go podcasting!", boostagram.Message)
|
||||
assert.Equal(t, "Satoshi Nakamoto", boostagram.SenderName)
|
||||
assert.Equal(t, "boost", boostagram.Action)
|
||||
assert.Equal(t, int64(1000), boostagram.ValueMsatTotal)
|
||||
|
||||
assert.Equal(t, "Go podcasting!", transaction.Description)
|
||||
|
||||
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
||||
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package transactions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -18,16 +19,21 @@ func TestMakeInvoice_NoApp(t *testing.T) {
|
|||
svc, err := tests.CreateTestService()
|
||||
assert.NoError(t, err)
|
||||
|
||||
metadata := strings.Repeat("a", constants.INVOICE_METADATA_MAX_LENGTH-2) // json encoding adds 2 characters
|
||||
txMetadata := make(map[string]interface{})
|
||||
txMetadata["randomkey"] = strings.Repeat("a", constants.INVOICE_METADATA_MAX_LENGTH-16) // json encoding adds 16 characters - {"randomkey":""}
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.MakeInvoice(ctx, 1234, "Hello world", "", 0, metadata, svc.LNClient, nil, nil)
|
||||
|
||||
transaction, err := transactionsService.MakeInvoice(ctx, 1234, "Hello world", "", 0, txMetadata, svc.LNClient, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var metadata map[string]interface{}
|
||||
err = json.Unmarshal(transaction.Metadata, &metadata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, uint64(tests.MockLNClientTransaction.Amount), transaction.AmountMsat)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_PENDING, transaction.State)
|
||||
assert.Equal(t, tests.MockLNClientTransaction.Preimage, *transaction.Preimage)
|
||||
assert.Equal(t, `"`+metadata+`"`, transaction.Metadata) // json-encoded
|
||||
assert.Equal(t, txMetadata["randomkey"], metadata["randomkey"])
|
||||
}
|
||||
|
||||
func TestMakeInvoice_MetadataTooLarge(t *testing.T) {
|
||||
|
|
@ -36,7 +42,9 @@ func TestMakeInvoice_MetadataTooLarge(t *testing.T) {
|
|||
defer tests.RemoveTestService()
|
||||
svc, err := tests.CreateTestService()
|
||||
assert.NoError(t, err)
|
||||
metadata := strings.Repeat("a", constants.INVOICE_METADATA_MAX_LENGTH-1) // json encoding adds 2 characters
|
||||
|
||||
metadata := make(map[string]interface{})
|
||||
metadata["randomkey"] = strings.Repeat("a", constants.INVOICE_METADATA_MAX_LENGTH-15) // json encoding adds 16 characters
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
transaction, err := transactionsService.MakeInvoice(ctx, 1234, "Hello world", "", 0, metadata, svc.LNClient, nil, nil)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package transactions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/db"
|
||||
"github.com/getAlby/hub/events"
|
||||
"github.com/getAlby/hub/lnclient"
|
||||
"github.com/getAlby/hub/tests"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -74,6 +76,72 @@ func TestNotifications_ReceivedUnknownPayment(t *testing.T) {
|
|||
assert.Equal(t, int64(1), result.RowsAffected)
|
||||
}
|
||||
|
||||
func TestNotifications_ReceivedKeysend(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
|
||||
defer tests.RemoveTestService()
|
||||
svc, err := tests.CreateTestService()
|
||||
assert.NoError(t, err)
|
||||
|
||||
transactionsService := NewTransactionsService(svc.DB)
|
||||
|
||||
metadata := map[string]interface{}{}
|
||||
|
||||
metadata["tlv_records"] = []lnclient.TLVRecord{
|
||||
{
|
||||
Type: 7629169,
|
||||
Value: "7b22616374696f6e223a22626f6f7374222c2276616c75655f6d736174223a313030302c2276616c75655f6d7361745f746f74616c223a313030302c226170705f6e616d65223a22e29aa1205765624c4e2044656d6f222c226170705f76657273696f6e223a22312e30222c22666565644944223a2268747470733a2f2f66656564732e706f6463617374696e6465782e6f72672f706332302e786d6c222c22706f6463617374223a22506f6463617374696e6720322e30222c22657069736f6465223a22457069736f6465203130343a2041204e65772044756d70222c227473223a32312c226e616d65223a22e29aa1205765624c4e2044656d6f222c2273656e6465725f6e616d65223a225361746f736869204e616b616d6f746f222c226d657373616765223a22476f20706f6463617374696e6721227d",
|
||||
},
|
||||
}
|
||||
|
||||
transaction := &lnclient.Transaction{
|
||||
Type: "incoming",
|
||||
Invoice: tests.MockInvoice,
|
||||
Description: "",
|
||||
DescriptionHash: "",
|
||||
Preimage: tests.MockLNClientTransaction.Preimage,
|
||||
PaymentHash: tests.MockLNClientTransaction.PaymentHash,
|
||||
Amount: 2000,
|
||||
FeesPaid: 75,
|
||||
SettledAt: &tests.MockTimeUnix,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
transactionsService.ConsumeEvent(ctx, &events.Event{
|
||||
Event: "nwc_payment_received",
|
||||
Properties: transaction,
|
||||
}, map[string]interface{}{})
|
||||
|
||||
transactionType := constants.TRANSACTION_TYPE_INCOMING
|
||||
incomingTransaction, err := transactionsService.LookupTransaction(ctx, tests.MockLNClientTransaction.PaymentHash, &transactionType, svc.LNClient, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(2000), incomingTransaction.AmountMsat)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, incomingTransaction.State)
|
||||
assert.Equal(t, tests.MockLNClientTransaction.Preimage, *incomingTransaction.Preimage)
|
||||
assert.Zero(t, incomingTransaction.FeeReserveMsat)
|
||||
|
||||
var boostagram Boostagram
|
||||
err = json.Unmarshal(incomingTransaction.Boostagram, &boostagram)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "⚡ WebLN Demo", boostagram.AppName)
|
||||
assert.Equal(t, "⚡ WebLN Demo", boostagram.Name)
|
||||
assert.Equal(t, "Podcasting 2.0", boostagram.Podcast)
|
||||
assert.Equal(t, "Episode 104: A New Dump", boostagram.Episode)
|
||||
assert.Equal(t, "https://feeds.podcastindex.org/pc20.xml", boostagram.FeedId)
|
||||
assert.Equal(t, int64(21), boostagram.Timestamp)
|
||||
assert.Equal(t, "Go podcasting!", boostagram.Message)
|
||||
assert.Equal(t, "Satoshi Nakamoto", boostagram.SenderName)
|
||||
assert.Equal(t, "boost", boostagram.Action)
|
||||
assert.Equal(t, int64(1000), boostagram.ValueMsatTotal)
|
||||
|
||||
assert.Equal(t, "Go podcasting!", incomingTransaction.Description)
|
||||
|
||||
transactions := []db.Transaction{}
|
||||
result := svc.DB.Find(&transactions)
|
||||
assert.Equal(t, int64(1), result.RowsAffected)
|
||||
}
|
||||
|
||||
func TestNotifications_SentKnownPayment(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
|
||||
|
|
@ -130,15 +198,8 @@ func TestNotifications_SentUnknownPayment(t *testing.T) {
|
|||
|
||||
transactionType := constants.TRANSACTION_TYPE_OUTGOING
|
||||
outgoingTransaction, err := transactionsService.LookupTransaction(ctx, tests.MockLNClientTransaction.PaymentHash, &transactionType, svc.LNClient, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(tests.MockLNClientTransaction.Amount), outgoingTransaction.AmountMsat)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, outgoingTransaction.State)
|
||||
assert.Equal(t, tests.MockLNClientTransaction.Preimage, *outgoingTransaction.Preimage)
|
||||
assert.Zero(t, outgoingTransaction.FeeReserveMsat)
|
||||
|
||||
transactions = []db.Transaction{}
|
||||
result = svc.DB.Find(&transactions)
|
||||
assert.Equal(t, int64(1), result.RowsAffected)
|
||||
assert.Nil(t, outgoingTransaction)
|
||||
assert.ErrorIs(t, err, NewNotFoundError())
|
||||
}
|
||||
|
||||
func TestNotifications_FailedKnownPayment(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/getAlby/hub/logger"
|
||||
decodepay "github.com/nbd-wtf/ln-decodepay"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
|
@ -30,15 +31,37 @@ type transactionsService struct {
|
|||
|
||||
type TransactionsService interface {
|
||||
events.EventSubscriber
|
||||
MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, metadata interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
LookupTransaction(ctx context.Context, paymentHash string, transactionType *string, lnClient lnclient.LNClient, appId *uint) (*Transaction, error)
|
||||
ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, transactionType *string, lnClient lnclient.LNClient, appId *uint) (transactions []Transaction, err error)
|
||||
SendPaymentSync(ctx context.Context, payReq string, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
SendKeysend(ctx context.Context, amount uint64, destination string, customRecords []lnclient.TLVRecord, preimage string, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
}
|
||||
|
||||
const (
|
||||
BoostagramTlvType = 7629169
|
||||
WhatsatTlvType = 34349334
|
||||
)
|
||||
|
||||
type Transaction = db.Transaction
|
||||
|
||||
type Boostagram struct {
|
||||
AppName string `json:"app_name"`
|
||||
Name string `json:"name"`
|
||||
Podcast string `json:"podcast"`
|
||||
URL string `json:"url"`
|
||||
Episode string `json:"episode,omitempty"`
|
||||
FeedId string `json:"feedID,omitempty"`
|
||||
ItemId string `json:"itemID,omitempty"`
|
||||
Timestamp int64 `json:"ts,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
SenderId string `json:"sender_id"`
|
||||
SenderName string `json:"sender_name"`
|
||||
Time string `json:"time"`
|
||||
Action string `json:"action"`
|
||||
ValueMsatTotal int64 `json:"value_msat_total"`
|
||||
}
|
||||
|
||||
type notFoundError struct {
|
||||
}
|
||||
|
||||
|
|
@ -78,10 +101,11 @@ func NewTransactionsService(db *gorm.DB) *transactionsService {
|
|||
}
|
||||
}
|
||||
|
||||
func (svc *transactionsService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, metadata interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) {
|
||||
var encodedMetadata string
|
||||
func (svc *transactionsService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) {
|
||||
var metadataBytes []byte
|
||||
if metadata != nil {
|
||||
metadataBytes, err := json.Marshal(metadata)
|
||||
var err error
|
||||
metadataBytes, err = json.Marshal(metadata)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to serialize metadata")
|
||||
return nil, err
|
||||
|
|
@ -89,7 +113,6 @@ func (svc *transactionsService) MakeInvoice(ctx context.Context, amount int64, d
|
|||
if len(metadataBytes) > constants.INVOICE_METADATA_MAX_LENGTH {
|
||||
return nil, fmt.Errorf("encoded invoice metadata provided is too large. Limit: %d Received: %d", constants.INVOICE_METADATA_MAX_LENGTH, len(metadataBytes))
|
||||
}
|
||||
encodedMetadata = string(metadataBytes)
|
||||
}
|
||||
|
||||
lnClientTransaction, err := lnClient.MakeInvoice(ctx, amount, description, descriptionHash, expiry)
|
||||
|
|
@ -121,7 +144,7 @@ func (svc *transactionsService) MakeInvoice(ctx context.Context, amount int64, d
|
|||
PaymentHash: lnClientTransaction.PaymentHash,
|
||||
ExpiresAt: expiresAt,
|
||||
Preimage: preimage,
|
||||
Metadata: encodedMetadata,
|
||||
Metadata: datatypes.JSON(metadataBytes),
|
||||
}
|
||||
err = svc.db.Create(&dbTransaction).Error
|
||||
if err != nil {
|
||||
|
|
@ -263,13 +286,14 @@ func (svc *transactionsService) SendKeysend(ctx context.Context, amount uint64,
|
|||
metadata := map[string]interface{}{}
|
||||
|
||||
metadata["destination"] = destination
|
||||
|
||||
metadata["tlv_records"] = customRecords
|
||||
metadataBytes, err := json.Marshal(metadata)
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to marshal metadata")
|
||||
logger.Logger.WithError(err).Error("Failed to serialize transaction metadata")
|
||||
return nil, err
|
||||
}
|
||||
boostagramBytes := svc.getBoostagramFromCustomRecords(customRecords)
|
||||
|
||||
var dbTransaction db.Transaction
|
||||
|
||||
|
|
@ -281,12 +305,14 @@ func (svc *transactionsService) SendKeysend(ctx context.Context, amount uint64,
|
|||
|
||||
dbTransaction = db.Transaction{
|
||||
AppId: appId,
|
||||
Description: svc.getDescriptionFromCustomRecords(customRecords),
|
||||
RequestEventId: requestEventId,
|
||||
Type: constants.TRANSACTION_TYPE_OUTGOING,
|
||||
State: constants.TRANSACTION_STATE_PENDING,
|
||||
FeeReserveMsat: svc.calculateFeeReserveMsat(uint64(amount)),
|
||||
AmountMsat: amount,
|
||||
Metadata: string(metadataBytes),
|
||||
Metadata: datatypes.JSON(metadataBytes),
|
||||
Boostagram: datatypes.JSON(boostagramBytes),
|
||||
PaymentHash: paymentHash,
|
||||
Preimage: &preimage,
|
||||
}
|
||||
|
|
@ -532,15 +558,25 @@ func (svc *transactionsService) ConsumeEvent(ctx context.Context, event *events.
|
|||
})
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
// Note: brand new payments cannot be associated with an app
|
||||
var metadata string
|
||||
// TODO: support customkey/customvalue for boostagrams received to isolated apps
|
||||
description := lnClientTransaction.Description
|
||||
var metadataBytes []byte
|
||||
var boostagramBytes []byte
|
||||
if lnClientTransaction.Metadata != nil {
|
||||
metadataBytes, err := json.Marshal(lnClientTransaction.Metadata)
|
||||
var err error
|
||||
metadataBytes, err = json.Marshal(lnClientTransaction.Metadata)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to serialize transaction metadata")
|
||||
return err
|
||||
}
|
||||
metadata = string(metadataBytes)
|
||||
|
||||
var customRecords []lnclient.TLVRecord
|
||||
customRecords, _ = lnClientTransaction.Metadata["tlv_records"].([]lnclient.TLVRecord)
|
||||
boostagramBytes = svc.getBoostagramFromCustomRecords(customRecords)
|
||||
extractedDescription := svc.getDescriptionFromCustomRecords(customRecords)
|
||||
if extractedDescription != "" {
|
||||
description = extractedDescription
|
||||
}
|
||||
}
|
||||
var expiresAt *time.Time
|
||||
if lnClientTransaction.ExpiresAt != nil {
|
||||
|
|
@ -552,10 +588,11 @@ func (svc *transactionsService) ConsumeEvent(ctx context.Context, event *events.
|
|||
AmountMsat: uint64(lnClientTransaction.Amount),
|
||||
PaymentRequest: lnClientTransaction.Invoice,
|
||||
PaymentHash: lnClientTransaction.PaymentHash,
|
||||
Description: lnClientTransaction.Description,
|
||||
Description: description,
|
||||
DescriptionHash: lnClientTransaction.DescriptionHash,
|
||||
ExpiresAt: expiresAt,
|
||||
Metadata: metadata,
|
||||
Metadata: datatypes.JSON(metadataBytes),
|
||||
Boostagram: datatypes.JSON(boostagramBytes),
|
||||
}
|
||||
err := tx.Create(&dbTransaction).Error
|
||||
if err != nil {
|
||||
|
|
@ -606,38 +643,10 @@ func (svc *transactionsService) ConsumeEvent(ctx context.Context, event *events.
|
|||
})
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
// Note: brand new payments cannot be associated with an app
|
||||
var metadata string
|
||||
if lnClientTransaction.Metadata != nil {
|
||||
metadataBytes, err := json.Marshal(lnClientTransaction.Metadata)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to serialize transaction metadata")
|
||||
return err
|
||||
}
|
||||
metadata = string(metadataBytes)
|
||||
}
|
||||
var expiresAt *time.Time
|
||||
if lnClientTransaction.ExpiresAt != nil {
|
||||
expiresAtValue := time.Unix(*lnClientTransaction.ExpiresAt, 0)
|
||||
expiresAt = &expiresAtValue
|
||||
}
|
||||
dbTransaction = db.Transaction{
|
||||
Type: constants.TRANSACTION_TYPE_OUTGOING,
|
||||
AmountMsat: uint64(lnClientTransaction.Amount),
|
||||
PaymentRequest: lnClientTransaction.Invoice,
|
||||
PaymentHash: lnClientTransaction.PaymentHash,
|
||||
Description: lnClientTransaction.Description,
|
||||
DescriptionHash: lnClientTransaction.DescriptionHash,
|
||||
ExpiresAt: expiresAt,
|
||||
Metadata: metadata,
|
||||
}
|
||||
err := tx.Create(&dbTransaction).Error
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"payment_hash": lnClientTransaction.PaymentHash,
|
||||
}).WithError(err).Error("Failed to create transaction")
|
||||
return err
|
||||
}
|
||||
// Note: payments made from outside cannot be associated with an app
|
||||
// for now this is disabled as it only applies to LND, and we do not import LND transactions either.
|
||||
logger.Logger.WithField("payment_hash", lnClientTransaction.PaymentHash).Error("payment not found")
|
||||
return NewNotFoundError()
|
||||
}
|
||||
|
||||
settledAt := time.Now()
|
||||
|
|
@ -784,3 +793,45 @@ func makePreimageHex() ([]byte, error) {
|
|||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) getBoostagramFromCustomRecords(customRecords []lnclient.TLVRecord) []byte {
|
||||
for _, record := range customRecords {
|
||||
if record.Type == BoostagramTlvType {
|
||||
bytes, err := hex.DecodeString(record.Value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) getDescriptionFromCustomRecords(customRecords []lnclient.TLVRecord) string {
|
||||
var description string
|
||||
|
||||
for _, record := range customRecords {
|
||||
switch record.Type {
|
||||
case BoostagramTlvType:
|
||||
bytes, err := hex.DecodeString(record.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var boostagram Boostagram
|
||||
if err := json.Unmarshal(bytes, &boostagram); err != nil {
|
||||
continue
|
||||
}
|
||||
return boostagram.Message
|
||||
|
||||
// TODO: consider adding support for this in LDK
|
||||
case WhatsatTlvType:
|
||||
bytes, err := hex.DecodeString(record.Value)
|
||||
if err == nil {
|
||||
description = string(bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return description
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue