From a1e0317240ca1465995bc6842c178d3f90437dae Mon Sep 17 00:00:00 2001 From: Sam Peters Date: Wed, 1 Nov 2023 14:17:52 -0500 Subject: [PATCH] feat: add pending tx queries --- .../dev/apollo-federation/supergraph.graphql | 31 ++++++ ...ending-onchain-transactions-for-account.ts | 33 +++++++ core/api/src/app/accounts/index.ts | 1 + ...ending-onchain-transactions-for-wallets.ts | 21 ++++ .../get-pending-transactions-by-addresses.ts | 25 +++++ core/api/src/app/wallets/index.ts | 2 + core/api/src/graphql/admin/schema.graphql | 29 ++++++ core/api/src/graphql/public/schema.graphql | 31 ++++++ .../graphql/public/types/abstract/account.ts | 12 ++- .../public/types/object/consumer-account.ts | 33 ++++++- .../graphql/shared/types/abstract/wallet.ts | 20 ++++ .../graphql/shared/types/object/btc-wallet.ts | 34 +++++++ .../graphql/shared/types/object/usd-wallet.ts | 34 +++++++ .../gql/pending-transactions-by-address.gql | 56 +++++++++++ .../test/bats/gql/pending-transactions.gql | 51 ++++++++++ core/api/test/bats/onchain-receive.bats | 99 +++++++++++++++++++ .../apollo-federation/supergraph.graphql | 31 ++++++ 17 files changed, 541 insertions(+), 2 deletions(-) create mode 100644 core/api/src/app/accounts/get-pending-onchain-transactions-for-account.ts create mode 100644 core/api/src/app/wallets/get-pending-onchain-transactions-for-wallets.ts create mode 100644 core/api/src/app/wallets/get-pending-transactions-by-addresses.ts create mode 100644 core/api/test/bats/gql/pending-transactions-by-address.gql create mode 100644 core/api/test/bats/gql/pending-transactions.gql diff --git a/core/api/dev/apollo-federation/supergraph.graphql b/core/api/dev/apollo-federation/supergraph.graphql index 7ce9b05cdfc..3a0bb76ae6b 100644 --- a/core/api/dev/apollo-federation/supergraph.graphql +++ b/core/api/dev/apollo-federation/supergraph.graphql @@ -32,6 +32,7 @@ interface Account level: AccountLevel! limits: AccountLimits! notificationSettings: NotificationSettings! + pendingTransactions(walletIds: [WalletId]): [Transaction!]! realtimePrice: RealtimePrice! transactions( """Returns the items in the list that come after the specified cursor.""" @@ -183,6 +184,11 @@ type BTCWallet implements Wallet """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + pendingTransactions: [Transaction!]! + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! """A list of BTC transactions associated with this wallet.""" @@ -306,6 +312,7 @@ type ConsumerAccount implements Account level: AccountLevel! limits: AccountLimits! notificationSettings: NotificationSettings! + pendingTransactions(walletIds: [WalletId]): [Transaction!]! """List the quiz questions of the consumer account""" quiz: [Quiz!]! @@ -1617,6 +1624,11 @@ type UsdWallet implements Wallet """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + pendingTransactions: [Transaction!]! + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! transactions( """Returns the items in the list that come after the specified cursor.""" @@ -1922,6 +1934,25 @@ interface Wallet paymentHash: PaymentHash! ): Invoice! pendingIncomingBalance: SignedAmount! + + """ + Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first + """ + pendingTransactions: [Transaction!]! + + """ + Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first + """ + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! """ diff --git a/core/api/src/app/accounts/get-pending-onchain-transactions-for-account.ts b/core/api/src/app/accounts/get-pending-onchain-transactions-for-account.ts new file mode 100644 index 00000000000..0d846e8e1f2 --- /dev/null +++ b/core/api/src/app/accounts/get-pending-onchain-transactions-for-account.ts @@ -0,0 +1,33 @@ +import { getPendingOnChainTransactionsForWallets } from "../wallets/get-pending-onchain-transactions-for-wallets" + +import { AccountValidator } from "@/domain/accounts" +import { RepositoryError } from "@/domain/errors" +import { WalletsRepository } from "@/services/mongoose" +import { checkedToWalletId } from "@/domain/wallets" + +export const getPendingOnChainTransactionsForAccountByWalletIds = async ({ + account, + walletIds, +}: { + account: Account + walletIds: string[] +}): Promise => { + const walletsRepo = WalletsRepository() + + const wallets: Wallet[] = [] + for (const uncheckedWalletId of walletIds) { + const walletId = checkedToWalletId(uncheckedWalletId) + if (walletId instanceof Error) return walletId + const wallet = await walletsRepo.findById(walletId) + if (wallet instanceof RepositoryError) return wallet + + const accountValidator = AccountValidator(account) + if (accountValidator instanceof Error) return accountValidator + const validateWallet = accountValidator.validateWalletForAccount(wallet) + if (validateWallet instanceof Error) return validateWallet + + wallets.push(wallet) + } + + return getPendingOnChainTransactionsForWallets({ wallets }) +} diff --git a/core/api/src/app/accounts/index.ts b/core/api/src/app/accounts/index.ts index 266908e1744..399fdbeb4d7 100644 --- a/core/api/src/app/accounts/index.ts +++ b/core/api/src/app/accounts/index.ts @@ -25,6 +25,7 @@ export * from "./disable-notification-category" export * from "./enable-notification-category" export * from "./enable-notification-channel" export * from "./disable-notification-channel" +export * from "./get-pending-onchain-transactions-for-account" const accounts = AccountsRepository() diff --git a/core/api/src/app/wallets/get-pending-onchain-transactions-for-wallets.ts b/core/api/src/app/wallets/get-pending-onchain-transactions-for-wallets.ts new file mode 100644 index 00000000000..f0edab562f8 --- /dev/null +++ b/core/api/src/app/wallets/get-pending-onchain-transactions-for-wallets.ts @@ -0,0 +1,21 @@ +import { CouldNotFindError } from "@/domain/errors" + +import { WalletOnChainPendingReceiveRepository } from "@/services/mongoose" + +export const getPendingOnChainTransactionsForWallets = async ({ + wallets, +}: { + wallets: Wallet[] +}): Promise => { + const walletIds = wallets.map((wallet) => wallet.id) + + const pendingHistory = await WalletOnChainPendingReceiveRepository().listByWalletIds({ + walletIds, + }) + + if (pendingHistory instanceof CouldNotFindError) { + return [] + } + + return pendingHistory +} diff --git a/core/api/src/app/wallets/get-pending-transactions-by-addresses.ts b/core/api/src/app/wallets/get-pending-transactions-by-addresses.ts new file mode 100644 index 00000000000..5e77cdc418f --- /dev/null +++ b/core/api/src/app/wallets/get-pending-transactions-by-addresses.ts @@ -0,0 +1,25 @@ +import { CouldNotFindError } from "@/domain/errors" + +import { WalletOnChainPendingReceiveRepository } from "@/services/mongoose" + +export const getPendingTransactionsForWalletsByAddresses = async ({ + wallets, + addresses, +}: { + wallets: Wallet[] + addresses: OnChainAddress[] +}): Promise => { + const walletIds = wallets.map((wallet) => wallet.id) + + const pendingHistory = + await WalletOnChainPendingReceiveRepository().listByWalletIdsAndAddresses({ + walletIds, + addresses, + }) + + if (pendingHistory instanceof CouldNotFindError) { + return [] + } + + return pendingHistory +} diff --git a/core/api/src/app/wallets/index.ts b/core/api/src/app/wallets/index.ts index f39e97348fd..3bb2a5a5fa9 100644 --- a/core/api/src/app/wallets/index.ts +++ b/core/api/src/app/wallets/index.ts @@ -17,6 +17,8 @@ export * from "./update-legacy-on-chain-receipt" export * from "./update-pending-invoices" export * from "./validate" export * from "./get-invoice-for-wallet-by-hash" +export * from "./get-pending-onchain-transactions-for-wallets" +export * from "./get-pending-transactions-by-addresses" import { WalletsRepository } from "@/services/mongoose" diff --git a/core/api/src/graphql/admin/schema.graphql b/core/api/src/graphql/admin/schema.graphql index 99fb5fd4a3e..edef2f0c94a 100644 --- a/core/api/src/graphql/admin/schema.graphql +++ b/core/api/src/graphql/admin/schema.graphql @@ -85,6 +85,11 @@ type BTCWallet implements Wallet { """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + pendingTransactions: [Transaction!]! + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! """A list of BTC transactions associated with this wallet.""" @@ -424,6 +429,11 @@ type UsdWallet implements Wallet { """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + pendingTransactions: [Transaction!]! + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! transactions( """Returns the items in the list that come after the specified cursor.""" @@ -480,6 +490,25 @@ interface Wallet { paymentHash: PaymentHash! ): Invoice! pendingIncomingBalance: SignedAmount! + + """ + Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first + """ + pendingTransactions: [Transaction!]! + + """ + Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first + """ + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! """ diff --git a/core/api/src/graphql/public/schema.graphql b/core/api/src/graphql/public/schema.graphql index 17c370aeec8..3ff5bb60205 100644 --- a/core/api/src/graphql/public/schema.graphql +++ b/core/api/src/graphql/public/schema.graphql @@ -7,6 +7,7 @@ interface Account { level: AccountLevel! limits: AccountLimits! notificationSettings: NotificationSettings! + pendingTransactions(walletIds: [WalletId]): [Transaction!]! realtimePrice: RealtimePrice! transactions( """Returns the items in the list that come after the specified cursor.""" @@ -126,6 +127,11 @@ type BTCWallet implements Wallet { """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + pendingTransactions: [Transaction!]! + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! """A list of BTC transactions associated with this wallet.""" @@ -227,6 +233,7 @@ type ConsumerAccount implements Account { level: AccountLevel! limits: AccountLimits! notificationSettings: NotificationSettings! + pendingTransactions(walletIds: [WalletId]): [Transaction!]! """List the quiz questions of the consumer account""" quiz: [Quiz!]! @@ -1269,6 +1276,11 @@ type UsdWallet implements Wallet { """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + pendingTransactions: [Transaction!]! + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! transactions( """Returns the items in the list that come after the specified cursor.""" @@ -1510,6 +1522,25 @@ interface Wallet { paymentHash: PaymentHash! ): Invoice! pendingIncomingBalance: SignedAmount! + + """ + Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first + """ + pendingTransactions: [Transaction!]! + + """ + Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first + """ + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! """ diff --git a/core/api/src/graphql/public/types/abstract/account.ts b/core/api/src/graphql/public/types/abstract/account.ts index 0466996f321..c1b82a0ab5c 100644 --- a/core/api/src/graphql/public/types/abstract/account.ts +++ b/core/api/src/graphql/public/types/abstract/account.ts @@ -13,7 +13,9 @@ import WalletId from "@/graphql/shared/types/scalar/wallet-id" import AccountLimits from "@/graphql/public/types/object/account-limits" import RealtimePrice from "@/graphql/public/types/object/realtime-price" import DisplayCurrency from "@/graphql/shared/types/scalar/display-currency" -import { TransactionConnection } from "@/graphql/shared/types/object/transaction" +import Transaction, { + TransactionConnection, +} from "@/graphql/shared/types/object/transaction" const IAccount = GT.Interface({ name: "Account", @@ -59,6 +61,14 @@ const IAccount = GT.Interface({ }, }, }, + pendingTransactions: { + type: GT.NonNullList(Transaction), + args: { + walletIds: { + type: GT.List(WalletId), + }, + }, + }, notificationSettings: { type: GT.NonNull(NotificationSettings), }, diff --git a/core/api/src/graphql/public/types/object/consumer-account.ts b/core/api/src/graphql/public/types/object/consumer-account.ts index 6d7d740bc7f..88b3d801b00 100644 --- a/core/api/src/graphql/public/types/object/consumer-account.ts +++ b/core/api/src/graphql/public/types/object/consumer-account.ts @@ -1,6 +1,8 @@ import AccountLevel from "../../../shared/types/scalar/account-level" -import { TransactionConnection } from "../../../shared/types/object/transaction" +import Transaction, { + TransactionConnection, +} from "../../../shared/types/object/transaction" import AccountLimits from "./account-limits" @@ -206,6 +208,35 @@ const ConsumerAccount = GT.Object({ ) }, }, + pendingTransactions: { + type: GT.NonNullList(Transaction), + args: { + walletIds: { + type: GT.List(WalletId), + }, + }, + resolve: async (source, args) => { + let { walletIds } = args + + if (!walletIds) { + const wallets = await WalletsRepository().listByAccountId(source.id) + if (wallets instanceof Error) { + throw mapError(wallets) + } + walletIds = wallets.map((wallet) => wallet.id) + } + + const transactions = + await Accounts.getPendingOnChainTransactionsForAccountByWalletIds({ + account: source, + walletIds, + }) + if (transactions instanceof Error) { + throw mapError(transactions) + } + return transactions + }, + }, notificationSettings: { type: GT.NonNull(NotificationSettings), diff --git a/core/api/src/graphql/shared/types/abstract/wallet.ts b/core/api/src/graphql/shared/types/abstract/wallet.ts index f4a39100716..ce5a1b3f495 100644 --- a/core/api/src/graphql/shared/types/abstract/wallet.ts +++ b/core/api/src/graphql/shared/types/abstract/wallet.ts @@ -48,6 +48,26 @@ const IWallet = GT.Interface({ }, }, }, + pendingTransactionsByAddress: { + description: dedent`Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first`, + type: GT.NonNullList(Transaction), + args: { + address: { + type: GT.NonNull(OnChainAddress), + description: "Returns the items that include this address.", + }, + }, + }, + pendingTransactions: { + description: dedent`Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first`, + type: GT.NonNullList(Transaction), + }, invoiceByPaymentHash: { type: GT.NonNull(IInvoice), args: { diff --git a/core/api/src/graphql/shared/types/object/btc-wallet.ts b/core/api/src/graphql/shared/types/object/btc-wallet.ts index 15c8d61d85d..0e1147c0dd1 100644 --- a/core/api/src/graphql/shared/types/object/btc-wallet.ts +++ b/core/api/src/graphql/shared/types/object/btc-wallet.ts @@ -92,6 +92,40 @@ const BtcWallet = GT.Object({ }, description: "A list of BTC transactions associated with this wallet.", }, + pendingTransactions: { + type: GT.NonNullList(Transaction), + resolve: async (source) => { + const transactions = await Wallets.getPendingOnChainTransactionsForWallets({ + wallets: [source], + }) + if (transactions instanceof Error) { + throw mapError(transactions) + } + return transactions + }, + }, + pendingTransactionsByAddress: { + type: GT.NonNullList(Transaction), + args: { + address: { + type: GT.NonNull(OnChainAddress), + description: "Returns the items that include this address.", + }, + }, + resolve: async (source, args) => { + const { address } = args + if (address instanceof Error) throw address + + const transactions = await Wallets.getPendingTransactionsForWalletsByAddresses({ + wallets: [source], + addresses: [address], + }) + if (transactions instanceof Error) { + throw mapError(transactions) + } + return transactions + }, + }, transactionsByAddress: { type: TransactionConnection, args: { diff --git a/core/api/src/graphql/shared/types/object/usd-wallet.ts b/core/api/src/graphql/shared/types/object/usd-wallet.ts index 93291623f84..429dfd32ca9 100644 --- a/core/api/src/graphql/shared/types/object/usd-wallet.ts +++ b/core/api/src/graphql/shared/types/object/usd-wallet.ts @@ -90,6 +90,40 @@ const UsdWallet = GT.Object({ ) }, }, + pendingTransactions: { + type: GT.NonNullList(Transaction), + resolve: async (source) => { + const transactions = await Wallets.getPendingOnChainTransactionsForWallets({ + wallets: [source], + }) + if (transactions instanceof Error) { + throw mapError(transactions) + } + return transactions + }, + }, + pendingTransactionsByAddress: { + type: GT.NonNullList(Transaction), + args: { + address: { + type: GT.NonNull(OnChainAddress), + description: "Returns the items that include this address.", + }, + }, + resolve: async (source, args) => { + const { address } = args + if (address instanceof Error) throw address + + const transactions = await Wallets.getPendingTransactionsForWalletsByAddresses({ + wallets: [source], + addresses: [address], + }) + if (transactions instanceof Error) { + throw mapError(transactions) + } + return transactions + }, + }, transactionsByAddress: { type: TransactionConnection, args: { diff --git a/core/api/test/bats/gql/pending-transactions-by-address.gql b/core/api/test/bats/gql/pending-transactions-by-address.gql new file mode 100644 index 00000000000..a31fa42a2a7 --- /dev/null +++ b/core/api/test/bats/gql/pending-transactions-by-address.gql @@ -0,0 +1,56 @@ +query pendingTransactionsByAddress($address: OnChainAddress!) { + me { + defaultAccount { + displayCurrency + wallets { + __typename + id + walletCurrency + pendingTransactionsByAddress(address: $address) { + __typename + id + status + direction + memo + createdAt + settlementAmount + settlementFee + settlementDisplayAmount + settlementDisplayFee + settlementDisplayCurrency + settlementCurrency + settlementPrice { + base + offset + } + initiationVia { + __typename + ... on InitiationViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on InitiationViaLn { + paymentHash + } + ... on InitiationViaOnChain { + address + } + } + settlementVia { + __typename + ... on SettlementViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on SettlementViaLn { + preImage + } + ... on SettlementViaOnChain { + transactionHash + } + } + } + } + } + } +} diff --git a/core/api/test/bats/gql/pending-transactions.gql b/core/api/test/bats/gql/pending-transactions.gql new file mode 100644 index 00000000000..bc26c2985a4 --- /dev/null +++ b/core/api/test/bats/gql/pending-transactions.gql @@ -0,0 +1,51 @@ +query pendingTransactions { + me { + defaultAccount { + displayCurrency + pendingTransactions { + __typename + id + status + direction + memo + createdAt + settlementAmount + settlementFee + settlementDisplayAmount + settlementDisplayFee + settlementDisplayCurrency + settlementCurrency + settlementPrice { + base + offset + } + initiationVia { + __typename + ... on InitiationViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on InitiationViaLn { + paymentHash + } + ... on InitiationViaOnChain { + address + } + } + settlementVia { + __typename + ... on SettlementViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on SettlementViaLn { + preImage + } + ... on SettlementViaOnChain { + transactionHash + } + } + } + } + } +} diff --git a/core/api/test/bats/onchain-receive.bats b/core/api/test/bats/onchain-receive.bats index 55660d559f9..609a44618aa 100644 --- a/core/api/test/bats/onchain-receive.bats +++ b/core/api/test/bats/onchain-receive.bats @@ -120,6 +120,54 @@ create_new_lnd_onchain_address() { bitcoin_cli sendtoaddress "$on_chain_address_created_2" "$amount" retry 15 1 check_for_broadcast "$token_name" "$on_chain_address_created_2" 1 + # Check pending transactions for address 1 + + address_1_pending_txns_variables=$( + jq -n \ + --arg address "$on_chain_address_created_1" \ + '{"address": $address}' + ) + exec_graphql "$token_name" 'pending-transactions-by-address' "$address_1_pending_txns_variables" + pending_txns_for_address_1=$( + graphql_output ' + .data.me.defaultAccount.wallets[] + | select(.__typename == "BTCWallet") + .pendingTransactionsByAddress' + ) + pending_txns_for_address_1_length="$(echo $pending_txns_for_address_1 | jq -r 'length')" + [[ "$pending_txns_for_address_1_length" == "1" ]] || exit 1 + address_1_from_pending_txns="$(echo $pending_txns_for_address_1 | jq -r '.[0].initiationVia.address')" + [[ "$address_1_from_pending_txns" == "$on_chain_address_created_1" ]] + + # Check pending transactions for address 2 + + address_2_pending_txns_variables=$( + jq -n \ + --arg address "$on_chain_address_created_2" \ + '{"address": $address}' + ) + exec_graphql "$token_name" 'pending-transactions-by-address' "$address_2_pending_txns_variables" + pending_txns_for_address_2=$( + graphql_output ' + .data.me.defaultAccount.wallets[] + | select(.__typename == "BTCWallet") + .pendingTransactionsByAddress' + ) + pending_txns_for_address_2_length="$(echo $pending_txns_for_address_2 | jq -r 'length')" + [[ "$pending_txns_for_address_2_length" == "1" ]] || exit 1 + address_2_from_pending_txns="$(echo $pending_txns_for_address_2 | jq -r '.[0].initiationVia.address')" + [[ "$address_2_from_pending_txns" == "$on_chain_address_created_2" ]] + + # Check pending transactions for account + + exec_graphql "$token_name" 'pending-transactions' + pending_txns_for_account=$( + graphql_output ' + .data.me.defaultAccount.pendingTransactions' + ) + pending_txns_for_account_length="$(echo $pending_txns_for_account | jq -r 'length')" + [[ "$pending_txns_for_account_length" == "2" ]] || exit 1 + # Mine transactions bitcoin_cli -generate 2 retry 15 1 check_for_onchain_initiated_settled "$token_name" "$on_chain_address_created_1" 2 @@ -162,6 +210,16 @@ create_new_lnd_onchain_address() { [[ "$txns_for_address_2_length" == "1" ]] || exit 1 address_2_from_txns="$(echo $txns_for_address_2 | jq -r '.[0].node.initiationVia.address')" [[ "$address_2_from_txns" == "$on_chain_address_created_2" ]] + + # Ensure no pending transactions for account + + exec_graphql "$token_name" 'pending-transactions' + pending_txns_for_account=$( + graphql_output ' + .data.me.defaultAccount.pendingTransactions' + ) + pending_txns_for_account_length="$(echo $pending_txns_for_account | jq -r 'length')" + [[ "$pending_txns_for_account_length" == "0" ]] || exit 1 } @test "onchain-receive: usd wallet, can create new address if current one is unused" { @@ -216,8 +274,49 @@ create_new_lnd_onchain_address() { # Execute onchain send and check for transaction bitcoin_cli sendtoaddress "$on_chain_address_created" "$amount" retry 15 1 check_for_broadcast "$token_name" "$on_chain_address_created" 1 + + # Check pending transactions for address + + address_pending_txns_variables=$( + jq -n \ + --arg address "$on_chain_address_created" \ + '{"address": $address}' + ) + exec_graphql "$token_name" 'pending-transactions-by-address' "$address_pending_txns_variables" + pending_txns_for_address=$( + graphql_output ' + .data.me.defaultAccount.wallets[] + | select(.__typename == "UsdWallet") + .pendingTransactionsByAddress' + ) + pending_txns_for_address_length="$(echo $pending_txns_for_address | jq -r 'length')" + [[ "$pending_txns_for_address_length" == "1" ]] || exit 1 + address_from_pending_txns="$(echo $pending_txns_for_address | jq -r '.[0].initiationVia.address')" + [[ "$address_from_pending_txns" == "$on_chain_address_created" ]] + + # Check pending transactions for account + + exec_graphql "$token_name" 'pending-transactions' + pending_txns_for_account=$( + graphql_output ' + .data.me.defaultAccount.pendingTransactions' + ) + pending_txns_for_account_length="$(echo $pending_txns_for_account | jq -r 'length')" + [[ "$pending_txns_for_account_length" == "1" ]] || exit 1 + bitcoin_cli -generate 2 retry 15 1 check_for_onchain_initiated_settled "$token_name" "$on_chain_address_created" 1 + + # Ensure no pending transactions for account + + exec_graphql "$token_name" 'pending-transactions' + pending_txns_for_account=$( + graphql_output ' + .data.me.defaultAccount.pendingTransactions' + ) + pending_txns_for_account_length="$(echo $pending_txns_for_account | jq -r 'length')" + [[ "$pending_txns_for_account_length" == "0" ]] || exit 1 + } @test "onchain-receive: process received batch transaction" { diff --git a/dev/config/apollo-federation/supergraph.graphql b/dev/config/apollo-federation/supergraph.graphql index 7ce9b05cdfc..3a0bb76ae6b 100644 --- a/dev/config/apollo-federation/supergraph.graphql +++ b/dev/config/apollo-federation/supergraph.graphql @@ -32,6 +32,7 @@ interface Account level: AccountLevel! limits: AccountLimits! notificationSettings: NotificationSettings! + pendingTransactions(walletIds: [WalletId]): [Transaction!]! realtimePrice: RealtimePrice! transactions( """Returns the items in the list that come after the specified cursor.""" @@ -183,6 +184,11 @@ type BTCWallet implements Wallet """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + pendingTransactions: [Transaction!]! + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! """A list of BTC transactions associated with this wallet.""" @@ -306,6 +312,7 @@ type ConsumerAccount implements Account level: AccountLevel! limits: AccountLimits! notificationSettings: NotificationSettings! + pendingTransactions(walletIds: [WalletId]): [Transaction!]! """List the quiz questions of the consumer account""" quiz: [Quiz!]! @@ -1617,6 +1624,11 @@ type UsdWallet implements Wallet """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + pendingTransactions: [Transaction!]! + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! transactions( """Returns the items in the list that come after the specified cursor.""" @@ -1922,6 +1934,25 @@ interface Wallet paymentHash: PaymentHash! ): Invoice! pendingIncomingBalance: SignedAmount! + + """ + Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first + """ + pendingTransactions: [Transaction!]! + + """ + Pending OnChain transactions. When transactions + are confirmed they will receive a new id and be found in the transactions + list. Transactions are ordered anti-chronologically, + ie: the newest transaction will be first + """ + pendingTransactionsByAddress( + """Returns the items that include this address.""" + address: OnChainAddress! + ): [Transaction!]! transactionById(transactionId: ID!): Transaction! """