Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
623401f
ENG-297 feat(bridge): add core Bridge integration
forge0x May 13, 2026
9714626
ENG-278 ENG-280 ENG-281 ENG-282 ENG-283 ENG-284 ENG-285 ENG-349 ENG-3…
forge0x May 13, 2026
05739f5
ENG-276 feat(bridge): add reconciliation and replay tooling
forge0x May 13, 2026
1818b7c
ENG-394 feat(accounts): create ETH-USDT Cash Wallet for new accounts
forge0x May 13, 2026
2096387
ENG-297 docs: document Bridge rebase PR-readiness plan
forge0x May 13, 2026
18f1ac7
ENG-376 fix(bridge): reuse pending withdrawal idempotency rows
forge0x May 13, 2026
dd75901
ENG-297 chore(bridge): satisfy scoped lint on branch changes
forge0x May 13, 2026
c8569de
ENG-297 fix(graphql): add isExternal to UsdtWallet
forge0x May 13, 2026
f01c0ad
fix(graphql): preserve latest account upgrade request query
forge0x May 15, 2026
0fe5263
feat: bridge reconciliation + USDT provisioning additions (#354)
heyolaniran May 16, 2026
bab26d8
ENG-297 - IBEX USD -> USDT Parity (#355)
islandbitcoin May 19, 2026
6e15c4c
fix(graphql): serialize USDT fee probe amounts (#358)
forge0x May 19, 2026
e2fb12e
Feat/bridge reconciliation (#360)
heyolaniran May 21, 2026
91c22d3
fix: expose usdt amounts as usd cents
forge0x May 29, 2026
836405e
fix: keep usdt cent boundaries explicit
forge0x May 29, 2026
b1af168
feat(cutover): prepare cash wallet migration for bridge (#377)
forge0x Jun 1, 2026
f405524
fix(bridge): restore cutover repository build
forge0x Jun 2, 2026
79d764f
fix(cutover): preserve cash wallet history after cutover (#380)
forge0x Jun 2, 2026
5c1b954
fix(wallets): preserve USDT transaction history precision (#382)
forge0x Jun 3, 2026
886b207
feat(bridge): external account webhook + ENG-350 transfer failure res…
heyolaniran Jun 3, 2026
f5c9077
test(bridge): verify webhook signature contract (#383)
patoo0x Jun 3, 2026
851c1ca
fix(cash-wallet): guard cutover start
patoo0x Jun 3, 2026
f1d5f10
feat: add BridgeTransferRequest ERPNext audit model and webhook wirin…
patoo0x Jun 4, 2026
563153b
fix(bridge): lower account level requirement from level 2 to level > …
heyolaniran Jun 4, 2026
6686332
fix(bridge): correct replay tooling for Bridge webhook_events API (#381)
heyolaniran Jun 4, 2026
cafd2d1
fix(bridge): add external account webhook config (#390)
patoo0x Jun 6, 2026
0964448
fix(bridge): align GraphQL contract and error codes (#393)
patoo0x Jun 6, 2026
9633256
fix(ibex): wire USDT LNURL-pay msat conversion (#389)
patoo0x Jun 7, 2026
ca878ae
feat(bridge): push notification on deposit settlement [ENG-275] (#392)
islandbitcoin Jun 7, 2026
a3acbc1
feat(bridge): split withdrawal into request, confirm, and cancel step…
heyolaniran Jun 8, 2026
7e321f2
feat(bridge): add KYC tier ceiling error code (ENG-354) (#394)
patoo0x Jun 8, 2026
c6db7d6
feat(alerts): Bridge alerting to PagerDuty / Slack / Discord [ENG-361…
islandbitcoin Jun 10, 2026
a23c9fd
feat(cashout): route Cashout V1 wallets via cutover guard (ENG-357) (…
islandbitcoin Jun 10, 2026
8532284
chore(bridge): add sandbox e2e test suite (#388)
patoo0x Jun 11, 2026
c067119
chore(bridge): add refreshed ERPNext dev backup (#400)
patoo0x Jun 11, 2026
f4ee0de
improve backup and restore scripts, updated to latest frappe backup
forge0x Jun 15, 2026
3d1a026
fix(dev): make local Frappe startup repair frontend
forge0x Jun 16, 2026
1967135
fix(bridge): resolve type errors in bridge-sandbox-e2e suite (#409)
islandbitcoin Jun 17, 2026
dbfdae0
fix(bridge): remove leaked sandbox API key from base config (#412)
islandbitcoin Jun 18, 2026
8c508cf
Normalize Bridge webhook payload envelopes (#406)
islandbitcoin Jun 18, 2026
340c9cb
Map IBEX crypto sends as USDT on-chain transactions (#405)
islandbitcoin Jun 18, 2026
e8ddd62
feat(bridge): withdrawal fee estimates — request/confirm/cancel flow …
heyolaniran Jun 18, 2026
5b4d6bd
Send Bridge cashout USDT to transfer deposit addresses (#407)
islandbitcoin Jun 19, 2026
979f9db
merge: resolve conflicts with origin/main (PR #413)
forge0x Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ junit.xml
.yarnrc.yml

docker-compose.local.yml

# Planning scratch files
findings.md
progress.md
task_plan.md
21 changes: 21 additions & 0 deletions DEV.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,27 @@ make stop-frappe # stop local Frappe

Update your `dev-overrides.yaml` with local Frappe credentials if running locally.

### Admin Panel API URL

The Frappe Admin Panel uses the Frappe site config key `flash_admin_api_url` to
call the Flash admin GraphQL API for pages such as `alert-users`.

- **Local Docker Frappe:** use `http://host.docker.internal:4001/graphql` so the
Frappe container can reach the backend admin GraphQL server running on the
host machine. Compose maps that hostname with Docker's symbolic
`host-gateway`, and the value can be overridden locally with
`FRAPPE_FLASH_API` if a developer's Docker runtime needs a different host
route.
- **Test / production Kubernetes:** do **not** use `host.docker.internal`. Set
`flash_admin_api_url` from the deployment config to the environment-specific
Kubernetes-reachable admin GraphQL endpoint, such as an internal service DNS
name or routed internal gateway URL. The endpoint should route to the admin
GraphQL server's `/graphql` path unless that environment explicitly rewrites a
different path.

If a local full Frappe backup includes `site_config_backup.json`, treat this key
as environment-specific and override it during deployment restore.

---

## Testing
Expand Down
259 changes: 259 additions & 0 deletions dev/apollo-federation/supergraph.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,159 @@ input BankAccountInput
currency: String!
}

type BridgeAddExternalAccountPayload
@join__type(graph: PUBLIC)
{
errors: [Error!]!
externalAccount: BridgeExternalAccountLink
}

input BridgeCancelWithdrawalRequestInput
@join__type(graph: PUBLIC)
{
withdrawalId: ID!
}

type BridgeCancelWithdrawalRequestPayload
@join__type(graph: PUBLIC)
{
errors: [Error!]!
withdrawal: BridgeWithdrawal
}

input BridgeCreateExternalAccountInput
@join__type(graph: PUBLIC)
{
accountNumber: String!
accountOwnerName: String!
bankName: String!
checkingOrSavings: String = "checking"
city: String!
country: String!
postalCode: String!
routingNumber: String!
state: String!
streetLine1: String!
}

type BridgeCreateExternalAccountPayload
@join__type(graph: PUBLIC)
{
errors: [Error!]!
externalAccount: BridgeExternalAccount
}

type BridgeCreateVirtualAccountPayload
@join__type(graph: PUBLIC)
{
errors: [Error!]!
virtualAccount: BridgeVirtualAccount
}

type BridgeExternalAccount
@join__type(graph: PUBLIC)
{
accountNumberLast4: String!
bankName: String!
id: ID!
status: String!
}

type BridgeExternalAccountLink
@join__type(graph: PUBLIC)
{
expiresAt: String!
linkUrl: String!
}

input BridgeInitiateKycInput
@join__type(graph: PUBLIC)
{
email: String
full_name: String
type: String
}

type BridgeInitiateKycPayload
@join__type(graph: PUBLIC)
{
errors: [Error!]!
kycLink: BridgeKycLink
}

input BridgeInitiateWithdrawalInput
@join__type(graph: PUBLIC)
{
withdrawalId: ID!
}

type BridgeInitiateWithdrawalPayload
@join__type(graph: PUBLIC)
{
errors: [Error!]!
withdrawal: BridgeWithdrawal
}

type BridgeKycLink
@join__type(graph: PUBLIC)
{
kycLink: String!
tosLink: String!
}

input BridgeRequestWithdrawalInput
@join__type(graph: PUBLIC)
{
amount: String!
externalAccountId: ID!
}

type BridgeRequestWithdrawalPayload
@join__type(graph: PUBLIC)
{
errors: [Error!]!
withdrawal: BridgeWithdrawal
}

type BridgeVirtualAccount
@join__type(graph: PUBLIC)
{
accountNumber: String
accountNumberLast4: String
bankName: String
id: ID
kycLink: String
message: String
pending: Boolean
routingNumber: String
tosLink: String
}

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

"""
A wallet belonging to an account which contains a BTC balance and a list of transactions.
"""
Expand Down Expand Up @@ -418,6 +571,29 @@ type CashoutOffer
walletId: WalletId!
}

type CashWalletCutover
@join__type(graph: PUBLIC)
{
completedAt: Timestamp
cutoverVersion: Int!
pauseReason: String
pausedAt: Timestamp
runId: String
scheduledAt: Timestamp
startedAt: Timestamp
state: CashWalletCutoverState!
updatedAt: Timestamp!
updatedBy: String
}

enum CashWalletCutoverState
@join__type(graph: PUBLIC)
{
COMPLETE @join__enumValue(graph: PUBLIC)
IN_PROGRESS @join__enumValue(graph: PUBLIC)
PRE @join__enumValue(graph: PUBLIC)
}

"""(Positive) Cent amount (1/100 of a dollar)"""
scalar CentAmount
@join__type(graph: PUBLIC)
Expand Down Expand Up @@ -985,6 +1161,22 @@ A bech32-encoded HTTPS/Onion URL that can be interacted with automatically by a
scalar Lnurl
@join__type(graph: PUBLIC)

input LnurlPaymentSendInput
@join__type(graph: PUBLIC)
{
"""Amount to spend from the USD/USDT wallet, in USD cents."""
amount: FractionalCentAmount!

"""LNURL-pay value to decode and pay."""
lnurl: Lnurl!

"""Optional memo for the Lightning payment."""
memo: Memo

"""Wallet ID with sufficient balance. Must belong to the current user."""
walletId: WalletId!
}

input LnUsdInvoiceCreateInput
@join__type(graph: PUBLIC)
{
Expand Down Expand Up @@ -1101,6 +1293,13 @@ type Mutation
accountEnableNotificationChannel(input: AccountEnableNotificationChannelInput!): AccountUpdateNotificationSettingsPayload!
accountUpdateDefaultWalletId(input: AccountUpdateDefaultWalletIdInput!): AccountUpdateDefaultWalletIdPayload!
accountUpdateDisplayCurrency(input: AccountUpdateDisplayCurrencyInput!): AccountUpdateDisplayCurrencyPayload!
bridgeAddExternalAccount: BridgeAddExternalAccountPayload!
bridgeCancelWithdrawalRequest(input: BridgeCancelWithdrawalRequestInput!): BridgeCancelWithdrawalRequestPayload!
bridgeCreateExternalAccount(input: BridgeCreateExternalAccountInput!): BridgeCreateExternalAccountPayload!
bridgeCreateVirtualAccount: BridgeCreateVirtualAccountPayload!
bridgeInitiateKyc(input: BridgeInitiateKycInput!): BridgeInitiateKycPayload!
bridgeInitiateWithdrawal(input: BridgeInitiateWithdrawalInput!): BridgeInitiateWithdrawalPayload!
bridgeRequestWithdrawal(input: BridgeRequestWithdrawalInput!): BridgeRequestWithdrawalPayload!
businessAccountUpgradeRequest(input: BusinessAccountUpgradeRequestInput!): AccountUpgradePayload!
callbackEndpointAdd(input: CallbackEndpointAddInput!): CallbackEndpointAddPayload!
callbackEndpointDelete(input: CallbackEndpointDeleteInput!): SuccessPayload!
Expand Down Expand Up @@ -1200,6 +1399,12 @@ type Mutation
"""
lnUsdInvoiceCreateOnBehalfOfRecipient(input: LnUsdInvoiceCreateOnBehalfOfRecipientInput!): LnInvoicePayload!
lnUsdInvoiceFeeProbe(input: LnUsdInvoiceFeeProbeInput!): CentAmountPayload!

"""
Pay a LNURL-pay endpoint using a USD/USDT wallet balance.
The wallet amount is converted to whole-satoshi millisatoshis before calling IBEX.
"""
lnurlPaymentSend(input: LnurlPaymentSendInput!): PaymentSendPayload!
merchantMapSuggest(input: MerchantMapSuggestInput!): MerchantPayload!
onChainAddressCreate(input: OnChainAddressCreateInput!): OnChainAddressPayload!
onChainAddressCurrent(input: OnChainAddressCurrentInput!): OnChainAddressPayload!
Expand Down Expand Up @@ -1557,9 +1762,15 @@ type Query
@join__type(graph: PUBLIC)
{
accountDefaultWallet(username: Username!, walletCurrency: WalletCurrency): PublicWallet!
bridgeExternalAccounts: [BridgeExternalAccount]
bridgeKycStatus: String
bridgeVirtualAccount: BridgeVirtualAccount
bridgeWithdrawalRequest(id: ID!): BridgeWithdrawal
bridgeWithdrawals: [BridgeWithdrawal]
btcPrice(currency: DisplayCurrency! = "USD"): Price @deprecated(reason: "Deprecated in favor of realtimePrice")
btcPriceList(range: PriceGraphRange!): [PricePoint]
businessMapMarkers: [MapMarker!]!
cashWalletCutover: CashWalletCutover!
currencyList: [Currency!]!
globals: Globals
isFlashNpub(input: IsFlashNpubInput!): IsFlashNpubPayload
Expand Down Expand Up @@ -1940,6 +2151,53 @@ type UpgradePayload
scalar USDCents
@join__type(graph: PUBLIC)

"""
A wallet belonging to an account which contains a USDT balance and a list of transactions.
"""
type UsdtWallet implements Wallet
@join__implements(graph: PUBLIC, interface: "Wallet")
@join__type(graph: PUBLIC)
{
accountId: ID!
balance: FractionalCentAmount
id: ID!
isExternal: Boolean!
lnurlp: Lnurl

"""An unconfirmed incoming onchain balance."""
pendingIncomingBalance: SignedAmount!
transactions(
"""Returns the items in the list that come after the specified cursor."""
after: String

"""Returns the items in the list that come before the specified cursor."""
before: String

"""Returns the first n items from the list."""
first: Int

"""Returns the last n items from the list."""
last: Int
): TransactionConnection
transactionsByAddress(
"""Returns the items that include this address."""
address: OnChainAddress!

"""Returns the items in the list that come after the specified cursor."""
after: String

"""Returns the items in the list that come before the specified cursor."""
before: String

"""Returns the first n items from the list."""
first: Int

"""Returns the last n items from the list."""
last: Int
): TransactionConnection
walletCurrency: WalletCurrency!
}

"""
A wallet belonging to an account which contains a USD balance and a list of transactions.
"""
Expand Down Expand Up @@ -2333,6 +2591,7 @@ enum WalletCurrency
{
BTC @join__enumValue(graph: PUBLIC)
USD @join__enumValue(graph: PUBLIC)
USDT @join__enumValue(graph: PUBLIC)
}

"""Unique identifier of a wallet"""
Expand Down
Loading
Loading