diff --git a/apps/consent/app/graphql/generated.ts b/apps/consent/app/graphql/generated.ts index 108647cdb6..118722c4db 100644 --- a/apps/consent/app/graphql/generated.ts +++ b/apps/consent/app/graphql/generated.ts @@ -1580,6 +1580,11 @@ export type Query = { readonly payoutSpeeds: ReadonlyArray; /** Returns 1 Sat and 1 Usd Cent price for the given currency in minor unit */ readonly realtimePrice: RealtimePrice; + /** + * Get a StableSats quote for buying or selling USD. + * Returns a quote with pricing and expiration information. + */ + readonly stableSatsGetQuote: StableSatsQuotePayload; /** @deprecated will be migrated to AccountDefaultWalletId */ readonly userDefaultWalletId: Scalars['WalletId']['output']; readonly usernameAvailable?: Maybe; @@ -1647,6 +1652,11 @@ export type QueryRealtimePriceArgs = { }; +export type QueryStableSatsGetQuoteArgs = { + input: StableSatsGetQuoteInput; +}; + + export type QueryUserDefaultWalletIdArgs = { username: Scalars['Username']['input']; }; @@ -1676,6 +1686,14 @@ export type QuizClaimPayload = { readonly quizzes: ReadonlyArray; }; +export const QuoteType = { + BuyUsdWithCents: 'BUY_USD_WITH_CENTS', + BuyUsdWithSats: 'BUY_USD_WITH_SATS', + SellUsdForCents: 'SELL_USD_FOR_CENTS', + SellUsdForSats: 'SELL_USD_FOR_SATS' +} as const; + +export type QuoteType = typeof QuoteType[keyof typeof QuoteType]; export type RealtimePrice = { readonly __typename: 'RealtimePrice'; readonly btcSatPrice: PriceOfOneSatInMinorUnit; @@ -1735,6 +1753,41 @@ export type SettlementViaOnChain = { readonly vout?: Maybe; }; +export type StableSatsGetQuoteInput = { + /** Amount in cents (for cent-based quotes) */ + readonly centAmount?: InputMaybe; + /** Type of quote to request */ + readonly quoteType: QuoteType; + /** Amount in satoshis (for sat-based quotes) */ + readonly satAmount?: InputMaybe; + /** Wallet id of the requesting wallet */ + readonly walletId: Scalars['WalletId']['input']; +}; + +export type StableSatsQuote = { + readonly __typename: 'StableSatsQuote'; + /** Amount to buy in cents (for buy USD quotes) */ + readonly amountToBuyInCents?: Maybe; + /** Amount to buy in satoshis (for sell USD quotes) */ + readonly amountToBuyInSats?: Maybe; + /** Amount to sell in cents (for sell USD quotes) */ + readonly amountToSellInCents?: Maybe; + /** Amount to sell in satoshis (for buy USD quotes) */ + readonly amountToSellInSats?: Maybe; + /** Whether the quote has been executed */ + readonly executed: Scalars['Boolean']['output']; + /** Quote expiration timestamp */ + readonly expiresAt: Scalars['Int']['output']; + /** Unique identifier for the quote */ + readonly quoteId: Scalars['String']['output']; +}; + +export type StableSatsQuotePayload = { + readonly __typename: 'StableSatsQuotePayload'; + readonly errors: ReadonlyArray; + readonly quote?: Maybe; +}; + export type Subscription = { readonly __typename: 'Subscription'; /** @deprecated Deprecated in favor of lnInvoicePaymentStatusByPaymentRequest */ @@ -2346,4 +2399,4 @@ export function useGetUserIdSuspenseQuery(baseOptions?: Apollo.SkipToken | Apoll export type GetUserIdQueryHookResult = ReturnType; export type GetUserIdLazyQueryHookResult = ReturnType; export type GetUserIdSuspenseQueryHookResult = ReturnType; -export type GetUserIdQueryResult = Apollo.QueryResult; +export type GetUserIdQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/apps/dashboard/services/graphql/generated.ts b/apps/dashboard/services/graphql/generated.ts index 712af87310..6f104e415c 100644 --- a/apps/dashboard/services/graphql/generated.ts +++ b/apps/dashboard/services/graphql/generated.ts @@ -1705,6 +1705,11 @@ export type Query = { readonly payoutSpeeds: ReadonlyArray; /** Returns 1 Sat and 1 Usd Cent price for the given currency in minor unit */ readonly realtimePrice: RealtimePrice; + /** + * Get a StableSats quote for buying or selling USD. + * Returns a quote with pricing and expiration information. + */ + readonly stableSatsGetQuote: StableSatsQuotePayload; /** @deprecated will be migrated to AccountDefaultWalletId */ readonly userDefaultWalletId: Scalars['WalletId']['output']; readonly usernameAvailable?: Maybe; @@ -1772,6 +1777,11 @@ export type QueryRealtimePriceArgs = { }; +export type QueryStableSatsGetQuoteArgs = { + input: StableSatsGetQuoteInput; +}; + + export type QueryUserDefaultWalletIdArgs = { username: Scalars['Username']['input']; }; @@ -1801,6 +1811,14 @@ export type QuizClaimPayload = { readonly quizzes: ReadonlyArray; }; +export const QuoteType = { + BuyUsdWithCents: 'BUY_USD_WITH_CENTS', + BuyUsdWithSats: 'BUY_USD_WITH_SATS', + SellUsdForCents: 'SELL_USD_FOR_CENTS', + SellUsdForSats: 'SELL_USD_FOR_SATS' +} as const; + +export type QuoteType = typeof QuoteType[keyof typeof QuoteType]; export type RealtimePrice = { readonly __typename: 'RealtimePrice'; readonly btcSatPrice: PriceOfOneSatInMinorUnit; @@ -1860,6 +1878,41 @@ export type SettlementViaOnChain = { readonly vout?: Maybe; }; +export type StableSatsGetQuoteInput = { + /** Amount in cents (for cent-based quotes) */ + readonly centAmount?: InputMaybe; + /** Type of quote to request */ + readonly quoteType: QuoteType; + /** Amount in satoshis (for sat-based quotes) */ + readonly satAmount?: InputMaybe; + /** Wallet id of the requesting wallet */ + readonly walletId: Scalars['WalletId']['input']; +}; + +export type StableSatsQuote = { + readonly __typename: 'StableSatsQuote'; + /** Amount to buy in cents (for buy USD quotes) */ + readonly amountToBuyInCents?: Maybe; + /** Amount to buy in satoshis (for sell USD quotes) */ + readonly amountToBuyInSats?: Maybe; + /** Amount to sell in cents (for sell USD quotes) */ + readonly amountToSellInCents?: Maybe; + /** Amount to sell in satoshis (for buy USD quotes) */ + readonly amountToSellInSats?: Maybe; + /** Whether the quote has been executed */ + readonly executed: Scalars['Boolean']['output']; + /** Quote expiration timestamp */ + readonly expiresAt: Scalars['Int']['output']; + /** Unique identifier for the quote */ + readonly quoteId: Scalars['String']['output']; +}; + +export type StableSatsQuotePayload = { + readonly __typename: 'StableSatsQuotePayload'; + readonly errors: ReadonlyArray; + readonly quote?: Maybe; +}; + export type StatefulNotification = { readonly __typename: 'StatefulNotification'; readonly acknowledgedAt?: Maybe; @@ -3736,6 +3789,7 @@ export type ResolversTypes = { Quiz: ResolverTypeWrapper; QuizClaimInput: QuizClaimInput; QuizClaimPayload: ResolverTypeWrapper & { errors: ReadonlyArray }>; + QuoteType: QuoteType; RealtimePrice: ResolverTypeWrapper; RealtimePriceInput: RealtimePriceInput; RealtimePricePayload: ResolverTypeWrapper & { errors: ReadonlyArray }>; @@ -3750,6 +3804,9 @@ export type ResolversTypes = { SettlementViaOnChain: ResolverTypeWrapper; SignedAmount: ResolverTypeWrapper; SignedDisplayMajorAmount: ResolverTypeWrapper; + StableSatsGetQuoteInput: StableSatsGetQuoteInput; + StableSatsQuote: ResolverTypeWrapper; + StableSatsQuotePayload: ResolverTypeWrapper & { errors: ReadonlyArray }>; StatefulNotification: ResolverTypeWrapper & { action?: Maybe }>; StatefulNotificationAcknowledgeInput: StatefulNotificationAcknowledgeInput; StatefulNotificationAcknowledgePayload: ResolverTypeWrapper & { notification: ResolversTypes['StatefulNotification'] }>; @@ -3978,6 +4035,9 @@ export type ResolversParentTypes = { SettlementViaOnChain: SettlementViaOnChain; SignedAmount: Scalars['SignedAmount']['output']; SignedDisplayMajorAmount: Scalars['SignedDisplayMajorAmount']['output']; + StableSatsGetQuoteInput: StableSatsGetQuoteInput; + StableSatsQuote: StableSatsQuote; + StableSatsQuotePayload: Omit & { errors: ReadonlyArray }; StatefulNotification: Omit & { action?: Maybe }; StatefulNotificationAcknowledgeInput: StatefulNotificationAcknowledgeInput; StatefulNotificationAcknowledgePayload: Omit & { notification: ResolversParentTypes['StatefulNotification'] }; @@ -4823,6 +4883,7 @@ export type QueryResolvers>; payoutSpeeds?: Resolver, ParentType, ContextType>; realtimePrice?: Resolver>; + stableSatsGetQuote?: Resolver>; userDefaultWalletId?: Resolver>; usernameAvailable?: Resolver, ParentType, ContextType, RequireFields>; }; @@ -4907,6 +4968,23 @@ export interface SignedDisplayMajorAmountScalarConfig extends GraphQLScalarTypeC name: 'SignedDisplayMajorAmount'; } +export type StableSatsQuoteResolvers = { + amountToBuyInCents?: Resolver, ParentType, ContextType>; + amountToBuyInSats?: Resolver, ParentType, ContextType>; + amountToSellInCents?: Resolver, ParentType, ContextType>; + amountToSellInSats?: Resolver, ParentType, ContextType>; + executed?: Resolver; + expiresAt?: Resolver; + quoteId?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type StableSatsQuotePayloadResolvers = { + errors?: Resolver, ParentType, ContextType>; + quote?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type StatefulNotificationResolvers = { acknowledgedAt?: Resolver, ParentType, ContextType>; action?: Resolver, ParentType, ContextType>; @@ -5296,6 +5374,8 @@ export type Resolvers = { SettlementViaOnChain?: SettlementViaOnChainResolvers; SignedAmount?: GraphQLScalarType; SignedDisplayMajorAmount?: GraphQLScalarType; + StableSatsQuote?: StableSatsQuoteResolvers; + StableSatsQuotePayload?: StableSatsQuotePayloadResolvers; StatefulNotification?: StatefulNotificationResolvers; StatefulNotificationAcknowledgePayload?: StatefulNotificationAcknowledgePayloadResolvers; StatefulNotificationConnection?: StatefulNotificationConnectionResolvers; diff --git a/apps/map/services/galoy/graphql/generated.ts b/apps/map/services/galoy/graphql/generated.ts index 37fa8b5092..aded3bb9c3 100644 --- a/apps/map/services/galoy/graphql/generated.ts +++ b/apps/map/services/galoy/graphql/generated.ts @@ -1580,6 +1580,11 @@ export type Query = { readonly payoutSpeeds: ReadonlyArray; /** Returns 1 Sat and 1 Usd Cent price for the given currency in minor unit */ readonly realtimePrice: RealtimePrice; + /** + * Get a StableSats quote for buying or selling USD. + * Returns a quote with pricing and expiration information. + */ + readonly stableSatsGetQuote: StableSatsQuotePayload; /** @deprecated will be migrated to AccountDefaultWalletId */ readonly userDefaultWalletId: Scalars['WalletId']['output']; readonly usernameAvailable?: Maybe; @@ -1647,6 +1652,11 @@ export type QueryRealtimePriceArgs = { }; +export type QueryStableSatsGetQuoteArgs = { + input: StableSatsGetQuoteInput; +}; + + export type QueryUserDefaultWalletIdArgs = { username: Scalars['Username']['input']; }; @@ -1676,6 +1686,14 @@ export type QuizClaimPayload = { readonly quizzes: ReadonlyArray; }; +export const QuoteType = { + BuyUsdWithCents: 'BUY_USD_WITH_CENTS', + BuyUsdWithSats: 'BUY_USD_WITH_SATS', + SellUsdForCents: 'SELL_USD_FOR_CENTS', + SellUsdForSats: 'SELL_USD_FOR_SATS' +} as const; + +export type QuoteType = typeof QuoteType[keyof typeof QuoteType]; export type RealtimePrice = { readonly __typename: 'RealtimePrice'; readonly btcSatPrice: PriceOfOneSatInMinorUnit; @@ -1735,6 +1753,41 @@ export type SettlementViaOnChain = { readonly vout?: Maybe; }; +export type StableSatsGetQuoteInput = { + /** Amount in cents (for cent-based quotes) */ + readonly centAmount?: InputMaybe; + /** Type of quote to request */ + readonly quoteType: QuoteType; + /** Amount in satoshis (for sat-based quotes) */ + readonly satAmount?: InputMaybe; + /** Wallet id of the requesting wallet */ + readonly walletId: Scalars['WalletId']['input']; +}; + +export type StableSatsQuote = { + readonly __typename: 'StableSatsQuote'; + /** Amount to buy in cents (for buy USD quotes) */ + readonly amountToBuyInCents?: Maybe; + /** Amount to buy in satoshis (for sell USD quotes) */ + readonly amountToBuyInSats?: Maybe; + /** Amount to sell in cents (for sell USD quotes) */ + readonly amountToSellInCents?: Maybe; + /** Amount to sell in satoshis (for buy USD quotes) */ + readonly amountToSellInSats?: Maybe; + /** Whether the quote has been executed */ + readonly executed: Scalars['Boolean']['output']; + /** Quote expiration timestamp */ + readonly expiresAt: Scalars['Int']['output']; + /** Unique identifier for the quote */ + readonly quoteId: Scalars['String']['output']; +}; + +export type StableSatsQuotePayload = { + readonly __typename: 'StableSatsQuotePayload'; + readonly errors: ReadonlyArray; + readonly quote?: Maybe; +}; + export type Subscription = { readonly __typename: 'Subscription'; /** @deprecated Deprecated in favor of lnInvoicePaymentStatusByPaymentRequest */ @@ -2360,4 +2413,4 @@ export function useBusinessMapMarkersSuspenseQuery(baseOptions?: Apollo.SkipToke export type BusinessMapMarkersQueryHookResult = ReturnType; export type BusinessMapMarkersLazyQueryHookResult = ReturnType; export type BusinessMapMarkersSuspenseQueryHookResult = ReturnType; -export type BusinessMapMarkersQueryResult = Apollo.QueryResult; +export type BusinessMapMarkersQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/apps/pay/lib/graphql/generated.ts b/apps/pay/lib/graphql/generated.ts index 86f2e4fb98..ccfe5174b2 100644 --- a/apps/pay/lib/graphql/generated.ts +++ b/apps/pay/lib/graphql/generated.ts @@ -1581,6 +1581,11 @@ export type Query = { readonly payoutSpeeds: ReadonlyArray; /** Returns 1 Sat and 1 Usd Cent price for the given currency in minor unit */ readonly realtimePrice: RealtimePrice; + /** + * Get a StableSats quote for buying or selling USD. + * Returns a quote with pricing and expiration information. + */ + readonly stableSatsGetQuote: StableSatsQuotePayload; /** @deprecated will be migrated to AccountDefaultWalletId */ readonly userDefaultWalletId: Scalars['WalletId']['output']; readonly usernameAvailable?: Maybe; @@ -1648,6 +1653,11 @@ export type QueryRealtimePriceArgs = { }; +export type QueryStableSatsGetQuoteArgs = { + input: StableSatsGetQuoteInput; +}; + + export type QueryUserDefaultWalletIdArgs = { username: Scalars['Username']['input']; }; @@ -1677,6 +1687,14 @@ export type QuizClaimPayload = { readonly quizzes: ReadonlyArray; }; +export const QuoteType = { + BuyUsdWithCents: 'BUY_USD_WITH_CENTS', + BuyUsdWithSats: 'BUY_USD_WITH_SATS', + SellUsdForCents: 'SELL_USD_FOR_CENTS', + SellUsdForSats: 'SELL_USD_FOR_SATS' +} as const; + +export type QuoteType = typeof QuoteType[keyof typeof QuoteType]; export type RealtimePrice = { readonly __typename: 'RealtimePrice'; readonly btcSatPrice: PriceOfOneSatInMinorUnit; @@ -1736,6 +1754,41 @@ export type SettlementViaOnChain = { readonly vout?: Maybe; }; +export type StableSatsGetQuoteInput = { + /** Amount in cents (for cent-based quotes) */ + readonly centAmount?: InputMaybe; + /** Type of quote to request */ + readonly quoteType: QuoteType; + /** Amount in satoshis (for sat-based quotes) */ + readonly satAmount?: InputMaybe; + /** Wallet id of the requesting wallet */ + readonly walletId: Scalars['WalletId']['input']; +}; + +export type StableSatsQuote = { + readonly __typename: 'StableSatsQuote'; + /** Amount to buy in cents (for buy USD quotes) */ + readonly amountToBuyInCents?: Maybe; + /** Amount to buy in satoshis (for sell USD quotes) */ + readonly amountToBuyInSats?: Maybe; + /** Amount to sell in cents (for sell USD quotes) */ + readonly amountToSellInCents?: Maybe; + /** Amount to sell in satoshis (for buy USD quotes) */ + readonly amountToSellInSats?: Maybe; + /** Whether the quote has been executed */ + readonly executed: Scalars['Boolean']['output']; + /** Quote expiration timestamp */ + readonly expiresAt: Scalars['Int']['output']; + /** Unique identifier for the quote */ + readonly quoteId: Scalars['String']['output']; +}; + +export type StableSatsQuotePayload = { + readonly __typename: 'StableSatsQuotePayload'; + readonly errors: ReadonlyArray; + readonly quote?: Maybe; +}; + export type Subscription = { readonly __typename: 'Subscription'; /** @deprecated Deprecated in favor of lnInvoicePaymentStatusByPaymentRequest */ @@ -3016,4 +3069,4 @@ export function usePriceSubscription(baseOptions: Apollo.SubscriptionHookOptions return Apollo.useSubscription(PriceDocument, options); } export type PriceSubscriptionHookResult = ReturnType; -export type PriceSubscriptionResult = Apollo.SubscriptionResult; +export type PriceSubscriptionResult = Apollo.SubscriptionResult; \ No newline at end of file diff --git a/bats/core/api/stablesats.bats b/bats/core/api/stablesats.bats new file mode 100644 index 0000000000..98400c0c6b --- /dev/null +++ b/bats/core/api/stablesats.bats @@ -0,0 +1,260 @@ +#!/usr/bin/env bats + +load "../../helpers/_common.bash" +load "../../helpers/user.bash" + +setup_file() { + clear_cache + create_user 'alice' +} + +@test "stablesats: can get quote for BUY_USD_WITH_SATS" { + token_name='alice' + btc_wallet_name="$token_name.btc_wallet_id" + sat_amount="10000" + + variables=$( + jq -n \ + --arg wallet_id "$(read_value $btc_wallet_name)" \ + --arg quote_type "BUY_USD_WITH_SATS" \ + --arg sat_amount "$sat_amount" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type, satAmount: ($sat_amount | tonumber)}}' + ) + + exec_graphql "$token_name" 'stablesats-get-quote' "$variables" + + errors="$(graphql_output '.data.stableSatsGetQuote.errors | length')" + [[ "${errors}" = "0" ]] || exit 1 + + quote_id="$(graphql_output '.data.stableSatsGetQuote.quote.quoteId')" + amount_to_sell_sats="$(graphql_output '.data.stableSatsGetQuote.quote.amountToSellInSats')" + amount_to_buy_cents="$(graphql_output '.data.stableSatsGetQuote.quote.amountToBuyInCents')" + expires_at="$(graphql_output '.data.stableSatsGetQuote.quote.expiresAt')" + executed="$(graphql_output '.data.stableSatsGetQuote.quote.executed')" + + [[ -n "$quote_id" ]] || exit 1 + [[ "$amount_to_sell_sats" -gt 0 ]] || exit 1 + [[ "$amount_to_buy_cents" -gt 0 ]] || exit 1 + [[ "$expires_at" -gt 0 ]] || exit 1 + [[ "$executed" = "false" ]] || exit 1 +} + +@test "stablesats: can get quote for BUY_USD_WITH_CENTS" { + token_name='alice' + btc_wallet_name="$token_name.btc_wallet_id" + cent_amount="500" + + variables=$( + jq -n \ + --arg wallet_id "$(read_value $btc_wallet_name)" \ + --arg quote_type "BUY_USD_WITH_CENTS" \ + --arg cent_amount "$cent_amount" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type, centAmount: ($cent_amount | tonumber)}}' + ) + + exec_graphql "$token_name" 'stablesats-get-quote' "$variables" + + errors="$(graphql_output '.data.stableSatsGetQuote.errors | length')" + [[ "${errors}" = "0" ]] || exit 1 + + quote_id="$(graphql_output '.data.stableSatsGetQuote.quote.quoteId')" + amount_to_sell_sats="$(graphql_output '.data.stableSatsGetQuote.quote.amountToSellInSats')" + amount_to_buy_cents="$(graphql_output '.data.stableSatsGetQuote.quote.amountToBuyInCents')" + expires_at="$(graphql_output '.data.stableSatsGetQuote.quote.expiresAt')" + executed="$(graphql_output '.data.stableSatsGetQuote.quote.executed')" + + [[ -n "$quote_id" ]] || exit 1 + [[ "$amount_to_sell_sats" -gt 0 ]] || exit 1 + [[ "$amount_to_buy_cents" = "$cent_amount" ]] || exit 1 + [[ "$expires_at" -gt 0 ]] || exit 1 + [[ "$executed" = "false" ]] || exit 1 +} + +@test "stablesats: can get quote for SELL_USD_FOR_SATS" { + token_name='alice' + usd_wallet_name="$token_name.usd_wallet_id" + sat_amount="5000" + + variables=$( + jq -n \ + --arg wallet_id "$(read_value $usd_wallet_name)" \ + --arg quote_type "SELL_USD_FOR_SATS" \ + --arg sat_amount "$sat_amount" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type, satAmount: ($sat_amount | tonumber)}}' + ) + + exec_graphql "$token_name" 'stablesats-get-quote' "$variables" + + errors="$(graphql_output '.data.stableSatsGetQuote.errors | length')" + [[ "${errors}" = "0" ]] || exit 1 + + quote_id="$(graphql_output '.data.stableSatsGetQuote.quote.quoteId')" + amount_to_buy_sats="$(graphql_output '.data.stableSatsGetQuote.quote.amountToBuyInSats')" + amount_to_sell_cents="$(graphql_output '.data.stableSatsGetQuote.quote.amountToSellInCents')" + expires_at="$(graphql_output '.data.stableSatsGetQuote.quote.expiresAt')" + executed="$(graphql_output '.data.stableSatsGetQuote.quote.executed')" + + [[ -n "$quote_id" ]] || exit 1 + [[ "$amount_to_buy_sats" = "$sat_amount" ]] || exit 1 + [[ "$amount_to_sell_cents" -gt 0 ]] || exit 1 + [[ "$expires_at" -gt 0 ]] || exit 1 + [[ "$executed" = "false" ]] || exit 1 +} + +@test "stablesats: can get quote for SELL_USD_FOR_CENTS" { + token_name='alice' + usd_wallet_name="$token_name.usd_wallet_id" + cent_amount="250" + + variables=$( + jq -n \ + --arg wallet_id "$(read_value $usd_wallet_name)" \ + --arg quote_type "SELL_USD_FOR_CENTS" \ + --arg cent_amount "$cent_amount" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type, centAmount: ($cent_amount | tonumber)}}' + ) + + exec_graphql "$token_name" 'stablesats-get-quote' "$variables" + + errors="$(graphql_output '.data.stableSatsGetQuote.errors | length')" + [[ "${errors}" = "0" ]] || exit 1 + + quote_id="$(graphql_output '.data.stableSatsGetQuote.quote.quoteId')" + amount_to_buy_sats="$(graphql_output '.data.stableSatsGetQuote.quote.amountToBuyInSats')" + amount_to_sell_cents="$(graphql_output '.data.stableSatsGetQuote.quote.amountToSellInCents')" + expires_at="$(graphql_output '.data.stableSatsGetQuote.quote.expiresAt')" + executed="$(graphql_output '.data.stableSatsGetQuote.quote.executed')" + + [[ -n "$quote_id" ]] || exit 1 + [[ "$amount_to_buy_sats" -gt 0 ]] || exit 1 + [[ "$amount_to_sell_cents" = "$cent_amount" ]] || exit 1 + [[ "$expires_at" -gt 0 ]] || exit 1 + [[ "$executed" = "false" ]] || exit 1 +} + +@test "stablesats: returns error for missing satAmount with BUY_USD_WITH_SATS" { + token_name='alice' + btc_wallet_name="$token_name.btc_wallet_id" + + variables=$( + jq -n \ + --arg wallet_id "$(read_value $btc_wallet_name)" \ + --arg quote_type "BUY_USD_WITH_SATS" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type}}' + ) + + exec_graphql "$token_name" 'stablesats-get-quote' "$variables" + + errors="$(graphql_output '.data.stableSatsGetQuote.errors | length')" + [[ "${errors}" -gt "0" ]] || exit 1 + + error_message="$(graphql_output '.data.stableSatsGetQuote.errors[0].message')" + [[ "$error_message" = "satAmount is required for BUY_USD_WITH_SATS" ]] || exit 1 +} + +@test "stablesats: returns error for missing centAmount with BUY_USD_WITH_CENTS" { + token_name='alice' + btc_wallet_name="$token_name.btc_wallet_id" + + variables=$( + jq -n \ + --arg wallet_id "$(read_value $btc_wallet_name)" \ + --arg quote_type "BUY_USD_WITH_CENTS" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type}}' + ) + + exec_graphql "$token_name" 'stablesats-get-quote' "$variables" + + errors="$(graphql_output '.data.stableSatsGetQuote.errors | length')" + [[ "${errors}" -gt "0" ]] || exit 1 + + error_message="$(graphql_output '.data.stableSatsGetQuote.errors[0].message')" + [[ "$error_message" = "centAmount is required for BUY_USD_WITH_CENTS" ]] || exit 1 +} + +@test "stablesats: returns error for invalid quote type" { + token_name='alice' + btc_wallet_name="$token_name.btc_wallet_id" + + variables=$( + jq -n \ + --arg wallet_id "$(read_value $btc_wallet_name)" \ + --arg quote_type "INVALID_QUOTE_TYPE" \ + --arg sat_amount "1000" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type, satAmount: ($sat_amount | tonumber)}}' + ) + + exec_graphql "$token_name" 'stablesats-get-quote' "$variables" + + # GraphQL enum validation error occurs at the schema level + errors="$(graphql_output '.errors | length')" + [[ "${errors}" -gt "0" ]] || exit 1 + + # The enum validation error is typically in the second error message + error_message="$(graphql_output '.errors[1].message')" + [[ "$error_message" =~ "does not exist in \"QuoteType\" enum" ]] || exit 1 +} + +@test "stablesats: requires authentication" { + # Create a random wallet ID to use for unauthenticated request + fake_wallet_id="70df854e-bb34-4c4b-91b5-1e4fdcca27fd" + + variables=$( + jq -n \ + --arg wallet_id "$fake_wallet_id" \ + --arg quote_type "BUY_USD_WITH_SATS" \ + --arg sat_amount "1000" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type, satAmount: ($sat_amount | tonumber)}}' + ) + + exec_graphql 'anon' 'stablesats-get-quote' "$variables" + + # Should get an authentication error + errors="$(graphql_output '.errors | length')" + [[ "${errors}" -gt "0" ]] || exit 1 + + # Check for authentication-related error message + error_message="$(graphql_output '.errors[0].message')" + [[ "$error_message" =~ (Not authorized|Unauthorized|Authentication|Authorization|access) ]] || exit 1 +} + +@test "stablesats: returns error for missing required parameters in SELL quotes" { + token_name='alice' + usd_wallet_name="$token_name.usd_wallet_id" + + # Test SELL_USD_FOR_SATS without satAmount + variables=$( + jq -n \ + --arg wallet_id "$(read_value $usd_wallet_name)" \ + --arg quote_type "SELL_USD_FOR_SATS" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type}}' + ) + + exec_graphql "$token_name" 'stablesats-get-quote' "$variables" + + errors="$(graphql_output '.data.stableSatsGetQuote.errors | length')" + [[ "${errors}" -gt "0" ]] || exit 1 + + error_message="$(graphql_output '.data.stableSatsGetQuote.errors[0].message')" + [[ "$error_message" = "satAmount is required for SELL_USD_FOR_SATS" ]] || exit 1 +} + +@test "stablesats: returns error for missing centAmount with SELL_USD_FOR_CENTS" { + token_name='alice' + usd_wallet_name="$token_name.usd_wallet_id" + + variables=$( + jq -n \ + --arg wallet_id "$(read_value $usd_wallet_name)" \ + --arg quote_type "SELL_USD_FOR_CENTS" \ + '{input: {walletId: $wallet_id, quoteType: $quote_type}}' + ) + + exec_graphql "$token_name" 'stablesats-get-quote' "$variables" + + errors="$(graphql_output '.data.stableSatsGetQuote.errors | length')" + [[ "${errors}" -gt "0" ]] || exit 1 + + error_message="$(graphql_output '.data.stableSatsGetQuote.errors[0].message')" + [[ "$error_message" = "centAmount is required for SELL_USD_FOR_CENTS" ]] || exit 1 +} diff --git a/bats/gql/stablesats-get-quote.gql b/bats/gql/stablesats-get-quote.gql new file mode 100644 index 0000000000..158aff71e8 --- /dev/null +++ b/bats/gql/stablesats-get-quote.gql @@ -0,0 +1,16 @@ +query StableSatsGetQuote($input: StableSatsGetQuoteInput!) { + stableSatsGetQuote(input: $input) { + errors { + message + } + quote { + quoteId + amountToSellInSats + amountToBuyInCents + amountToBuyInSats + amountToSellInCents + expiresAt + executed + } + } +} diff --git a/core/api/src/app/errors.ts b/core/api/src/app/errors.ts index 9eac8c0bf2..b402591b2a 100644 --- a/core/api/src/app/errors.ts +++ b/core/api/src/app/errors.ts @@ -17,6 +17,7 @@ import * as NotificationsErrors from "@/domain/notifications/errors" import * as CacheErrors from "@/domain/cache/errors" import * as PhoneProviderServiceErrors from "@/domain/phone-provider/errors" import * as DealerPriceErrors from "@/domain/dealer-price/errors" +import * as QuotesErrors from "@/domain/quotes/errors" import * as PubSubErrors from "@/domain/pubsub/errors" import * as CaptchaErrors from "@/domain/captcha/errors" import * as AuthenticationErrors from "@/domain/authentication/errors" @@ -42,6 +43,7 @@ export const ApplicationErrors = { ...LightningErrors, ...LnurlServiceErrors, ...PriceServiceErrors, + ...QuotesErrors, ...LockServiceErrors, ...RateLimitServiceErrors, ...IpFetcherErrors, diff --git a/core/api/src/app/prices/index.ts b/core/api/src/app/prices/index.ts index b37c6056e1..59cd7ce1fa 100644 --- a/core/api/src/app/prices/index.ts +++ b/core/api/src/app/prices/index.ts @@ -4,3 +4,6 @@ export * from "./get-current-price" export * from "./get-price-history" export * from "./list-currencies" export * from "./mid-price" +export * from "./stablesats-accept-quote" +export * from "./stablesats-get-quote-to-buy-usd" +export * from "./stablesats-get-quote-to-sell-usd" diff --git a/core/api/src/app/prices/stablesats-accept-quote.ts b/core/api/src/app/prices/stablesats-accept-quote.ts new file mode 100644 index 0000000000..d63774e528 --- /dev/null +++ b/core/api/src/app/prices/stablesats-accept-quote.ts @@ -0,0 +1,15 @@ +import { checkedToQuoteId } from "@/domain/quotes" +import { QuotesService } from "@/services/quotes" + +export const stableSatsAcceptQuote = async ({ + quoteId, +}: { + quoteId: string +}): Promise => { + const checkedQuoteId = checkedToQuoteId(quoteId) + + if (checkedQuoteId instanceof Error) return checkedQuoteId + + const quotesService = QuotesService() + return quotesService.acceptQuote(checkedQuoteId) +} diff --git a/core/api/src/app/prices/stablesats-get-quote-to-buy-usd.ts b/core/api/src/app/prices/stablesats-get-quote-to-buy-usd.ts new file mode 100644 index 0000000000..bf3ed5358d --- /dev/null +++ b/core/api/src/app/prices/stablesats-get-quote-to-buy-usd.ts @@ -0,0 +1,37 @@ +import { checkedToBtcPaymentAmount, checkedToUsdPaymentAmount } from "@/domain/shared" +import { QuotesService } from "@/services/quotes" +import type { QuoteToBuyUsd } from "@/domain/quotes/index.types" + +export const stableSatsGetQuoteToBuyUsdWithSats = async ({ + btcAmount, + immediateExecution = false, +}: { + btcAmount: number + immediateExecution?: boolean +}): Promise => { + const validatedBtcAmount = checkedToBtcPaymentAmount(btcAmount) + if (validatedBtcAmount instanceof Error) return validatedBtcAmount + + const quotesService = QuotesService() + return quotesService.getQuoteToBuyUsdWithSats({ + btcAmount: validatedBtcAmount, + immediateExecution, + }) +} + +export const stableSatsGetQuoteToBuyUsdWithCents = async ({ + usdAmount, + immediateExecution = false, +}: { + usdAmount: number + immediateExecution?: boolean +}): Promise => { + const validatedUsdAmount = checkedToUsdPaymentAmount(usdAmount) + if (validatedUsdAmount instanceof Error) return validatedUsdAmount + + const quotesService = QuotesService() + return quotesService.getQuoteToBuyUsdWithCents({ + usdAmount: validatedUsdAmount, + immediateExecution, + }) +} diff --git a/core/api/src/app/prices/stablesats-get-quote-to-sell-usd.ts b/core/api/src/app/prices/stablesats-get-quote-to-sell-usd.ts new file mode 100644 index 0000000000..44e05c0aa7 --- /dev/null +++ b/core/api/src/app/prices/stablesats-get-quote-to-sell-usd.ts @@ -0,0 +1,37 @@ +import { checkedToBtcPaymentAmount, checkedToUsdPaymentAmount } from "@/domain/shared" +import { QuotesService } from "@/services/quotes" +import type { QuoteToSellUsd } from "@/domain/quotes/index.types" + +export const stableSatsGetQuoteToSellUsdWithSats = async ({ + btcAmount, + immediateExecution = false, +}: { + btcAmount: number + immediateExecution?: boolean +}): Promise => { + const validatedBtcAmount = checkedToBtcPaymentAmount(btcAmount) + if (validatedBtcAmount instanceof Error) return validatedBtcAmount + + const quotesService = QuotesService() + return quotesService.getQuoteToSellUsdWithSats({ + btcAmount: validatedBtcAmount, + immediateExecution, + }) +} + +export const stableSatsGetQuoteToSellUsdWithCents = async ({ + usdAmount, + immediateExecution = false, +}: { + usdAmount: number + immediateExecution?: boolean +}): Promise => { + const validatedUsdAmount = checkedToUsdPaymentAmount(usdAmount) + if (validatedUsdAmount instanceof Error) return validatedUsdAmount + + const quotesService = QuotesService() + return quotesService.getQuoteToSellUsdWithCents({ + usdAmount: validatedUsdAmount, + immediateExecution, + }) +} diff --git a/core/api/src/config/env.ts b/core/api/src/config/env.ts index 18f66ee917..f62332737d 100644 --- a/core/api/src/config/env.ts +++ b/core/api/src/config/env.ts @@ -39,6 +39,8 @@ export const env = createEnv({ PRICE_SERVER_PORT: z.number().or(z.string()).pipe(z.coerce.number()).default(3325), PRICE_SERVER_HOST: z.string().default("localhost"), + QUOTE_SERVER_PORT: z.number().or(z.string()).pipe(z.coerce.number()).default(3326), + QUOTE_SERVER_HOST: z.string().default("localhost"), TWILIO_ACCOUNT_SID: z.string().min(1), TWILIO_AUTH_TOKEN: z.string().min(1), @@ -176,6 +178,9 @@ export const env = createEnv({ PRICE_SERVER_PORT: process.env.PRICE_SERVER_PORT, PRICE_SERVER_HOST: process.env.PRICE_SERVER_HOST, + QUOTE_SERVER_PORT: process.env.QUOTE_SERVER_PORT, + QUOTE_SERVER_HOST: process.env.QUOTE_SERVER_HOST, + TWILIO_ACCOUNT_SID: process.env.TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN: process.env.TWILIO_AUTH_TOKEN, TWILIO_VERIFY_SERVICE_ID: process.env.TWILIO_VERIFY_SERVICE_ID, diff --git a/core/api/src/config/index.ts b/core/api/src/config/index.ts index 9c54a7b838..56511deada 100644 --- a/core/api/src/config/index.ts +++ b/core/api/src/config/index.ts @@ -155,6 +155,8 @@ export const GALOY_ADMIN_PORT = env.GALOY_ADMIN_PORT export const NETWORK = env.NETWORK as BtcNetwork export const PRICE_SERVER_PORT = env.PRICE_SERVER_PORT export const PRICE_SERVER_HOST = env.PRICE_SERVER_HOST +export const QUOTE_SERVER_PORT = env.QUOTE_SERVER_PORT +export const QUOTE_SERVER_HOST = env.QUOTE_SERVER_HOST export const TWILIO_ACCOUNT_SID = env.TWILIO_ACCOUNT_SID export const TWILIO_AUTH_TOKEN = env.TWILIO_AUTH_TOKEN export const TWILIO_VERIFY_SERVICE_ID = env.TWILIO_VERIFY_SERVICE_ID diff --git a/core/api/src/domain/errors.ts b/core/api/src/domain/errors.ts index bd1307394e..335fa24953 100644 --- a/core/api/src/domain/errors.ts +++ b/core/api/src/domain/errors.ts @@ -111,6 +111,7 @@ export class InvalidTotpCode extends ValidationError {} export class InvalidLedgerTransactionId extends ValidationError {} export class InvalidLedgerTransactionStateError extends ValidationError {} export class InvalidDisplayCurrencyError extends ValidationError {} +export class InvalidQuoteIdError extends ValidationError {} export class AlreadyPaidError extends ValidationError {} export class SelfPaymentError extends ValidationError {} export class LessThanDustThresholdError extends ValidationError {} diff --git a/core/api/src/domain/quotes/errors.ts b/core/api/src/domain/quotes/errors.ts new file mode 100644 index 0000000000..86e0c66847 --- /dev/null +++ b/core/api/src/domain/quotes/errors.ts @@ -0,0 +1,32 @@ +import { DomainError, ErrorLevel } from "@/domain/shared" + +export class QuotesError extends DomainError {} + +export class QuotesServiceError extends QuotesError {} +export class QuotesNotAvailableError extends QuotesServiceError {} +export class NoConnectionToQuotesError extends QuotesServiceError { + level = ErrorLevel.Critical +} + +export class QuotesAlreadyAcceptedError extends QuotesServiceError {} +export class QuotesExpiredError extends QuotesServiceError {} +export class QuotesCouldNotParseIdError extends QuotesServiceError {} + +export class QuotesExchangePriceError extends QuotesServiceError { + level = ErrorLevel.Critical +} + +export class QuotesEntityError extends QuotesServiceError { + level = ErrorLevel.Critical +} + +export class QuotesLedgerError extends QuotesServiceError { + level = ErrorLevel.Critical +} + +export class QuotesServerError extends QuotesServiceError { + level = ErrorLevel.Critical +} +export class UnknownQuotesServiceError extends QuotesServiceError { + level = ErrorLevel.Critical +} diff --git a/core/api/src/domain/quotes/index.ts b/core/api/src/domain/quotes/index.ts new file mode 100644 index 0000000000..ae4e09eea4 --- /dev/null +++ b/core/api/src/domain/quotes/index.ts @@ -0,0 +1,12 @@ +import { InvalidQuoteIdError } from "../errors" + +import { QuoteId } from "./index.types" + +export * from "./errors" + +export const checkedToQuoteId = (quoteId?: string): QuoteId | ValidationError => { + if (!quoteId || typeof quoteId !== "string") { + return new InvalidQuoteIdError(quoteId) + } + return quoteId as QuoteId +} diff --git a/core/api/src/domain/quotes/index.types.d.ts b/core/api/src/domain/quotes/index.types.d.ts new file mode 100644 index 0000000000..1d74bc5fd4 --- /dev/null +++ b/core/api/src/domain/quotes/index.types.d.ts @@ -0,0 +1,54 @@ +type QuotesServiceError = import("./errors").QuotesServiceError + +type QuoteId = string & { readonly brand: unique symbol } + +export interface IQuotesService { + getQuoteToBuyUsdWithSats({ + btcAmount, + immediateExecution, + }: { + btcAmount: BtcPaymentAmount + immediateExecution?: boolean + }): Promise + getQuoteToBuyUsdWithCents({ + usdAmount, + immediateExecution, + }: { + usdAmount: UsdPaymentAmount + immediateExecution?: boolean + }): Promise + + getQuoteToSellUsdWithSats({ + btcAmount, + immediateExecution, + }: { + btcAmount: BtcPaymentAmount + immediateExecution?: boolean + }): Promise + + getQuoteToSellUsdWithCents({ + usdAmount, + immediateExecution, + }: { + usdAmount: UsdPaymentAmount + immediateExecution?: boolean + }): Promise + + acceptQuote(quoteId: QuoteId): Promise +} + +export type QuoteToBuyUsd = { + quoteId: QuoteId + amountToSellInSats: number + amountToBuyInCents: number + expiresAt: number + executed: boolean +} + +export type QuoteToSellUsd = { + quoteId: QuoteId + amountToBuyInSats: number + amountToSellInCents: number + expiresAt: number + executed: boolean +} diff --git a/core/api/src/graphql/error-map.ts b/core/api/src/graphql/error-map.ts index 6c9efdd21b..c86d742863 100644 --- a/core/api/src/graphql/error-map.ts +++ b/core/api/src/graphql/error-map.ts @@ -38,6 +38,8 @@ import { LnurlRequestInvoiceError, QuizClaimedTooEarlyError, PriceServiceOfflineError, + QuotesError, + QuotesOfflineError, OperationRestrictedError, AuthorizationError, } from "@/graphql/error" @@ -387,6 +389,17 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => { message = "Stale dealer price, can't perform USD operation." return new DealerOfflineError({ message, logger: baseLogger }) + case "QuotesExchangePriceError": + case "QuotesEntityError": + case "QuotesLedgerError": + case "QuotesServerError": + message = error.message || "Quotes service internal error." + return new QuotesError({ message, logger: baseLogger }) + + case "NoConnectionToQuotesError": + message = "No connection to quotes to perform USD operation." + return new QuotesOfflineError({ message, logger: baseLogger }) + case "RouteNotFoundError": case "MaxFeeTooLargeForRoutelessPaymentError": message = "Unable to find a route for payment." @@ -434,6 +447,10 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => { message = "Invalid currency." return new ValidationInternalError({ message, logger: baseLogger }) + case "InvalidQuoteIdError": + message = "Invalid quote id." + return new ValidationInternalError({ message, logger: baseLogger }) + case "MismatchedCurrencyForWalletError": message = "Unsupported operation for wallet's currency." return new ValidationInternalError({ message, logger: baseLogger }) @@ -736,6 +753,12 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => { case "NotReachableError": case "DealerPriceError": case "DealerPriceServiceError": + case "QuotesError": + case "QuotesServiceError": + case "QuotesNotAvailableError": + case "QuotesAlreadyAcceptedError": + case "QuotesExpiredError": + case "QuotesCouldNotParseIdError": case "InvalidNegativeAmountError": case "DomainError": case "ErrorLevel": @@ -877,6 +900,7 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => { case "UnknownCacheServiceError": case "UnknownPhoneProviderServiceError": case "UnknownDealerPriceServiceError": + case "UnknownQuotesServiceError": case "UnknownPubSubError": case "UnknownBigIntConversionError": case "UnknownDomainError": diff --git a/core/api/src/graphql/error.ts b/core/api/src/graphql/error.ts index 2d85a71b88..67336fa17d 100644 --- a/core/api/src/graphql/error.ts +++ b/core/api/src/graphql/error.ts @@ -206,6 +206,28 @@ export class DealerOfflineError extends CustomGraphQLError { } } +export class QuotesError extends CustomGraphQLError { + constructor(errData: PartialBy) { + super({ + code: "QUOTES_ERROR", + forwardToClient: true, + ...errData, + logger: baseLogger, + }) + } +} + +export class QuotesOfflineError extends CustomGraphQLError { + constructor(errData: PartialBy) { + super({ + code: "QUOTES_OFFLINE", + forwardToClient: true, + ...errData, + logger: baseLogger, + }) + } +} + export class PriceServiceOfflineError extends CustomGraphQLError { constructor(errData: PartialBy) { super({ diff --git a/core/api/src/graphql/public/queries.ts b/core/api/src/graphql/public/queries.ts index f773d635df..ec76b3a7fd 100644 --- a/core/api/src/graphql/public/queries.ts +++ b/core/api/src/graphql/public/queries.ts @@ -19,6 +19,7 @@ import LnInvoicePaymentStatusQuery from "@/graphql/public/root/query/ln-invoice- import LnInvoicePaymentStatusByHashQuery from "@/graphql/public/root/query/ln-invoice-payment-status-by-hash" import LnInvoicePaymentStatusByPaymentRequestQuery from "@/graphql/public/root/query/ln-invoice-payment-status-by-payment-request" import CurrencyConversionEstimationQuery from "@/graphql/public/root/query/currency-conversion-estimation" +import StableSatsGetQuoteQuery from "@/graphql/public/root/query/stablesats-get-quote" export const queryFields = { unauthed: { @@ -46,6 +47,7 @@ export const queryFields = { onChainTxFee: OnChainTxFeeQuery, onChainUsdTxFee: OnChainUsdTxFeeQuery, onChainUsdTxFeeAsBtcDenominated: OnChainUsdTxFeeAsBtcDenominatedQuery, + stableSatsGetQuote: StableSatsGetQuoteQuery, }, }, } as const diff --git a/core/api/src/graphql/public/root/query/stablesats-get-quote.ts b/core/api/src/graphql/public/root/query/stablesats-get-quote.ts new file mode 100644 index 0000000000..f8d840a1e6 --- /dev/null +++ b/core/api/src/graphql/public/root/query/stablesats-get-quote.ts @@ -0,0 +1,100 @@ +import dedent from "dedent" + +import StableSatsGetQuoteInput from "@/graphql/public/types/object/stablesats-get-quote-input" + +import { + stableSatsGetQuoteToBuyUsdWithSats, + stableSatsGetQuoteToBuyUsdWithCents, +} from "@/app/prices/stablesats-get-quote-to-buy-usd" +import { + stableSatsGetQuoteToSellUsdWithSats, + stableSatsGetQuoteToSellUsdWithCents, +} from "@/app/prices/stablesats-get-quote-to-sell-usd" + +import { GT } from "@/graphql/index" +import StableSatsQuotePayload from "@/graphql/public/types/payload/stablesats-quote" +import { mapAndParseErrorForGqlResponse } from "@/graphql/error-map" + +const StableSatsGetQuoteQuery = GT.Field({ + extensions: { + complexity: 120, + }, + type: GT.NonNull(StableSatsQuotePayload), + description: dedent`Get a StableSats quote for buying or selling USD. + Returns a quote with pricing and expiration information.`, + args: { + input: { type: GT.NonNull(StableSatsGetQuoteInput) }, + }, + resolve: async (_, args) => { + const { quoteType, satAmount, centAmount } = args.input + + // Validate input parameters + for (const input of [quoteType, satAmount, centAmount]) { + if (input instanceof Error) { + return { errors: [{ message: input.message }] } + } + } + + let result + + switch (quoteType) { + case "BUY_USD_WITH_SATS": + if (!satAmount) { + return { errors: [{ message: "satAmount is required for BUY_USD_WITH_SATS" }] } + } + result = await stableSatsGetQuoteToBuyUsdWithSats({ + btcAmount: satAmount, + immediateExecution: false, + }) + break + + case "BUY_USD_WITH_CENTS": + if (!centAmount) { + return { + errors: [{ message: "centAmount is required for BUY_USD_WITH_CENTS" }], + } + } + result = await stableSatsGetQuoteToBuyUsdWithCents({ + usdAmount: centAmount, + immediateExecution: false, + }) + break + + case "SELL_USD_FOR_SATS": + if (!satAmount) { + return { errors: [{ message: "satAmount is required for SELL_USD_FOR_SATS" }] } + } + result = await stableSatsGetQuoteToSellUsdWithSats({ + btcAmount: satAmount, + immediateExecution: false, + }) + break + + case "SELL_USD_FOR_CENTS": + if (!centAmount) { + return { + errors: [{ message: "centAmount is required for SELL_USD_FOR_CENTS" }], + } + } + result = await stableSatsGetQuoteToSellUsdWithCents({ + usdAmount: centAmount, + immediateExecution: false, + }) + break + + default: + return { errors: [{ message: "Invalid quote type" }] } + } + + if (result instanceof Error) { + return { errors: [mapAndParseErrorForGqlResponse(result)] } + } + + return { + errors: [], + quote: result, + } + }, +}) + +export default StableSatsGetQuoteQuery diff --git a/core/api/src/graphql/public/schema.graphql b/core/api/src/graphql/public/schema.graphql index a9418e8ec1..403ae97be3 100644 --- a/core/api/src/graphql/public/schema.graphql +++ b/core/api/src/graphql/public/schema.graphql @@ -1341,6 +1341,12 @@ type Query { Returns 1 Sat and 1 Usd Cent price for the given currency in minor unit """ realtimePrice(currency: DisplayCurrency = "USD"): RealtimePrice! + + """ + Get a StableSats quote for buying or selling USD. + Returns a quote with pricing and expiration information. + """ + stableSatsGetQuote(input: StableSatsGetQuoteInput!): StableSatsQuotePayload! userDefaultWalletId(username: Username!): WalletId! @deprecated(reason: "will be migrated to AccountDefaultWalletId") usernameAvailable(username: Username!): Boolean } @@ -1363,6 +1369,13 @@ type QuizClaimPayload { quizzes: [Quiz!]! } +enum QuoteType { + BUY_USD_WITH_CENTS + BUY_USD_WITH_SATS + SELL_USD_FOR_CENTS + SELL_USD_FOR_SATS +} + type RealtimePrice { btcSatPrice: PriceOfOneSatInMinorUnit! denominatorCurrency: DisplayCurrency! @deprecated(reason: "Deprecated in favor of denominatorCurrencyDetails") @@ -1437,6 +1450,48 @@ A string amount (of a currency) that can be negative (e.g. in a transaction) """ scalar SignedDisplayMajorAmount +input StableSatsGetQuoteInput { + """Amount in cents (for cent-based quotes)""" + centAmount: CentAmount + + """Type of quote to request""" + quoteType: QuoteType! + + """Amount in satoshis (for sat-based quotes)""" + satAmount: SatAmount + + """Wallet id of the requesting wallet""" + walletId: WalletId! +} + +type StableSatsQuote { + """Amount to buy in cents (for buy USD quotes)""" + amountToBuyInCents: Int + + """Amount to buy in satoshis (for sell USD quotes)""" + amountToBuyInSats: Int + + """Amount to sell in cents (for sell USD quotes)""" + amountToSellInCents: Int + + """Amount to sell in satoshis (for buy USD quotes)""" + amountToSellInSats: Int + + """Whether the quote has been executed""" + executed: Boolean! + + """Quote expiration timestamp""" + expiresAt: Int! + + """Unique identifier for the quote""" + quoteId: String! +} + +type StableSatsQuotePayload { + errors: [Error!]! + quote: StableSatsQuote +} + type Subscription { lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! @deprecated(reason: "Deprecated in favor of lnInvoicePaymentStatusByPaymentRequest") lnInvoicePaymentStatusByHash(input: LnInvoicePaymentStatusByHashInput!): LnInvoicePaymentStatusPayload! diff --git a/core/api/src/graphql/public/types/object/stablesats-get-quote-input.ts b/core/api/src/graphql/public/types/object/stablesats-get-quote-input.ts new file mode 100644 index 0000000000..c72e729e8b --- /dev/null +++ b/core/api/src/graphql/public/types/object/stablesats-get-quote-input.ts @@ -0,0 +1,30 @@ +import QuoteType from "@/graphql/public/types/scalar/quote-type" +import CentAmount from "@/graphql/public/types/scalar/cent-amount" +import SatAmount from "@/graphql/shared/types/scalar/sat-amount" +import WalletId from "@/graphql/shared/types/scalar/wallet-id" + +import { GT } from "@/graphql" + +const StableSatsGetQuoteInput = GT.Input({ + name: "StableSatsGetQuoteInput", + fields: () => ({ + walletId: { + type: GT.NonNull(WalletId), + description: "Wallet id of the requesting wallet", + }, + quoteType: { + type: GT.NonNull(QuoteType), + description: "Type of quote to request", + }, + satAmount: { + type: SatAmount, + description: "Amount in satoshis (for sat-based quotes)", + }, + centAmount: { + type: CentAmount, + description: "Amount in cents (for cent-based quotes)", + }, + }), +}) + +export default StableSatsGetQuoteInput diff --git a/core/api/src/graphql/public/types/object/stablesats-quote.ts b/core/api/src/graphql/public/types/object/stablesats-quote.ts new file mode 100644 index 0000000000..fc8e6ae2db --- /dev/null +++ b/core/api/src/graphql/public/types/object/stablesats-quote.ts @@ -0,0 +1,37 @@ +import { GT } from "@/graphql" + +const StableSatsQuote = GT.Object({ + name: "StableSatsQuote", + fields: () => ({ + quoteId: { + type: GT.NonNull(GT.String), + description: "Unique identifier for the quote", + }, + amountToSellInSats: { + type: GT.Int, + description: "Amount to sell in satoshis (for buy USD quotes)", + }, + amountToBuyInCents: { + type: GT.Int, + description: "Amount to buy in cents (for buy USD quotes)", + }, + amountToBuyInSats: { + type: GT.Int, + description: "Amount to buy in satoshis (for sell USD quotes)", + }, + amountToSellInCents: { + type: GT.Int, + description: "Amount to sell in cents (for sell USD quotes)", + }, + expiresAt: { + type: GT.NonNull(GT.Int), + description: "Quote expiration timestamp", + }, + executed: { + type: GT.NonNull(GT.Boolean), + description: "Whether the quote has been executed", + }, + }), +}) + +export default StableSatsQuote diff --git a/core/api/src/graphql/public/types/payload/stablesats-quote.ts b/core/api/src/graphql/public/types/payload/stablesats-quote.ts new file mode 100644 index 0000000000..7c970b9ed7 --- /dev/null +++ b/core/api/src/graphql/public/types/payload/stablesats-quote.ts @@ -0,0 +1,17 @@ +import IError from "@/graphql/shared/types/abstract/error" +import { GT } from "@/graphql/index" +import StableSatsQuote from "@/graphql/public/types/object/stablesats-quote" + +const StableSatsQuotePayload = GT.Object({ + name: "StableSatsQuotePayload", + fields: () => ({ + errors: { + type: GT.NonNullList(IError), + }, + quote: { + type: StableSatsQuote, + }, + }), +}) + +export default StableSatsQuotePayload diff --git a/core/api/src/graphql/public/types/scalar/quote-type.ts b/core/api/src/graphql/public/types/scalar/quote-type.ts new file mode 100644 index 0000000000..7c96834403 --- /dev/null +++ b/core/api/src/graphql/public/types/scalar/quote-type.ts @@ -0,0 +1,13 @@ +import { GT } from "@/graphql/index" + +const QuoteType = GT.Enum({ + name: "QuoteType", + values: { + BUY_USD_WITH_SATS: { value: "BUY_USD_WITH_SATS" }, + BUY_USD_WITH_CENTS: { value: "BUY_USD_WITH_CENTS" }, + SELL_USD_FOR_SATS: { value: "SELL_USD_FOR_SATS" }, + SELL_USD_FOR_CENTS: { value: "SELL_USD_FOR_CENTS" }, + }, +}) + +export default QuoteType diff --git a/core/api/src/services/quotes/helpers.ts b/core/api/src/services/quotes/helpers.ts new file mode 100644 index 0000000000..e4e21855d1 --- /dev/null +++ b/core/api/src/services/quotes/helpers.ts @@ -0,0 +1,34 @@ +import { + GetQuoteToBuyUsdResponse, + GetQuoteToSellUsdResponse, +} from "./proto/services/quotes/v1/quote_service_pb" + +import { QuoteToBuyUsd, QuoteToSellUsd } from "@/domain/quotes/index.types" + +export const convertGetQuoteToBuyUsdResponse = ( + response: GetQuoteToBuyUsdResponse, +): QuoteToBuyUsd => { + const responseObj = response.toObject() + + return { + quoteId: responseObj.quoteId as QuoteToBuyUsd["quoteId"], + amountToSellInSats: responseObj.amountToSellInSats, + amountToBuyInCents: responseObj.amountToBuyInCents, + expiresAt: responseObj.expiresAt, + executed: responseObj.executed, + } +} + +export const convertGetQuoteToSellUsdResponse = ( + response: GetQuoteToSellUsdResponse, +): QuoteToSellUsd => { + const responseObj = response.toObject() + + return { + quoteId: responseObj.quoteId as QuoteToSellUsd["quoteId"], + amountToBuyInSats: responseObj.amountToBuyInSats, + amountToSellInCents: responseObj.amountToSellInCents, + expiresAt: responseObj.expiresAt, + executed: responseObj.executed, + } +} diff --git a/core/api/src/services/quotes/index.ts b/core/api/src/services/quotes/index.ts new file mode 100644 index 0000000000..9cf91e1159 --- /dev/null +++ b/core/api/src/services/quotes/index.ts @@ -0,0 +1 @@ +export * from "./quotes" diff --git a/core/api/src/services/quotes/proto/buf.gen.yaml b/core/api/src/services/quotes/proto/buf.gen.yaml new file mode 100644 index 0000000000..addcc3c669 --- /dev/null +++ b/core/api/src/services/quotes/proto/buf.gen.yaml @@ -0,0 +1,16 @@ +# /proto/buf.gen.yaml +version: v1 + +plugins: + - name: js + out: . + opt: import_style=commonjs,binary + path: ../../../../node_modules/.bin/protoc-gen-js + - name: grpc + out: . + opt: grpc_js + path: ../../../../node_modules/.bin/grpc_tools_node_protoc_plugin + - name: ts + out: . + opt: grpc_js + path: ../../../../node_modules/.bin/protoc-gen-ts diff --git a/core/api/src/services/quotes/proto/buf.yaml b/core/api/src/services/quotes/proto/buf.yaml new file mode 100644 index 0000000000..879cf4e212 --- /dev/null +++ b/core/api/src/services/quotes/proto/buf.yaml @@ -0,0 +1,8 @@ +# /proto/buf.yaml +version: v1 +lint: + use: + - DEFAULT +breaking: + use: + - WIRE_JSON diff --git a/core/api/src/services/quotes/proto/services/quotes/v1/quote_service.proto b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service.proto new file mode 100644 index 0000000000..cafdc2aca4 --- /dev/null +++ b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; +package services.quotes.v1; + +service QuoteService { + rpc GetQuoteToBuyUsd(GetQuoteToBuyUsdRequest) returns (GetQuoteToBuyUsdResponse) {} + rpc GetQuoteToSellUsd(GetQuoteToSellUsdRequest) returns (GetQuoteToSellUsdResponse) {} + rpc AcceptQuote(AcceptQuoteRequest) returns (AcceptQuoteResponse) {} +} + +message GetQuoteToBuyUsdRequest { + oneof quote_for { + uint64 amount_to_sell_in_sats = 1; + uint64 amount_to_buy_in_cents = 2; + } + + bool immediate_execution = 3; +} + +message GetQuoteToBuyUsdResponse { + string quote_id = 1; + uint64 amount_to_sell_in_sats = 2; + uint64 amount_to_buy_in_cents = 3; + uint32 expires_at = 4; + bool executed = 5; +} + +message GetQuoteToSellUsdRequest { + oneof quote_for { + uint64 amount_to_buy_in_sats = 1; + uint64 amount_to_sell_in_cents = 2; + } + + bool immediate_execution = 3; +} + +message GetQuoteToSellUsdResponse { + string quote_id = 1; + uint64 amount_to_buy_in_sats = 2; + uint64 amount_to_sell_in_cents = 3; + uint32 expires_at = 4; + bool executed = 5; +} + +message AcceptQuoteRequest { + string quote_id = 1; +} + +message AcceptQuoteResponse {} diff --git a/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_grpc_pb.d.ts b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_grpc_pb.d.ts new file mode 100644 index 0000000000..ac5914647f --- /dev/null +++ b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_grpc_pb.d.ts @@ -0,0 +1,227 @@ +// package: services.quotes.v1 +// file: services/quotes/v1/quote_service.proto + +/* tslint:disable */ +/* eslint-disable */ + +import * as grpc from "@grpc/grpc-js" +import * as services_quotes_v1_quote_service_pb from "./quote_service_pb" + +interface IQuoteServiceService + extends grpc.ServiceDefinition { + getQuoteToBuyUsd: IQuoteServiceService_IGetQuoteToBuyUsd + getQuoteToSellUsd: IQuoteServiceService_IGetQuoteToSellUsd + acceptQuote: IQuoteServiceService_IAcceptQuote +} + +interface IQuoteServiceService_IGetQuoteToBuyUsd + extends grpc.MethodDefinition< + services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest, + services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse + > { + path: "/services.quotes.v1.QuoteService/GetQuoteToBuyUsd" + requestStream: false + responseStream: false + requestSerialize: grpc.serialize + requestDeserialize: grpc.deserialize + responseSerialize: grpc.serialize + responseDeserialize: grpc.deserialize +} +interface IQuoteServiceService_IGetQuoteToSellUsd + extends grpc.MethodDefinition< + services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest, + services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse + > { + path: "/services.quotes.v1.QuoteService/GetQuoteToSellUsd" + requestStream: false + responseStream: false + requestSerialize: grpc.serialize + requestDeserialize: grpc.deserialize + responseSerialize: grpc.serialize + responseDeserialize: grpc.deserialize +} +interface IQuoteServiceService_IAcceptQuote + extends grpc.MethodDefinition< + services_quotes_v1_quote_service_pb.AcceptQuoteRequest, + services_quotes_v1_quote_service_pb.AcceptQuoteResponse + > { + path: "/services.quotes.v1.QuoteService/AcceptQuote" + requestStream: false + responseStream: false + requestSerialize: grpc.serialize + requestDeserialize: grpc.deserialize + responseSerialize: grpc.serialize + responseDeserialize: grpc.deserialize +} + +export const QuoteServiceService: IQuoteServiceService + +export interface IQuoteServiceServer extends grpc.UntypedServiceImplementation { + getQuoteToBuyUsd: grpc.handleUnaryCall< + services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest, + services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse + > + getQuoteToSellUsd: grpc.handleUnaryCall< + services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest, + services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse + > + acceptQuote: grpc.handleUnaryCall< + services_quotes_v1_quote_service_pb.AcceptQuoteRequest, + services_quotes_v1_quote_service_pb.AcceptQuoteResponse + > +} + +export interface IQuoteServiceClient { + getQuoteToBuyUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + getQuoteToBuyUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest, + metadata: grpc.Metadata, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + getQuoteToBuyUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest, + metadata: grpc.Metadata, + options: Partial, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + getQuoteToSellUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + getQuoteToSellUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest, + metadata: grpc.Metadata, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + getQuoteToSellUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest, + metadata: grpc.Metadata, + options: Partial, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + acceptQuote( + request: services_quotes_v1_quote_service_pb.AcceptQuoteRequest, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.AcceptQuoteResponse, + ) => void, + ): grpc.ClientUnaryCall + acceptQuote( + request: services_quotes_v1_quote_service_pb.AcceptQuoteRequest, + metadata: grpc.Metadata, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.AcceptQuoteResponse, + ) => void, + ): grpc.ClientUnaryCall + acceptQuote( + request: services_quotes_v1_quote_service_pb.AcceptQuoteRequest, + metadata: grpc.Metadata, + options: Partial, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.AcceptQuoteResponse, + ) => void, + ): grpc.ClientUnaryCall +} + +export class QuoteServiceClient extends grpc.Client implements IQuoteServiceClient { + constructor( + address: string, + credentials: grpc.ChannelCredentials, + options?: Partial, + ) + public getQuoteToBuyUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + public getQuoteToBuyUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest, + metadata: grpc.Metadata, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + public getQuoteToBuyUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest, + metadata: grpc.Metadata, + options: Partial, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + public getQuoteToSellUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + public getQuoteToSellUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest, + metadata: grpc.Metadata, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + public getQuoteToSellUsd( + request: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest, + metadata: grpc.Metadata, + options: Partial, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse, + ) => void, + ): grpc.ClientUnaryCall + public acceptQuote( + request: services_quotes_v1_quote_service_pb.AcceptQuoteRequest, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.AcceptQuoteResponse, + ) => void, + ): grpc.ClientUnaryCall + public acceptQuote( + request: services_quotes_v1_quote_service_pb.AcceptQuoteRequest, + metadata: grpc.Metadata, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.AcceptQuoteResponse, + ) => void, + ): grpc.ClientUnaryCall + public acceptQuote( + request: services_quotes_v1_quote_service_pb.AcceptQuoteRequest, + metadata: grpc.Metadata, + options: Partial, + callback: ( + error: grpc.ServiceError | null, + response: services_quotes_v1_quote_service_pb.AcceptQuoteResponse, + ) => void, + ): grpc.ClientUnaryCall +} diff --git a/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_grpc_pb.js b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_grpc_pb.js new file mode 100644 index 0000000000..a0409dfec0 --- /dev/null +++ b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_grpc_pb.js @@ -0,0 +1,110 @@ +// GENERATED CODE -- DO NOT EDIT! + +'use strict'; +var grpc = require('@grpc/grpc-js'); +var services_quotes_v1_quote_service_pb = require('../../../services/quotes/v1/quote_service_pb.js'); + +function serialize_services_quotes_v1_AcceptQuoteRequest(arg) { + if (!(arg instanceof services_quotes_v1_quote_service_pb.AcceptQuoteRequest)) { + throw new Error('Expected argument of type services.quotes.v1.AcceptQuoteRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_services_quotes_v1_AcceptQuoteRequest(buffer_arg) { + return services_quotes_v1_quote_service_pb.AcceptQuoteRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_services_quotes_v1_AcceptQuoteResponse(arg) { + if (!(arg instanceof services_quotes_v1_quote_service_pb.AcceptQuoteResponse)) { + throw new Error('Expected argument of type services.quotes.v1.AcceptQuoteResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_services_quotes_v1_AcceptQuoteResponse(buffer_arg) { + return services_quotes_v1_quote_service_pb.AcceptQuoteResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_services_quotes_v1_GetQuoteToBuyUsdRequest(arg) { + if (!(arg instanceof services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest)) { + throw new Error('Expected argument of type services.quotes.v1.GetQuoteToBuyUsdRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_services_quotes_v1_GetQuoteToBuyUsdRequest(buffer_arg) { + return services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_services_quotes_v1_GetQuoteToBuyUsdResponse(arg) { + if (!(arg instanceof services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse)) { + throw new Error('Expected argument of type services.quotes.v1.GetQuoteToBuyUsdResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_services_quotes_v1_GetQuoteToBuyUsdResponse(buffer_arg) { + return services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_services_quotes_v1_GetQuoteToSellUsdRequest(arg) { + if (!(arg instanceof services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest)) { + throw new Error('Expected argument of type services.quotes.v1.GetQuoteToSellUsdRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_services_quotes_v1_GetQuoteToSellUsdRequest(buffer_arg) { + return services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_services_quotes_v1_GetQuoteToSellUsdResponse(arg) { + if (!(arg instanceof services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse)) { + throw new Error('Expected argument of type services.quotes.v1.GetQuoteToSellUsdResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_services_quotes_v1_GetQuoteToSellUsdResponse(buffer_arg) { + return services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + + +var QuoteServiceService = exports.QuoteServiceService = { + getQuoteToBuyUsd: { + path: '/services.quotes.v1.QuoteService/GetQuoteToBuyUsd', + requestStream: false, + responseStream: false, + requestType: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdRequest, + responseType: services_quotes_v1_quote_service_pb.GetQuoteToBuyUsdResponse, + requestSerialize: serialize_services_quotes_v1_GetQuoteToBuyUsdRequest, + requestDeserialize: deserialize_services_quotes_v1_GetQuoteToBuyUsdRequest, + responseSerialize: serialize_services_quotes_v1_GetQuoteToBuyUsdResponse, + responseDeserialize: deserialize_services_quotes_v1_GetQuoteToBuyUsdResponse, + }, + getQuoteToSellUsd: { + path: '/services.quotes.v1.QuoteService/GetQuoteToSellUsd', + requestStream: false, + responseStream: false, + requestType: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdRequest, + responseType: services_quotes_v1_quote_service_pb.GetQuoteToSellUsdResponse, + requestSerialize: serialize_services_quotes_v1_GetQuoteToSellUsdRequest, + requestDeserialize: deserialize_services_quotes_v1_GetQuoteToSellUsdRequest, + responseSerialize: serialize_services_quotes_v1_GetQuoteToSellUsdResponse, + responseDeserialize: deserialize_services_quotes_v1_GetQuoteToSellUsdResponse, + }, + acceptQuote: { + path: '/services.quotes.v1.QuoteService/AcceptQuote', + requestStream: false, + responseStream: false, + requestType: services_quotes_v1_quote_service_pb.AcceptQuoteRequest, + responseType: services_quotes_v1_quote_service_pb.AcceptQuoteResponse, + requestSerialize: serialize_services_quotes_v1_AcceptQuoteRequest, + requestDeserialize: deserialize_services_quotes_v1_AcceptQuoteRequest, + responseSerialize: serialize_services_quotes_v1_AcceptQuoteResponse, + responseDeserialize: deserialize_services_quotes_v1_AcceptQuoteResponse, + }, +}; + +exports.QuoteServiceClient = grpc.makeGenericClientConstructor(QuoteServiceService, 'QuoteService'); diff --git a/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_pb.d.ts b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_pb.d.ts new file mode 100644 index 0000000000..d05dccac76 --- /dev/null +++ b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_pb.d.ts @@ -0,0 +1,190 @@ +// package: services.quotes.v1 +// file: services/quotes/v1/quote_service.proto + +/* tslint:disable */ +/* eslint-disable */ + +import * as jspb from "google-protobuf"; + +export class GetQuoteToBuyUsdRequest extends jspb.Message { + + hasAmountToSellInSats(): boolean; + clearAmountToSellInSats(): void; + getAmountToSellInSats(): number; + setAmountToSellInSats(value: number): GetQuoteToBuyUsdRequest; + + hasAmountToBuyInCents(): boolean; + clearAmountToBuyInCents(): void; + getAmountToBuyInCents(): number; + setAmountToBuyInCents(value: number): GetQuoteToBuyUsdRequest; + getImmediateExecution(): boolean; + setImmediateExecution(value: boolean): GetQuoteToBuyUsdRequest; + + getQuoteForCase(): GetQuoteToBuyUsdRequest.QuoteForCase; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): GetQuoteToBuyUsdRequest.AsObject; + static toObject(includeInstance: boolean, msg: GetQuoteToBuyUsdRequest): GetQuoteToBuyUsdRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: GetQuoteToBuyUsdRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): GetQuoteToBuyUsdRequest; + static deserializeBinaryFromReader(message: GetQuoteToBuyUsdRequest, reader: jspb.BinaryReader): GetQuoteToBuyUsdRequest; +} + +export namespace GetQuoteToBuyUsdRequest { + export type AsObject = { + amountToSellInSats: number, + amountToBuyInCents: number, + immediateExecution: boolean, + } + + export enum QuoteForCase { + QUOTE_FOR_NOT_SET = 0, + AMOUNT_TO_SELL_IN_SATS = 1, + AMOUNT_TO_BUY_IN_CENTS = 2, + } + +} + +export class GetQuoteToBuyUsdResponse extends jspb.Message { + getQuoteId(): string; + setQuoteId(value: string): GetQuoteToBuyUsdResponse; + getAmountToSellInSats(): number; + setAmountToSellInSats(value: number): GetQuoteToBuyUsdResponse; + getAmountToBuyInCents(): number; + setAmountToBuyInCents(value: number): GetQuoteToBuyUsdResponse; + getExpiresAt(): number; + setExpiresAt(value: number): GetQuoteToBuyUsdResponse; + getExecuted(): boolean; + setExecuted(value: boolean): GetQuoteToBuyUsdResponse; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): GetQuoteToBuyUsdResponse.AsObject; + static toObject(includeInstance: boolean, msg: GetQuoteToBuyUsdResponse): GetQuoteToBuyUsdResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: GetQuoteToBuyUsdResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): GetQuoteToBuyUsdResponse; + static deserializeBinaryFromReader(message: GetQuoteToBuyUsdResponse, reader: jspb.BinaryReader): GetQuoteToBuyUsdResponse; +} + +export namespace GetQuoteToBuyUsdResponse { + export type AsObject = { + quoteId: string, + amountToSellInSats: number, + amountToBuyInCents: number, + expiresAt: number, + executed: boolean, + } +} + +export class GetQuoteToSellUsdRequest extends jspb.Message { + + hasAmountToBuyInSats(): boolean; + clearAmountToBuyInSats(): void; + getAmountToBuyInSats(): number; + setAmountToBuyInSats(value: number): GetQuoteToSellUsdRequest; + + hasAmountToSellInCents(): boolean; + clearAmountToSellInCents(): void; + getAmountToSellInCents(): number; + setAmountToSellInCents(value: number): GetQuoteToSellUsdRequest; + getImmediateExecution(): boolean; + setImmediateExecution(value: boolean): GetQuoteToSellUsdRequest; + + getQuoteForCase(): GetQuoteToSellUsdRequest.QuoteForCase; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): GetQuoteToSellUsdRequest.AsObject; + static toObject(includeInstance: boolean, msg: GetQuoteToSellUsdRequest): GetQuoteToSellUsdRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: GetQuoteToSellUsdRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): GetQuoteToSellUsdRequest; + static deserializeBinaryFromReader(message: GetQuoteToSellUsdRequest, reader: jspb.BinaryReader): GetQuoteToSellUsdRequest; +} + +export namespace GetQuoteToSellUsdRequest { + export type AsObject = { + amountToBuyInSats: number, + amountToSellInCents: number, + immediateExecution: boolean, + } + + export enum QuoteForCase { + QUOTE_FOR_NOT_SET = 0, + AMOUNT_TO_BUY_IN_SATS = 1, + AMOUNT_TO_SELL_IN_CENTS = 2, + } + +} + +export class GetQuoteToSellUsdResponse extends jspb.Message { + getQuoteId(): string; + setQuoteId(value: string): GetQuoteToSellUsdResponse; + getAmountToBuyInSats(): number; + setAmountToBuyInSats(value: number): GetQuoteToSellUsdResponse; + getAmountToSellInCents(): number; + setAmountToSellInCents(value: number): GetQuoteToSellUsdResponse; + getExpiresAt(): number; + setExpiresAt(value: number): GetQuoteToSellUsdResponse; + getExecuted(): boolean; + setExecuted(value: boolean): GetQuoteToSellUsdResponse; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): GetQuoteToSellUsdResponse.AsObject; + static toObject(includeInstance: boolean, msg: GetQuoteToSellUsdResponse): GetQuoteToSellUsdResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: GetQuoteToSellUsdResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): GetQuoteToSellUsdResponse; + static deserializeBinaryFromReader(message: GetQuoteToSellUsdResponse, reader: jspb.BinaryReader): GetQuoteToSellUsdResponse; +} + +export namespace GetQuoteToSellUsdResponse { + export type AsObject = { + quoteId: string, + amountToBuyInSats: number, + amountToSellInCents: number, + expiresAt: number, + executed: boolean, + } +} + +export class AcceptQuoteRequest extends jspb.Message { + getQuoteId(): string; + setQuoteId(value: string): AcceptQuoteRequest; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): AcceptQuoteRequest.AsObject; + static toObject(includeInstance: boolean, msg: AcceptQuoteRequest): AcceptQuoteRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: AcceptQuoteRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): AcceptQuoteRequest; + static deserializeBinaryFromReader(message: AcceptQuoteRequest, reader: jspb.BinaryReader): AcceptQuoteRequest; +} + +export namespace AcceptQuoteRequest { + export type AsObject = { + quoteId: string, + } +} + +export class AcceptQuoteResponse extends jspb.Message { + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): AcceptQuoteResponse.AsObject; + static toObject(includeInstance: boolean, msg: AcceptQuoteResponse): AcceptQuoteResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: AcceptQuoteResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): AcceptQuoteResponse; + static deserializeBinaryFromReader(message: AcceptQuoteResponse, reader: jspb.BinaryReader): AcceptQuoteResponse; +} + +export namespace AcceptQuoteResponse { + export type AsObject = { + } +} diff --git a/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_pb.js b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_pb.js new file mode 100644 index 0000000000..e63d8b0563 --- /dev/null +++ b/core/api/src/services/quotes/proto/services/quotes/v1/quote_service_pb.js @@ -0,0 +1,1393 @@ +// source: services/quotes/v1/quote_service.proto +/** + * @fileoverview + * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. + * @suppress {messageConventions} JS Compiler reports an error if a variable or + * field starts with 'MSG_' and isn't a translatable message. + * @public + */ +// GENERATED CODE -- DO NOT EDIT! +/* eslint-disable */ +// @ts-nocheck + +var jspb = require('google-protobuf'); +var goog = jspb; +var global = + (typeof globalThis !== 'undefined' && globalThis) || + (typeof window !== 'undefined' && window) || + (typeof global !== 'undefined' && global) || + (typeof self !== 'undefined' && self) || + (function () { return this; }).call(null) || + Function('return this')(); + +goog.exportSymbol('proto.services.quotes.v1.AcceptQuoteRequest', null, global); +goog.exportSymbol('proto.services.quotes.v1.AcceptQuoteResponse', null, global); +goog.exportSymbol('proto.services.quotes.v1.GetQuoteToBuyUsdRequest', null, global); +goog.exportSymbol('proto.services.quotes.v1.GetQuoteToBuyUsdRequest.QuoteForCase', null, global); +goog.exportSymbol('proto.services.quotes.v1.GetQuoteToBuyUsdResponse', null, global); +goog.exportSymbol('proto.services.quotes.v1.GetQuoteToSellUsdRequest', null, global); +goog.exportSymbol('proto.services.quotes.v1.GetQuoteToSellUsdRequest.QuoteForCase', null, global); +goog.exportSymbol('proto.services.quotes.v1.GetQuoteToSellUsdResponse', null, global); +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.services.quotes.v1.GetQuoteToBuyUsdRequest.oneofGroups_); +}; +goog.inherits(proto.services.quotes.v1.GetQuoteToBuyUsdRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.services.quotes.v1.GetQuoteToBuyUsdRequest.displayName = 'proto.services.quotes.v1.GetQuoteToBuyUsdRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.services.quotes.v1.GetQuoteToBuyUsdResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.services.quotes.v1.GetQuoteToBuyUsdResponse.displayName = 'proto.services.quotes.v1.GetQuoteToBuyUsdResponse'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.services.quotes.v1.GetQuoteToSellUsdRequest.oneofGroups_); +}; +goog.inherits(proto.services.quotes.v1.GetQuoteToSellUsdRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.services.quotes.v1.GetQuoteToSellUsdRequest.displayName = 'proto.services.quotes.v1.GetQuoteToSellUsdRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.services.quotes.v1.GetQuoteToSellUsdResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.services.quotes.v1.GetQuoteToSellUsdResponse.displayName = 'proto.services.quotes.v1.GetQuoteToSellUsdResponse'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.services.quotes.v1.AcceptQuoteRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.services.quotes.v1.AcceptQuoteRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.services.quotes.v1.AcceptQuoteRequest.displayName = 'proto.services.quotes.v1.AcceptQuoteRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.services.quotes.v1.AcceptQuoteResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.services.quotes.v1.AcceptQuoteResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.services.quotes.v1.AcceptQuoteResponse.displayName = 'proto.services.quotes.v1.AcceptQuoteResponse'; +} + +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.oneofGroups_ = [[1,2]]; + +/** + * @enum {number} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.QuoteForCase = { + QUOTE_FOR_NOT_SET: 0, + AMOUNT_TO_SELL_IN_SATS: 1, + AMOUNT_TO_BUY_IN_CENTS: 2 +}; + +/** + * @return {proto.services.quotes.v1.GetQuoteToBuyUsdRequest.QuoteForCase} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.getQuoteForCase = function() { + return /** @type {proto.services.quotes.v1.GetQuoteToBuyUsdRequest.QuoteForCase} */(jspb.Message.computeOneofCase(this, proto.services.quotes.v1.GetQuoteToBuyUsdRequest.oneofGroups_[0])); +}; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.toObject = function(opt_includeInstance) { + return proto.services.quotes.v1.GetQuoteToBuyUsdRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.toObject = function(includeInstance, msg) { + var f, obj = { +amountToSellInSats: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f, +amountToBuyInCents: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f, +immediateExecution: jspb.Message.getBooleanFieldWithDefault(msg, 3, false) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.services.quotes.v1.GetQuoteToBuyUsdRequest; + return proto.services.quotes.v1.GetQuoteToBuyUsdRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readUint64()); + msg.setAmountToSellInSats(value); + break; + case 2: + var value = /** @type {number} */ (reader.readUint64()); + msg.setAmountToBuyInCents(value); + break; + case 3: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setImmediateExecution(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.services.quotes.v1.GetQuoteToBuyUsdRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = /** @type {number} */ (jspb.Message.getField(message, 1)); + if (f != null) { + writer.writeUint64( + 1, + f + ); + } + f = /** @type {number} */ (jspb.Message.getField(message, 2)); + if (f != null) { + writer.writeUint64( + 2, + f + ); + } + f = message.getImmediateExecution(); + if (f) { + writer.writeBool( + 3, + f + ); + } +}; + + +/** + * optional uint64 amount_to_sell_in_sats = 1; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.getAmountToSellInSats = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.setAmountToSellInSats = function(value) { + return jspb.Message.setOneofField(this, 1, proto.services.quotes.v1.GetQuoteToBuyUsdRequest.oneofGroups_[0], value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.clearAmountToSellInSats = function() { + return jspb.Message.setOneofField(this, 1, proto.services.quotes.v1.GetQuoteToBuyUsdRequest.oneofGroups_[0], undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.hasAmountToSellInSats = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional uint64 amount_to_buy_in_cents = 2; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.getAmountToBuyInCents = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.setAmountToBuyInCents = function(value) { + return jspb.Message.setOneofField(this, 2, proto.services.quotes.v1.GetQuoteToBuyUsdRequest.oneofGroups_[0], value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.clearAmountToBuyInCents = function() { + return jspb.Message.setOneofField(this, 2, proto.services.quotes.v1.GetQuoteToBuyUsdRequest.oneofGroups_[0], undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.hasAmountToBuyInCents = function() { + return jspb.Message.getField(this, 2) != null; +}; + + +/** + * optional bool immediate_execution = 3; + * @return {boolean} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.getImmediateExecution = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdRequest.prototype.setImmediateExecution = function(value) { + return jspb.Message.setProto3BooleanField(this, 3, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.toObject = function(opt_includeInstance) { + return proto.services.quotes.v1.GetQuoteToBuyUsdResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.toObject = function(includeInstance, msg) { + var f, obj = { +quoteId: jspb.Message.getFieldWithDefault(msg, 1, ""), +amountToSellInSats: jspb.Message.getFieldWithDefault(msg, 2, 0), +amountToBuyInCents: jspb.Message.getFieldWithDefault(msg, 3, 0), +expiresAt: jspb.Message.getFieldWithDefault(msg, 4, 0), +executed: jspb.Message.getBooleanFieldWithDefault(msg, 5, false) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.services.quotes.v1.GetQuoteToBuyUsdResponse; + return proto.services.quotes.v1.GetQuoteToBuyUsdResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setQuoteId(value); + break; + case 2: + var value = /** @type {number} */ (reader.readUint64()); + msg.setAmountToSellInSats(value); + break; + case 3: + var value = /** @type {number} */ (reader.readUint64()); + msg.setAmountToBuyInCents(value); + break; + case 4: + var value = /** @type {number} */ (reader.readUint32()); + msg.setExpiresAt(value); + break; + case 5: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setExecuted(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.services.quotes.v1.GetQuoteToBuyUsdResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getQuoteId(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getAmountToSellInSats(); + if (f !== 0) { + writer.writeUint64( + 2, + f + ); + } + f = message.getAmountToBuyInCents(); + if (f !== 0) { + writer.writeUint64( + 3, + f + ); + } + f = message.getExpiresAt(); + if (f !== 0) { + writer.writeUint32( + 4, + f + ); + } + f = message.getExecuted(); + if (f) { + writer.writeBool( + 5, + f + ); + } +}; + + +/** + * optional string quote_id = 1; + * @return {string} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.getQuoteId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.setQuoteId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional uint64 amount_to_sell_in_sats = 2; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.getAmountToSellInSats = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.setAmountToSellInSats = function(value) { + return jspb.Message.setProto3IntField(this, 2, value); +}; + + +/** + * optional uint64 amount_to_buy_in_cents = 3; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.getAmountToBuyInCents = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.setAmountToBuyInCents = function(value) { + return jspb.Message.setProto3IntField(this, 3, value); +}; + + +/** + * optional uint32 expires_at = 4; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.getExpiresAt = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.setExpiresAt = function(value) { + return jspb.Message.setProto3IntField(this, 4, value); +}; + + +/** + * optional bool executed = 5; + * @return {boolean} + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.getExecuted = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 5, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.services.quotes.v1.GetQuoteToBuyUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToBuyUsdResponse.prototype.setExecuted = function(value) { + return jspb.Message.setProto3BooleanField(this, 5, value); +}; + + + +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.oneofGroups_ = [[1,2]]; + +/** + * @enum {number} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.QuoteForCase = { + QUOTE_FOR_NOT_SET: 0, + AMOUNT_TO_BUY_IN_SATS: 1, + AMOUNT_TO_SELL_IN_CENTS: 2 +}; + +/** + * @return {proto.services.quotes.v1.GetQuoteToSellUsdRequest.QuoteForCase} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.getQuoteForCase = function() { + return /** @type {proto.services.quotes.v1.GetQuoteToSellUsdRequest.QuoteForCase} */(jspb.Message.computeOneofCase(this, proto.services.quotes.v1.GetQuoteToSellUsdRequest.oneofGroups_[0])); +}; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.toObject = function(opt_includeInstance) { + return proto.services.quotes.v1.GetQuoteToSellUsdRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.toObject = function(includeInstance, msg) { + var f, obj = { +amountToBuyInSats: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f, +amountToSellInCents: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f, +immediateExecution: jspb.Message.getBooleanFieldWithDefault(msg, 3, false) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.services.quotes.v1.GetQuoteToSellUsdRequest; + return proto.services.quotes.v1.GetQuoteToSellUsdRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readUint64()); + msg.setAmountToBuyInSats(value); + break; + case 2: + var value = /** @type {number} */ (reader.readUint64()); + msg.setAmountToSellInCents(value); + break; + case 3: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setImmediateExecution(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.services.quotes.v1.GetQuoteToSellUsdRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = /** @type {number} */ (jspb.Message.getField(message, 1)); + if (f != null) { + writer.writeUint64( + 1, + f + ); + } + f = /** @type {number} */ (jspb.Message.getField(message, 2)); + if (f != null) { + writer.writeUint64( + 2, + f + ); + } + f = message.getImmediateExecution(); + if (f) { + writer.writeBool( + 3, + f + ); + } +}; + + +/** + * optional uint64 amount_to_buy_in_sats = 1; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.getAmountToBuyInSats = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.setAmountToBuyInSats = function(value) { + return jspb.Message.setOneofField(this, 1, proto.services.quotes.v1.GetQuoteToSellUsdRequest.oneofGroups_[0], value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.clearAmountToBuyInSats = function() { + return jspb.Message.setOneofField(this, 1, proto.services.quotes.v1.GetQuoteToSellUsdRequest.oneofGroups_[0], undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.hasAmountToBuyInSats = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional uint64 amount_to_sell_in_cents = 2; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.getAmountToSellInCents = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.setAmountToSellInCents = function(value) { + return jspb.Message.setOneofField(this, 2, proto.services.quotes.v1.GetQuoteToSellUsdRequest.oneofGroups_[0], value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.clearAmountToSellInCents = function() { + return jspb.Message.setOneofField(this, 2, proto.services.quotes.v1.GetQuoteToSellUsdRequest.oneofGroups_[0], undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.hasAmountToSellInCents = function() { + return jspb.Message.getField(this, 2) != null; +}; + + +/** + * optional bool immediate_execution = 3; + * @return {boolean} + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.getImmediateExecution = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdRequest} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdRequest.prototype.setImmediateExecution = function(value) { + return jspb.Message.setProto3BooleanField(this, 3, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.toObject = function(opt_includeInstance) { + return proto.services.quotes.v1.GetQuoteToSellUsdResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.toObject = function(includeInstance, msg) { + var f, obj = { +quoteId: jspb.Message.getFieldWithDefault(msg, 1, ""), +amountToBuyInSats: jspb.Message.getFieldWithDefault(msg, 2, 0), +amountToSellInCents: jspb.Message.getFieldWithDefault(msg, 3, 0), +expiresAt: jspb.Message.getFieldWithDefault(msg, 4, 0), +executed: jspb.Message.getBooleanFieldWithDefault(msg, 5, false) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.services.quotes.v1.GetQuoteToSellUsdResponse; + return proto.services.quotes.v1.GetQuoteToSellUsdResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setQuoteId(value); + break; + case 2: + var value = /** @type {number} */ (reader.readUint64()); + msg.setAmountToBuyInSats(value); + break; + case 3: + var value = /** @type {number} */ (reader.readUint64()); + msg.setAmountToSellInCents(value); + break; + case 4: + var value = /** @type {number} */ (reader.readUint32()); + msg.setExpiresAt(value); + break; + case 5: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setExecuted(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.services.quotes.v1.GetQuoteToSellUsdResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getQuoteId(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getAmountToBuyInSats(); + if (f !== 0) { + writer.writeUint64( + 2, + f + ); + } + f = message.getAmountToSellInCents(); + if (f !== 0) { + writer.writeUint64( + 3, + f + ); + } + f = message.getExpiresAt(); + if (f !== 0) { + writer.writeUint32( + 4, + f + ); + } + f = message.getExecuted(); + if (f) { + writer.writeBool( + 5, + f + ); + } +}; + + +/** + * optional string quote_id = 1; + * @return {string} + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.getQuoteId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.setQuoteId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional uint64 amount_to_buy_in_sats = 2; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.getAmountToBuyInSats = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.setAmountToBuyInSats = function(value) { + return jspb.Message.setProto3IntField(this, 2, value); +}; + + +/** + * optional uint64 amount_to_sell_in_cents = 3; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.getAmountToSellInCents = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.setAmountToSellInCents = function(value) { + return jspb.Message.setProto3IntField(this, 3, value); +}; + + +/** + * optional uint32 expires_at = 4; + * @return {number} + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.getExpiresAt = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.setExpiresAt = function(value) { + return jspb.Message.setProto3IntField(this, 4, value); +}; + + +/** + * optional bool executed = 5; + * @return {boolean} + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.getExecuted = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 5, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.services.quotes.v1.GetQuoteToSellUsdResponse} returns this + */ +proto.services.quotes.v1.GetQuoteToSellUsdResponse.prototype.setExecuted = function(value) { + return jspb.Message.setProto3BooleanField(this, 5, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.services.quotes.v1.AcceptQuoteRequest.prototype.toObject = function(opt_includeInstance) { + return proto.services.quotes.v1.AcceptQuoteRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.services.quotes.v1.AcceptQuoteRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.AcceptQuoteRequest.toObject = function(includeInstance, msg) { + var f, obj = { +quoteId: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.services.quotes.v1.AcceptQuoteRequest} + */ +proto.services.quotes.v1.AcceptQuoteRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.services.quotes.v1.AcceptQuoteRequest; + return proto.services.quotes.v1.AcceptQuoteRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.services.quotes.v1.AcceptQuoteRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.services.quotes.v1.AcceptQuoteRequest} + */ +proto.services.quotes.v1.AcceptQuoteRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setQuoteId(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.services.quotes.v1.AcceptQuoteRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.services.quotes.v1.AcceptQuoteRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.services.quotes.v1.AcceptQuoteRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.AcceptQuoteRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getQuoteId(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string quote_id = 1; + * @return {string} + */ +proto.services.quotes.v1.AcceptQuoteRequest.prototype.getQuoteId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.quotes.v1.AcceptQuoteRequest} returns this + */ +proto.services.quotes.v1.AcceptQuoteRequest.prototype.setQuoteId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.services.quotes.v1.AcceptQuoteResponse.prototype.toObject = function(opt_includeInstance) { + return proto.services.quotes.v1.AcceptQuoteResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.services.quotes.v1.AcceptQuoteResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.AcceptQuoteResponse.toObject = function(includeInstance, msg) { + var f, obj = { + + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.services.quotes.v1.AcceptQuoteResponse} + */ +proto.services.quotes.v1.AcceptQuoteResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.services.quotes.v1.AcceptQuoteResponse; + return proto.services.quotes.v1.AcceptQuoteResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.services.quotes.v1.AcceptQuoteResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.services.quotes.v1.AcceptQuoteResponse} + */ +proto.services.quotes.v1.AcceptQuoteResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.services.quotes.v1.AcceptQuoteResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.services.quotes.v1.AcceptQuoteResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.services.quotes.v1.AcceptQuoteResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.quotes.v1.AcceptQuoteResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; +}; + + +goog.object.extend(exports, proto.services.quotes.v1); diff --git a/core/api/src/services/quotes/quotes.ts b/core/api/src/services/quotes/quotes.ts new file mode 100644 index 0000000000..13f2b2b892 --- /dev/null +++ b/core/api/src/services/quotes/quotes.ts @@ -0,0 +1,224 @@ +import util from "util" + +import { credentials } from "@grpc/grpc-js" + +import { baseLogger } from "../logger" + +import { + convertGetQuoteToBuyUsdResponse, + convertGetQuoteToSellUsdResponse, +} from "./helpers" + +import { QuoteServiceClient } from "./proto/services/quotes/v1/quote_service_grpc_pb" + +import { + GetQuoteToBuyUsdRequest, + GetQuoteToBuyUsdResponse, + GetQuoteToSellUsdRequest, + GetQuoteToSellUsdResponse, + AcceptQuoteRequest, + AcceptQuoteResponse, +} from "./proto/services/quotes/v1/quote_service_pb" + +import { QUOTE_SERVER_HOST, QUOTE_SERVER_PORT } from "@/config" + +import { parseErrorMessageFromUnknown } from "@/domain/shared" +import { wrapAsyncFunctionsToRunInSpan } from "@/services/tracing" +import { + QuotesServiceError, + QuotesServerError, + NoConnectionToQuotesError, + UnknownQuotesServiceError, + QuotesExchangePriceError, + QuotesEntityError, + QuotesAlreadyAcceptedError, + QuotesExpiredError, + QuotesCouldNotParseIdError, + QuotesLedgerError, +} from "@/domain/quotes" +import { + IQuotesService, + QuoteToBuyUsd, + QuoteToSellUsd, +} from "@/domain/quotes/index.types" + +const client = new QuoteServiceClient( + `${QUOTE_SERVER_HOST}:${QUOTE_SERVER_PORT}`, + credentials.createInsecure(), +) + +const clientGetQuoteToBuyUsd = util.promisify< + GetQuoteToBuyUsdRequest, + GetQuoteToBuyUsdResponse +>(client.getQuoteToBuyUsd.bind(client)) + +const clientGetQuoteToSellUsd = util.promisify< + GetQuoteToSellUsdRequest, + GetQuoteToSellUsdResponse +>(client.getQuoteToSellUsd.bind(client)) + +const clientAcceptQuote = util.promisify( + client.acceptQuote.bind(client), +) + +export const QuotesService = (): IQuotesService => { + const getQuoteToBuyUsdWithSats = async function ({ + btcAmount, + immediateExecution = false, + }: { + btcAmount: BtcPaymentAmount + immediateExecution: boolean + }): Promise { + try { + const req = new GetQuoteToBuyUsdRequest().setAmountToSellInSats( + Number(btcAmount.amount), + ) + req.setImmediateExecution(immediateExecution) + + const response = await clientGetQuoteToBuyUsd(req) + + return convertGetQuoteToBuyUsdResponse(response) + } catch (error) { + baseLogger.error({ error }, "GetQuoteToBuyUsdWithSats unable to fetch quote") + return handleDealerErrors(error) + } + } + + const getQuoteToBuyUsdWithCents = async function ({ + usdAmount, + immediateExecution = false, + }: { + usdAmount: UsdPaymentAmount + immediateExecution: boolean + }): Promise { + try { + const req = new GetQuoteToBuyUsdRequest().setAmountToBuyInCents( + Number(usdAmount.amount), + ) + req.setImmediateExecution(immediateExecution) + + const response = await clientGetQuoteToBuyUsd(req) + + return convertGetQuoteToBuyUsdResponse(response) + } catch (error) { + baseLogger.error({ error }, "GetQuoteToBuyUsdWithCents unable to fetch quote") + return handleDealerErrors(error) + } + } + + const getQuoteToSellUsdWithSats = async function ({ + btcAmount, + immediateExecution = false, + }: { + btcAmount: BtcPaymentAmount + immediateExecution: boolean + }): Promise { + try { + const req = new GetQuoteToSellUsdRequest().setAmountToBuyInSats( + Number(btcAmount.amount), + ) + req.setImmediateExecution(immediateExecution) + + const response = await clientGetQuoteToSellUsd(req) + + return convertGetQuoteToSellUsdResponse(response) + } catch (error) { + baseLogger.error({ error }, "GetQuoteToSellUsdWithSats unable to fetch quote") + return handleDealerErrors(error) + } + } + + const getQuoteToSellUsdWithCents = async function ({ + usdAmount, + immediateExecution = false, + }: { + usdAmount: UsdPaymentAmount + immediateExecution: boolean + }): Promise { + try { + const req = new GetQuoteToSellUsdRequest().setAmountToSellInCents( + Number(usdAmount.amount), + ) + req.setImmediateExecution(immediateExecution) + + const response = await clientGetQuoteToSellUsd(req) + + return convertGetQuoteToSellUsdResponse(response) + } catch (error) { + baseLogger.error({ error }, "GetQuoteToSellUsdWithCents unable to fetch quote") + return handleDealerErrors(error) + } + } + + const acceptQuote = async function ( + quoteId: string, + ): Promise { + try { + await clientAcceptQuote(new AcceptQuoteRequest().setQuoteId(quoteId)) + return true + } catch (error) { + baseLogger.error({ error }, "AcceptQuote unable to accept quote") + return handleDealerErrors(error) + } + } + + return wrapAsyncFunctionsToRunInSpan({ + namespace: "services.dealer-quote", + fns: { + getQuoteToBuyUsdWithSats, + getQuoteToBuyUsdWithCents, + + getQuoteToSellUsdWithSats, + getQuoteToSellUsdWithCents, + + acceptQuote, + }, + }) +} + +const handleDealerErrors = (err: Error | string | unknown) => { + const errMsg = parseErrorMessageFromUnknown(err) + + const match = (knownErrDetail: RegExp): boolean => knownErrDetail.test(errMsg) + + switch (true) { + case match(KnownDealerErrorDetails.NoConnection): + return new NoConnectionToQuotesError(errMsg) + + case match(KnownDealerErrorDetails.QuotesExchangePrice): + return new QuotesExchangePriceError(errMsg) + + case match(KnownDealerErrorDetails.QuotesLedgerError): + return new QuotesLedgerError(errMsg) + + case match(KnownDealerErrorDetails.QuotesEntityError): + return new QuotesEntityError(errMsg) + + case match(KnownDealerErrorDetails.QuotesAlreadyAcceptedError): + return new QuotesAlreadyAcceptedError(errMsg) + + case match(KnownDealerErrorDetails.QuotesExpiredError): + return new QuotesExpiredError(errMsg) + + case match(KnownDealerErrorDetails.QuotesCouldNotParseIdError): + return new QuotesCouldNotParseIdError(errMsg) + + case match(KnownDealerErrorDetails.QuotesServer): + return new QuotesServerError(errMsg) + + default: + return new UnknownQuotesServiceError(errMsg) + } +} + +export const KnownDealerErrorDetails = { + NoConnection: /No connection established/, + QuotesExchangePrice: + /(?:StalePrice: last update was at|No price data available|OrderBook:)/, + QuotesLedgerError: /Sqlx/, + QuotesEntityError: /EntityError/, + QuotesAlreadyAcceptedError: /already accepted/, + QuotesExpiredError: /Quote has expired/, + QuotesCouldNotParseIdError: /CouldNotParseIncomingUuid/, + QuotesServer: /QuotesServerError/, +} as const diff --git a/dev/config/apollo-federation/supergraph.graphql b/dev/config/apollo-federation/supergraph.graphql index af3cfad19a..5b7ea6c3b3 100644 --- a/dev/config/apollo-federation/supergraph.graphql +++ b/dev/config/apollo-federation/supergraph.graphql @@ -1791,6 +1791,12 @@ type Query Returns 1 Sat and 1 Usd Cent price for the given currency in minor unit """ realtimePrice(currency: DisplayCurrency = "USD"): RealtimePrice! @join__field(graph: PUBLIC) + + """ + Get a StableSats quote for buying or selling USD. + Returns a quote with pricing and expiration information. + """ + stableSatsGetQuote(input: StableSatsGetQuoteInput!): StableSatsQuotePayload! @join__field(graph: PUBLIC) userDefaultWalletId(username: Username!): WalletId! @join__field(graph: PUBLIC) @deprecated(reason: "will be migrated to AccountDefaultWalletId") usernameAvailable(username: Username!): Boolean @join__field(graph: PUBLIC) } @@ -1819,6 +1825,15 @@ type QuizClaimPayload quizzes: [Quiz!]! } +enum QuoteType + @join__type(graph: PUBLIC) +{ + BUY_USD_WITH_CENTS @join__enumValue(graph: PUBLIC) + BUY_USD_WITH_SATS @join__enumValue(graph: PUBLIC) + SELL_USD_FOR_CENTS @join__enumValue(graph: PUBLIC) + SELL_USD_FOR_SATS @join__enumValue(graph: PUBLIC) +} + type RealtimePrice @join__type(graph: PUBLIC) { @@ -1920,6 +1935,54 @@ A string amount (of a currency) that can be negative (e.g. in a transaction) scalar SignedDisplayMajorAmount @join__type(graph: PUBLIC) +input StableSatsGetQuoteInput + @join__type(graph: PUBLIC) +{ + """Amount in cents (for cent-based quotes)""" + centAmount: CentAmount + + """Type of quote to request""" + quoteType: QuoteType! + + """Amount in satoshis (for sat-based quotes)""" + satAmount: SatAmount + + """Wallet id of the requesting wallet""" + walletId: WalletId! +} + +type StableSatsQuote + @join__type(graph: PUBLIC) +{ + """Amount to buy in cents (for buy USD quotes)""" + amountToBuyInCents: Int + + """Amount to buy in satoshis (for sell USD quotes)""" + amountToBuyInSats: Int + + """Amount to sell in cents (for sell USD quotes)""" + amountToSellInCents: Int + + """Amount to sell in satoshis (for buy USD quotes)""" + amountToSellInSats: Int + + """Whether the quote has been executed""" + executed: Boolean! + + """Quote expiration timestamp""" + expiresAt: Int! + + """Unique identifier for the quote""" + quoteId: String! +} + +type StableSatsQuotePayload + @join__type(graph: PUBLIC) +{ + errors: [Error!]! + quote: StableSatsQuote +} + type StatefulNotification @join__type(graph: NOTIFICATIONS) { diff --git a/dev/config/stablesats.yml b/dev/config/stablesats.yml index 82244ad287..9fec0519a8 100644 --- a/dev/config/stablesats.yml +++ b/dev/config/stablesats.yml @@ -1,3 +1,4 @@ +# This file documents the defaults: exchanges: okex: weight: 1.0 @@ -6,6 +7,21 @@ price_server: enabled: true server: listen_port: 3325 + health: + unhealthy_msg_interval_price: 20 + fees: + base_fee_rate: 0.0005 + immediate_fee_rate: 0.0005 + delayed_fee_rate: 0.0007 + price_cache: + dev_mock_price_btc_in_usd: 20000 + +quotes_server: + enabled: true + server: + listen_port: 3326 + health: + unhealthy_msg_interval_price: 20 fees: base_fee_rate: 0.0005 immediate_fee_rate: 0.0005 diff --git a/dev/docker-compose.deps.yml b/dev/docker-compose.deps.yml index 1c936954d1..1b3d924ba1 100644 --- a/dev/docker-compose.deps.yml +++ b/dev/docker-compose.deps.yml @@ -356,15 +356,31 @@ services: image: postgres:14.1 environment: POSTGRES_PASSWORD: postgres - + stablesats-pg: + image: postgres:15.1 + environment: + - POSTGRES_USER=user + - POSTGRES_PASSWORD=password + - POSTGRES_DB=pg + healthcheck: + test: ["CMD-SHELL", "pg_isready"] + interval: 5s + timeout: 30s + retries: 5 stablesats: image: us.gcr.io/galoy-org/stablesats-rs:latest ports: - "3325:3325" + - "3326:3326" command: ["stablesats", "run"] working_dir: /repo/config + environment: + - PG_CON=postgres://user:password@stablesats-pg:5432/pg depends_on: - - otel-agent + stablesats-pg: + condition: service_healthy + otel-agent: + condition: service_started restart: on-failure:10 volumes: - ${HOST_PROJECT_PATH:-.}/:/repo