Skip to content

Commit

Permalink
feat(core): enforce uniqueness and add tx external id param (#4287)
Browse files Browse the repository at this point in the history
* revert(core): remove external id uniqueness from invoices collection

This reverts commit d272b1f.

* revert(core): remove external id param from mutations

This reverts commit a3971a7.

* revert(core): update codegen

This reverts commit 9f5780d.
  • Loading branch information
vindard authored Apr 16, 2024
1 parent 69bff43 commit 31596c7
Show file tree
Hide file tree
Showing 16 changed files with 134 additions and 24 deletions.
7 changes: 7 additions & 0 deletions apps/consent/app/graphql/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ export type LnInvoiceCreateInput = {
readonly amount: Scalars['SatAmount']['input'];
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a BTC wallet belonging to the current account. */
Expand All @@ -650,6 +651,7 @@ export type LnInvoiceCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']['input']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a BTC wallet which belongs to any account. */
Expand Down Expand Up @@ -716,6 +718,7 @@ export type LnNoAmountInvoice = Invoice & {
export type LnNoAmountInvoiceCreateInput = {
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** ID for either a USD or BTC wallet belonging to the account of the current user. */
Expand All @@ -725,6 +728,7 @@ export type LnNoAmountInvoiceCreateInput = {
export type LnNoAmountInvoiceCreateOnBehalfOfRecipientInput = {
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** ID for either a USD or BTC wallet which belongs to the account of any user. */
Expand Down Expand Up @@ -787,6 +791,7 @@ export type LnUsdInvoiceBtcDenominatedCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']['input']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. Acts as a note to the recipient. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a USD wallet which belongs to the account of any user. */
Expand All @@ -798,6 +803,7 @@ export type LnUsdInvoiceCreateInput = {
readonly amount: Scalars['CentAmount']['input'];
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a USD wallet belonging to the current user. */
Expand All @@ -810,6 +816,7 @@ export type LnUsdInvoiceCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']['input']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. Acts as a note to the recipient. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a USD wallet which belongs to the account of any user. */
Expand Down
7 changes: 7 additions & 0 deletions apps/dashboard/services/graphql/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ export type LnInvoiceCreateInput = {
readonly amount: Scalars['SatAmount']['input'];
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a BTC wallet belonging to the current account. */
Expand All @@ -690,6 +691,7 @@ export type LnInvoiceCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']['input']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a BTC wallet which belongs to any account. */
Expand Down Expand Up @@ -756,6 +758,7 @@ export type LnNoAmountInvoice = Invoice & {
export type LnNoAmountInvoiceCreateInput = {
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** ID for either a USD or BTC wallet belonging to the account of the current user. */
Expand All @@ -765,6 +768,7 @@ export type LnNoAmountInvoiceCreateInput = {
export type LnNoAmountInvoiceCreateOnBehalfOfRecipientInput = {
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** ID for either a USD or BTC wallet which belongs to the account of any user. */
Expand Down Expand Up @@ -827,6 +831,7 @@ export type LnUsdInvoiceBtcDenominatedCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']['input']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. Acts as a note to the recipient. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a USD wallet which belongs to the account of any user. */
Expand All @@ -838,6 +843,7 @@ export type LnUsdInvoiceCreateInput = {
readonly amount: Scalars['CentAmount']['input'];
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a USD wallet belonging to the current user. */
Expand All @@ -850,6 +856,7 @@ export type LnUsdInvoiceCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']['input']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. Acts as a note to the recipient. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a USD wallet which belongs to the account of any user. */
Expand Down
7 changes: 7 additions & 0 deletions apps/map/services/galoy/graphql/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ export type LnInvoiceCreateInput = {
readonly amount: Scalars['SatAmount']['input'];
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a BTC wallet belonging to the current account. */
Expand All @@ -650,6 +651,7 @@ export type LnInvoiceCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']['input']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a BTC wallet which belongs to any account. */
Expand Down Expand Up @@ -716,6 +718,7 @@ export type LnNoAmountInvoice = Invoice & {
export type LnNoAmountInvoiceCreateInput = {
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** ID for either a USD or BTC wallet belonging to the account of the current user. */
Expand All @@ -725,6 +728,7 @@ export type LnNoAmountInvoiceCreateInput = {
export type LnNoAmountInvoiceCreateOnBehalfOfRecipientInput = {
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** ID for either a USD or BTC wallet which belongs to the account of any user. */
Expand Down Expand Up @@ -787,6 +791,7 @@ export type LnUsdInvoiceBtcDenominatedCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']['input']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. Acts as a note to the recipient. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a USD wallet which belongs to the account of any user. */
Expand All @@ -798,6 +803,7 @@ export type LnUsdInvoiceCreateInput = {
readonly amount: Scalars['CentAmount']['input'];
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a USD wallet belonging to the current user. */
Expand All @@ -810,6 +816,7 @@ export type LnUsdInvoiceCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']['input']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']['input']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']['input']>;
/** Optional memo for the lightning invoice. Acts as a note to the recipient. */
readonly memo?: InputMaybe<Scalars['Memo']['input']>;
/** Wallet ID for a USD wallet which belongs to the account of any user. */
Expand Down
7 changes: 7 additions & 0 deletions apps/pay/lib/graphql/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ export type LnInvoiceCreateInput = {
readonly amount: Scalars['SatAmount'];
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']>;
/** Wallet ID for a BTC wallet belonging to the current account. */
Expand All @@ -649,6 +650,7 @@ export type LnInvoiceCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']>;
/** Wallet ID for a BTC wallet which belongs to any account. */
Expand Down Expand Up @@ -715,6 +717,7 @@ export type LnNoAmountInvoice = Invoice & {
export type LnNoAmountInvoiceCreateInput = {
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']>;
/** ID for either a USD or BTC wallet belonging to the account of the current user. */
Expand All @@ -724,6 +727,7 @@ export type LnNoAmountInvoiceCreateInput = {
export type LnNoAmountInvoiceCreateOnBehalfOfRecipientInput = {
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']>;
/** ID for either a USD or BTC wallet which belongs to the account of any user. */
Expand Down Expand Up @@ -786,6 +790,7 @@ export type LnUsdInvoiceBtcDenominatedCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']>;
/** Optional memo for the lightning invoice. Acts as a note to the recipient. */
readonly memo?: InputMaybe<Scalars['Memo']>;
/** Wallet ID for a USD wallet which belongs to the account of any user. */
Expand All @@ -797,6 +802,7 @@ export type LnUsdInvoiceCreateInput = {
readonly amount: Scalars['CentAmount'];
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']>;
/** Optional memo for the lightning invoice. */
readonly memo?: InputMaybe<Scalars['Memo']>;
/** Wallet ID for a USD wallet belonging to the current user. */
Expand All @@ -809,6 +815,7 @@ export type LnUsdInvoiceCreateOnBehalfOfRecipientInput = {
readonly descriptionHash?: InputMaybe<Scalars['Hex32Bytes']>;
/** Optional invoice expiration time in minutes. */
readonly expiresIn?: InputMaybe<Scalars['Minutes']>;
readonly externalId?: InputMaybe<Scalars['TxExternalId']>;
/** Optional memo for the lightning invoice. Acts as a note to the recipient. */
readonly memo?: InputMaybe<Scalars['Memo']>;
/** Wallet ID for a USD wallet which belongs to the account of any user. */
Expand Down
29 changes: 28 additions & 1 deletion bats/core/api/invoices.bats
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ seed_invoices() {
)
exec_graphql "$token_name" 'ln-invoice-create' "$variables"

external_id="seed-$RANDOM"
variables=$(
jq -n \
--arg wallet_id "$(read_value $btc_wallet_name)" \
--arg amount "$btc_amount" \
'{input: {walletId: $wallet_id, amount: $amount}}'
--arg external_id "$external_id" \
'{input: {walletId: $wallet_id, amount: $amount, externalId: $external_id}}'
)
exec_graphql "$token_name" 'ln-invoice-create' "$variables"

Expand Down Expand Up @@ -67,6 +69,31 @@ seed_invoices() {
[[ "$num_errors" == "0" ]] || exit 1
}

@test "invoices: adding multiple invoices with same external id fails" {
token_name='alice'
btc_wallet_name="$token_name.btc_wallet_id"
btc_amount="1000"
external_id="external-id-$RANDOM"

variables=$(
jq -n \
--arg wallet_id "$(read_value $btc_wallet_name)" \
--arg amount "$btc_amount" \
--arg external_id "$external_id" \
'{input: {walletId: $wallet_id, amount: $amount, externalId: $external_id}}'
)

exec_graphql "$token_name" 'ln-invoice-create' "$variables"
num_errors="$(graphql_output '.data.lnInvoiceCreate.errors | length')"
[[ "$num_errors" == "0" ]] || exit 1

exec_graphql "$token_name" 'ln-invoice-create' "$variables"
invoice="$(graphql_output '.data.lnInvoiceCreate.invoice')"
[[ "$invoice" == "null" ]] || exit 1
error_msg="$(graphql_output '.data.lnInvoiceCreate.errors[0].message')"
[[ "${error_msg}" =~ "already exists" ]] || exit 1
}

@test "invoices: get invoices for account" {
token_name='alice'

Expand Down
6 changes: 4 additions & 2 deletions bats/core/api/ln-receive.bats
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ usd_amount=50
num_callback_events_before=$(cat_callback | grep "$account_id" | wc -l)

# Generate invoice
external_id="test-$RANDOM"
variables=$(
jq -n \
--arg wallet_id "$(read_value $btc_wallet_name)" \
--arg amount "$btc_amount" \
'{input: {walletId: $wallet_id, amount: $amount}}'
--arg external_id "$external_id" \
'{input: {walletId: $wallet_id, amount: $amount, externalId: $external_id}}'
)
exec_graphql "$token_name" 'ln-invoice-create' "$variables"
invoice="$(graphql_output '.data.lnInvoiceCreate.invoice')"
Expand Down Expand Up @@ -165,7 +167,7 @@ usd_amount=50
| awk 'BEGIN{RS="callback │ "}{if(NR>1)print $0}' \
| jq -r '.transaction.externalId'
)
[[ "$external_id_from_callback" == "$payment_hash" ]] || exit 1
[[ "$external_id_from_callback" == "$external_id" ]] || exit 1
}

@test "ln-receive: settle via ln for USD wallet, invoice with amount" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Minutes from "@/graphql/public/types/scalar/minutes"
import WalletId from "@/graphql/shared/types/scalar/wallet-id"
import SatAmount from "@/graphql/shared/types/scalar/sat-amount"
import Hex32Bytes from "@/graphql/public/types/scalar/hex32bytes"
import TxExternalId from "@/graphql/shared/types/scalar/tx-external-id"
import LnInvoicePayload from "@/graphql/public/types/payload/ln-invoice"
import { mapAndParseErrorForGqlResponse } from "@/graphql/error-map"

Expand All @@ -25,6 +26,7 @@ const LnInvoiceCreateOnBehalfOfRecipientInput = GT.Input({
type: Minutes,
description: "Optional invoice expiration time in minutes.",
},
externalId: { type: TxExternalId },
}),
})

Expand All @@ -40,8 +42,16 @@ const LnInvoiceCreateOnBehalfOfRecipientMutation = GT.Field({
input: { type: GT.NonNull(LnInvoiceCreateOnBehalfOfRecipientInput) },
},
resolve: async (_, args) => {
const { recipientWalletId, amount, memo, descriptionHash, expiresIn } = args.input
for (const input of [recipientWalletId, amount, memo, descriptionHash, expiresIn]) {
const { recipientWalletId, amount, memo, descriptionHash, expiresIn, externalId } =
args.input
for (const input of [
recipientWalletId,
amount,
memo,
descriptionHash,
expiresIn,
externalId,
]) {
if (input instanceof Error) {
return { errors: [{ message: input.message }] }
}
Expand All @@ -53,7 +63,7 @@ const LnInvoiceCreateOnBehalfOfRecipientMutation = GT.Field({
memo,
descriptionHash,
expiresIn,
externalId: undefined,
externalId,
})

if (invoice instanceof Error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Memo from "@/graphql/shared/types/scalar/memo"
import Minutes from "@/graphql/public/types/scalar/minutes"
import WalletId from "@/graphql/shared/types/scalar/wallet-id"
import SatAmount from "@/graphql/shared/types/scalar/sat-amount"
import TxExternalId from "@/graphql/shared/types/scalar/tx-external-id"
import LnInvoicePayload from "@/graphql/public/types/payload/ln-invoice"
import { mapAndParseErrorForGqlResponse } from "@/graphql/error-map"

Expand All @@ -23,6 +24,7 @@ const LnInvoiceCreateInput = GT.Input({
type: Minutes,
description: "Optional invoice expiration time in minutes.",
},
externalId: { type: TxExternalId },
}),
})

Expand All @@ -38,9 +40,9 @@ const LnInvoiceCreateMutation = GT.Field({
input: { type: GT.NonNull(LnInvoiceCreateInput) },
},
resolve: async (_, args) => {
const { walletId, amount, memo, expiresIn } = args.input
const { walletId, amount, memo, expiresIn, externalId } = args.input

for (const input of [walletId, amount, memo, expiresIn]) {
for (const input of [walletId, amount, memo, expiresIn, externalId]) {
if (input instanceof Error) {
return { errors: [{ message: input.message }] }
}
Expand All @@ -51,7 +53,7 @@ const LnInvoiceCreateMutation = GT.Field({
amount,
memo,
expiresIn,
externalId: undefined,
externalId,
})

if (invoice instanceof Error) {
Expand Down
Loading

0 comments on commit 31596c7

Please sign in to comment.