Skip to content
Open
12 changes: 12 additions & 0 deletions dev/apollo-federation/supergraph.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -374,13 +374,25 @@ type BridgeWithdrawal
@join__type(graph: PUBLIC)
{
amount: String!
bridgeDeveloperFee: String
bridgeExchangeFee: String
bridgeTransferId: String
createdAt: String!
currency: String!
externalAccountId: String
failureReason: String
finalAmount: String
estimatedBridgeFee: String
estimatedBridgeFeePercent: String
estimatedCustomerFee: String
estimatedGasBuffer: String
flashFee: String
flashFeeIsEstimate: Boolean!
flashFeeNotice: String
flashFeePercent: String
id: ID!
status: String!
subtotalAmount: String
}

"""
Expand Down
13 changes: 12 additions & 1 deletion dev/config/base-config.yaml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should never check in an API key, not even a sandbox one. Remove this and purge it from commit history

Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,20 @@ ibex:

bridge:
enabled: true
apiKey: "sk-test-3bd6463c9cd77c3d8858c60b9997d0c6"
apiKey: "<replace>"
baseUrl: "https://api.sandbox.bridge.xyz/v0"
minWithdrawalAmount: 2
developerFeePercent: 2.0
withdrawalFeeEstimate:
bridgeFixedFeePercent: 0.6
usdtTransferGasLimit: 65000
gasPriceBufferMultiplier: 1.5
ethereumGasRpcUrls:
- "https://ethereum-rpc.publicnode.com"
- "https://eth.llamarpc.com"
- "https://cloudflare-eth.com"
fallbackGasPriceGwei: 30
ethUsdFallback: 3000
timeoutMs: 15000
webhook:
port: 4009
Expand Down
12 changes: 12 additions & 0 deletions src/app/bridge/get-withdrawal-flash-fee-notice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getI18nInstance } from "@config"
import { getLanguageOrDefault } from "@domain/locale"

export const BRIDGE_WITHDRAWAL_FLASH_FEE_NOTICE_PHRASE =
"notification.bridgeWithdrawal.flashFeeNotice"

export const getBridgeWithdrawalFlashFeeNotice = (locale: UserLanguage): string =>
getI18nInstance().__({ phrase: BRIDGE_WITHDRAWAL_FLASH_FEE_NOTICE_PHRASE, locale })

export const getBridgeWithdrawalFlashFeeNoticeForUser = (
user?: Pick<User, "language">,
): string => getBridgeWithdrawalFlashFeeNotice(getLanguageOrDefault(user?.language ?? ""))
1 change: 1 addition & 0 deletions src/config/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"title": "Deposit received"
},
"bridgeWithdrawal": {
"flashFeeNotice": "Shown fees and amounts are estimates. Final fees may differ.",
"cancelled": {
"body": "Your withdrawal of {{amount}} has been cancelled.",
"title": "Withdrawal cancelled"
Expand Down
3 changes: 2 additions & 1 deletion src/config/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"title": "Depósito recibido"
},
"bridgeWithdrawal": {
"flashFeeNotice": "Las comisiones y montos mostrados son estimados. Las comisiones finales pueden variar.",
"cancelled": {
"body": "Su retiro de {{amount}} ha sido cancelado.",
"title": "Retiro cancelado"
Expand All @@ -57,4 +58,4 @@
}
}
}
}
}
22 changes: 21 additions & 1 deletion src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,26 @@ export const configSchema = {
apiKey: { type: "string" },
baseUrl: { type: "string" },
minWithdrawalAmount: { type: "number" },
developerFeePercent: { type: "number" },
withdrawalFeeEstimate: {
type: "object",
properties: {
bridgeFixedFeePercent: { type: "number", default: 0.6 },
usdtTransferGasLimit: { type: "integer", default: 65000 },
gasPriceBufferMultiplier: { type: "number", default: 1.5 },
ethereumGasRpcUrls: {
type: "array",
items: { type: "string" },
default: [
"https://ethereum-rpc.publicnode.com",
"https://eth.llamarpc.com",
"https://cloudflare-eth.com",
],
},
fallbackGasPriceGwei: { type: "number", default: 30 },
ethUsdFallback: { type: "number", default: 3000 },
},
},
timeoutMs: { type: "integer", default: 10000 },
webhook: {
type: "object",
Expand All @@ -670,7 +690,7 @@ export const configSchema = {
required: ["port", "publicKeys", "timestampSkewMs"],
},
},
required: ["enabled", "apiKey", "baseUrl", "webhook"],
required: ["enabled", "apiKey", "baseUrl", "developerFeePercent", "webhook"],
},
exchangeRates: {
type: "object",
Expand Down
11 changes: 11 additions & 0 deletions src/config/schema.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,22 @@ type BridgeWebhook = {
replaySecret?: string
}

type BridgeWithdrawalFeeEstimateConfig = {
bridgeFixedFeePercent?: number
usdtTransferGasLimit?: number
gasPriceBufferMultiplier?: number
ethereumGasRpcUrls?: string[]
fallbackGasPriceGwei?: number
ethUsdFallback?: number
}

type BridgeConfig = {
enabled: boolean
apiKey: string
baseUrl: string
minWithdrawalAmount: number
developerFeePercent: number
withdrawalFeeEstimate?: BridgeWithdrawalFeeEstimateConfig
timeoutMs?: number
webhook: BridgeWebhook
}
Expand Down
12 changes: 2 additions & 10 deletions src/graphql/public/root/query/bridge-withdrawal-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BridgeWithdrawal from "@graphql/public/types/object/bridge-withdrawal"
import { BridgeConfig } from "@config"
import { BridgeDisabledError } from "@services/bridge/errors"
import * as BridgeAccountsRepo from "@services/mongoose/bridge-accounts"
import { presentBridgeWithdrawal } from "@services/bridge/withdrawal-fees"
import { RepositoryError } from "@domain/errors"

const bridgeWithdrawalRequest = GT.Field({
Expand All @@ -24,16 +25,7 @@ const bridgeWithdrawalRequest = GT.Field({
// Ownership check — never expose another account's withdrawal
if (withdrawal.accountId !== (domainAccount.id as string)) return null

return {
id: withdrawal.id,
amount: withdrawal.amount,
currency: withdrawal.currency,
externalAccountId: withdrawal.externalAccountId,
status: withdrawal.status,
bridgeTransferId: withdrawal.bridgeTransferId,
failureReason: withdrawal.failureReason,
createdAt: withdrawal.createdAt.toISOString(),
}
return presentBridgeWithdrawal(withdrawal)
},
})

Expand Down
12 changes: 12 additions & 0 deletions src/graphql/public/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,25 @@ type BridgeVirtualAccount {

type BridgeWithdrawal {
amount: String!
bridgeDeveloperFee: String
bridgeExchangeFee: String
bridgeTransferId: String
createdAt: String!
currency: String!
externalAccountId: String
failureReason: String
finalAmount: String
estimatedBridgeFee: String
estimatedBridgeFeePercent: String
estimatedCustomerFee: String
estimatedGasBuffer: String
flashFee: String
flashFeeIsEstimate: Boolean!
flashFeeNotice: String
flashFeePercent: String
id: ID!
status: String!
subtotalAmount: String
}

type BuildInformation {
Expand Down
22 changes: 22 additions & 0 deletions src/graphql/public/types/object/bridge-withdrawal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { GT } from "@graphql/index"
import { getBridgeWithdrawalFlashFeeNoticeForUser } from "@app/bridge/get-withdrawal-flash-fee-notice"
import { isFlashFeeEstimate } from "@services/bridge/withdrawal-fees"

const BridgeWithdrawal = GT.Object({
name: "BridgeWithdrawal",
Expand All @@ -8,6 +10,26 @@ const BridgeWithdrawal = GT.Object({
currency: { type: GT.NonNull(GT.String) },
externalAccountId: { type: GT.String },
status: { type: GT.NonNull(GT.String) },
estimatedBridgeFeePercent: { type: GT.String },
estimatedBridgeFee: { type: GT.String },
estimatedGasBuffer: { type: GT.String },
estimatedCustomerFee: { type: GT.String },
flashFeePercent: { type: GT.String },
flashFee: { type: GT.String },
flashFeeIsEstimate: { type: GT.NonNull(GT.Boolean) },
flashFeeNotice: {
type: GT.String,
resolve: (parent, _, { user }: GraphQLPublicContext) => {
const isEstimate =
parent.flashFeeIsEstimate === true || isFlashFeeEstimate(parent)
if (!isEstimate) return null
return getBridgeWithdrawalFlashFeeNoticeForUser(user)
},
},
bridgeDeveloperFee: { type: GT.String },
bridgeExchangeFee: { type: GT.String },
subtotalAmount: { type: GT.String },
finalAmount: { type: GT.String },
bridgeTransferId: { type: GT.String },
failureReason: { type: GT.String },
createdAt: { type: GT.NonNull(GT.String) },
Expand Down
14 changes: 13 additions & 1 deletion src/services/bridge/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,19 @@ export interface Transfer {
bank_routing_number?: string
to_address?: string
}
receipt?: Record<string, unknown>
receipt?: {
initial_amount: string
developer_fee: string
exchange_fee: string
subtotal_amount: string
gas_fee?: string
final_amount?: string
exchange_rate?: string
source_tx_hash?: string
destination_tx_hash?: string
remaining_prefunded_balance?: string
url?: string
}
created_at: string
updated_at: string
}
Expand Down
Loading