-
Notifications
You must be signed in to change notification settings - Fork 62
Add core-side synthetic destination for swap-to-address #724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,9 @@ import { | |
| EdgeSwapRequestOptions | ||
| } from '../../types/types' | ||
| import { fuzzyTimeout, timeout } from '../../util/promise' | ||
| import { CurrencyConfig } from '../account/plugin-api' | ||
| import { ApiInput } from '../root-pixie' | ||
| import { makeSyntheticDestinationWallet } from './synthetic-wallet' | ||
|
|
||
| /** | ||
| * Fetch quotes from all plugins, and sorts the best ones to the front. | ||
|
|
@@ -41,12 +43,23 @@ export async function fetchSwapQuotes( | |
| const { swapSettings, userSettings } = account | ||
| const swapPlugins = state.plugins.swap | ||
|
|
||
| // Resolve the destination. A normal swap provides `toWallet`; a | ||
| // swap-to-address (private send) request provides `toAddressInfo` instead, and | ||
| // the core builds a synthetic destination wallet from it so plugins receive an | ||
| // `EdgeCurrencyWallet` unchanged. | ||
| const swapRequest = resolveSwapRequest(ai, accountId, request) | ||
|
|
||
| log.warn( | ||
| 'Requesting swap quotes for: ', | ||
| { | ||
| ...request, | ||
| fromWallet: request.fromWallet.id, | ||
| toWallet: request.toWallet.id | ||
| ...swapRequest, | ||
| fromWallet: swapRequest.fromWallet.id, | ||
| toWallet: swapRequest.toWallet?.id, | ||
| // Never log the pasted destination address (private-send privacy): | ||
| toAddressInfo: | ||
| swapRequest.toAddressInfo == null | ||
| ? undefined | ||
| : { ...swapRequest.toAddressInfo, toAddress: '[redacted]' } | ||
| }, | ||
| { preferPluginId, promoCodes } | ||
| ) | ||
|
|
@@ -63,14 +76,23 @@ export async function fetchSwapQuotes( | |
| pendingIds.add(pluginId) | ||
| promises.push( | ||
| swapPlugins[pluginId] | ||
| .fetchSwapQuote(request, userSettings[pluginId], { | ||
| .fetchSwapQuote(swapRequest, userSettings[pluginId], { | ||
|
j0ntz marked this conversation as resolved.
|
||
| infoPayload: state.infoCache.corePlugins?.[pluginId] ?? {}, | ||
| promoCode: promoCodes[pluginId] | ||
| }) | ||
| .then( | ||
| quote => { | ||
| upgradeSwapQuote(quote) | ||
| const { fromWallet, toWallet, ...request } = quote.request ?? {} | ||
| const { fromWallet, toWallet, toAddressInfo, ...rest } = | ||
| quote.request ?? {} | ||
| // Never log the pasted destination address (private-send privacy): | ||
| const request = | ||
| toAddressInfo == null | ||
| ? rest | ||
| : { | ||
| ...rest, | ||
| toAddressInfo: { ...toAddressInfo, toAddress: '[redacted]' } | ||
| } | ||
| const cleaned = { ...quote, request } | ||
| pendingIds.delete(pluginId) | ||
| log.warn(`${pluginId} gave swap quote:`, cleaned) | ||
|
|
@@ -86,12 +108,12 @@ export async function fetchSwapQuotes( | |
| swapPluginId: pluginId, | ||
| request: { | ||
| // Stringify to include "null" | ||
| fromToken: String(request.fromTokenId), | ||
| fromWalletType: request.fromWallet.type, | ||
| fromToken: String(swapRequest.fromTokenId), | ||
| fromWalletType: swapRequest.fromWallet.type, | ||
| // Stringify to include "null" | ||
| toToken: String(request.toTokenId), | ||
| toWalletType: request.toWallet.type, | ||
| quoteFor: request.quoteFor | ||
| toToken: String(swapRequest.toTokenId), | ||
| toWalletType: swapRequest.toWallet?.type, | ||
| quoteFor: swapRequest.quoteFor | ||
| } | ||
| }) | ||
| } | ||
|
|
@@ -117,7 +139,7 @@ export async function fetchSwapQuotes( | |
| ) | ||
|
|
||
| // Prepare quotes for the bridge: | ||
| return quotes.map(quote => wrapQuote(swapPlugins, request, quote)) | ||
| return quotes.map(quote => wrapQuote(swapPlugins, swapRequest, quote)) | ||
| }, | ||
| (errors: unknown[]) => { | ||
| log.warn(`All ${promises.length} swap quotes rejected.`) | ||
|
|
@@ -129,6 +151,46 @@ export async function fetchSwapQuotes( | |
| return await timeout(promise, noResponseMs) | ||
| } | ||
|
|
||
| /** | ||
| * Validates the destination on a swap request and resolves it to a request that | ||
| * always carries a `toWallet`. Exactly one of `toWallet` or `toAddressInfo` must | ||
| * be present; when it is `toAddressInfo`, a synthetic destination wallet is built | ||
| * core-side from the descriptor. | ||
| */ | ||
| function resolveSwapRequest( | ||
| ai: ApiInput, | ||
| accountId: string, | ||
| request: EdgeSwapRequest | ||
| ): EdgeSwapRequest { | ||
| const { toWallet, toAddressInfo } = request | ||
|
|
||
| if ((toWallet == null) === (toAddressInfo == null)) { | ||
| throw new Error( | ||
| 'Swap request must include exactly one of `toWallet` or `toAddressInfo`' | ||
| ) | ||
| } | ||
| if (toAddressInfo == null) return request | ||
|
|
||
| const { toPluginId, toAddress } = toAddressInfo | ||
| const { toTokenId } = request | ||
| if (ai.props.state.plugins.currency[toPluginId] == null) { | ||
| throw new Error( | ||
| `Cannot build swap destination: no currency plugin "${toPluginId}"` | ||
| ) | ||
| } | ||
| const currencyConfig = new CurrencyConfig(ai, accountId, toPluginId) | ||
| if (toTokenId != null && currencyConfig.allTokens[toTokenId] == null) { | ||
| throw new Error( | ||
| `Cannot build swap destination: no token "${toTokenId}" on plugin "${toPluginId}"` | ||
| ) | ||
| } | ||
|
|
||
| return { | ||
| ...request, | ||
| toWallet: makeSyntheticDestinationWallet(currencyConfig, toAddress) | ||
| } | ||
|
j0ntz marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved swap request breaks re-quoteMedium Severity When Reviewed by Cursor Bugbot for commit bfcfcbb. Configure here. |
||
| } | ||
|
|
||
| function wrapQuote( | ||
| swapPlugins: EdgePluginMap<EdgeSwapPlugin>, | ||
| request: EdgeSwapRequest, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import { bridgifyObject } from 'yaob' | ||
|
|
||
| import { | ||
| EdgeAddress, | ||
| EdgeCurrencyConfig, | ||
| EdgeCurrencyWallet, | ||
| EdgeReceiveAddress | ||
| } from '../../types/types' | ||
|
|
||
| /** | ||
| * A prefix marking a synthetic destination wallet's `id`. A swap-to-address | ||
| * destination has no real payout wallet, so this id does not resolve to one; | ||
| * it only exists because plugins read `toWallet.id` for order metadata. | ||
| */ | ||
| export const SYNTHETIC_WALLET_ID_PREFIX = 'synthetic://' | ||
|
|
||
| /** | ||
| * Build a synthetic, bridgified destination wallet for a swap-to-address | ||
| * request. It is backed by the real `currencyConfig` core already holds, so | ||
| * `currencyInfo` and `currencyConfig.allTokens` are authentic, while | ||
| * `getAddresses` / `getReceiveAddress` return the pasted destination address. | ||
| * | ||
| * It is bridgified here (core-side) so swap-plugin method calls work unchanged | ||
| * and so it survives the yaob wire format when it rides back to the GUI inside | ||
| * `quote.request.toWallet`. A GUI-built fake cannot do this: its function | ||
| * properties fail to cross the bridge (see the Phase 1 verdict). | ||
| */ | ||
| export function makeSyntheticDestinationWallet( | ||
| currencyConfig: EdgeCurrencyConfig, | ||
| toAddress: string | ||
| ): EdgeCurrencyWallet { | ||
| const { currencyInfo } = currencyConfig | ||
|
|
||
| const addresses: EdgeAddress[] = [ | ||
| { addressType: 'publicAddress', publicAddress: toAddress } | ||
| ] | ||
| const receiveAddress: EdgeReceiveAddress = { | ||
| publicAddress: toAddress, | ||
| metadata: {}, | ||
| nativeAmount: '0' | ||
| } | ||
|
|
||
| const wallet = { | ||
| id: `${SYNTHETIC_WALLET_ID_PREFIX}${currencyInfo.pluginId}`, | ||
| type: currencyInfo.walletType, | ||
| currencyConfig, | ||
| currencyInfo, | ||
|
|
||
| async getAddresses(): Promise<EdgeAddress[]> { | ||
| return addresses | ||
| }, | ||
|
|
||
| async getReceiveAddress(): Promise<EdgeReceiveAddress> { | ||
| return receiveAddress | ||
| } | ||
| } | ||
| bridgifyObject(wallet) | ||
|
|
||
| // The synthetic destination only implements the `EdgeCurrencyWallet` surface | ||
| // that swap plugins read on `toWallet` (id, type, currencyInfo, | ||
| // currencyConfig, getAddresses, getReceiveAddress). It is never used as a | ||
| // source wallet, so the spend/sign/sync methods are intentionally absent. | ||
| return wallet as unknown as EdgeCurrencyWallet | ||
| } |


Uh oh!
There was an error while loading. Please reload this page.