Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

- added: `EdgeCurrencyConfig.encodePayLink`
- added: `EdgeCurrencyConfig.parseLink`
- deprecated: `EdgeCurrencyWallet.encodeUri`. Use `EdgeCurrencyConfig.encodePayLink`
- deprecated: `EdgeCurrencyWallet.parseUri`. Use `EdgeCurrencyConfig.parseLink`
- fixed: Avoid deprecated Gradle syntax.

## 2.34.0 (2025-08-25)
Expand Down
61 changes: 60 additions & 1 deletion src/core/account/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import {
EdgeCurrencyInfo,
EdgeGetTokenDetailsFilter,
EdgeOtherMethods,
EdgeParsedLink,
EdgePayLink,
EdgeSwapConfig,
EdgeSwapInfo,
EdgeToken,
EdgeTokenId,
EdgeTokenMap
} from '../../types/types'
import { parsedUriToLink } from '../currency/uri-tools'
import { uniqueStrings } from '../currency/wallet/enabled-tokens'
import { getCurrencyTools } from '../plugins/plugins-selectors'
import { ApiInput } from '../root-pixie'
import { changePluginUserSettings, changeSwapSettings } from './account-files'
import { getTokenId } from './custom-tokens'
import { getTokenId, makeMetaTokens } from './custom-tokens'

const emptyTokens: EdgeTokenMap = {}
const emptyTokenIds: string[] = []
Expand Down Expand Up @@ -186,6 +190,54 @@ export class CurrencyConfig
)
}

async parseLink(
link: string,
opts: { tokenId?: EdgeTokenId } = {}
): Promise<EdgeParsedLink> {
const { tokenId } = opts
const { allTokens } = this
const tools = await getCurrencyTools(this._ai, this._pluginId)

if (tools.parseLink != null) {
return await tools.parseLink(link, { tokenId, allTokens })
}

// Fallback version:
if (tools.parseUri != null) {
const out = await tools.parseUri(
link,
this.downgradeTokenId(tokenId),
makeMetaTokens(this.customTokens)
)
return parsedUriToLink(out, this.currencyInfo, allTokens)
}

return {}
}

async encodePayLink(link: EdgePayLink): Promise<string> {
const { tokenId } = link
const { allTokens } = this
const tools = await getCurrencyTools(this._ai, this._pluginId)

if (tools.encodePayLink != null) {
return await tools.encodePayLink(link, { allTokens })
}

// Fallback version:
if (tools.encodeUri != null) {
return await tools.encodeUri(
{
...link,
currencyCode: this.downgradeTokenId(tokenId)
},
makeMetaTokens(this.customTokens)
)
}

return ''
}

async importKey(
userInput: string,
opts: { keyOptions?: object } = {}
Expand All @@ -198,6 +250,13 @@ export class CurrencyConfig
const keys = await tools.importPrivateKey(userInput, opts.keyOptions)
return { ...keys, imported: true }
}

private downgradeTokenId(tokenId?: EdgeTokenId): string | undefined {
if (tokenId === undefined) return
return tokenId == null
? this.currencyInfo.currencyCode
: this.allTokens[tokenId]?.currencyCode
}
}

export class SwapConfig extends Bridgeable<EdgeSwapConfig> {
Expand Down
163 changes: 163 additions & 0 deletions src/core/currency/uri-tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import {
EdgeCurrencyInfo,
EdgeParsedLink,
EdgeParsedUri,
EdgeTokenMap
} from '../../types/types'
import { makeMetaToken } from '../account/custom-tokens'

export function parsedUriToLink(
uri: EdgeParsedUri,
currencyInfo: EdgeCurrencyInfo,
allTokens: EdgeTokenMap
): EdgeParsedLink {
const {
// Edge has never supported BitID:
// bitIDCallbackUri,
// bitIDDomain,
// bitidKycProvider, // Experimental
// bitidKycRequest, // Experimental
// bitidPaymentAddress, // Experimental
// bitIDURI,

// The GUI handles address requests:
// returnUri,

currencyCode,
expireDate,
legacyAddress,
metadata,
minNativeAmount,
nativeAmount,
paymentProtocolUrl,
privateKeys,
publicAddress,
segwitAddress,
token,
uniqueIdentifier,
walletConnect
} = uri
let { tokenId } = uri

if (tokenId === undefined && currencyCode != null) {
tokenId =
currencyCode === currencyInfo.currencyCode
? null
: Object.keys(allTokens).find(
tokenId => allTokens[tokenId].currencyCode === currencyCode
)
}

const out: EdgeParsedLink = {}

// Payment addresses:
if (publicAddress != null) {
out.pay = {
publicAddress: legacyAddress ?? publicAddress ?? segwitAddress,
addressType:
legacyAddress != null
? 'legacyAddress'
: publicAddress != null
? 'publicAddress'
: 'segwitAddress',
label: metadata?.name,
message: metadata?.notes,
memo: uniqueIdentifier,
memoType: 'text',
nativeAmount: nativeAmount,
minNativeAmount: minNativeAmount,
tokenId: tokenId,
expires: expireDate,
isGateway: (metadata as any)?.gateway // Undocumented feature
}
}
Copy link

Choose a reason for hiding this comment

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

Bug: Payment Links Fail for Non-Public Addresses

The parsedUriToLink function only creates payment links if publicAddress is present. This misses cases where a URI only provides legacyAddress or segwitAddress, even though the internal logic can handle these address types.

Fix in Cursor Fix in Web


if (paymentProtocolUrl != null) {
out.paymentProtocol = { paymentProtocolUrl }
}

// Private keys:
if (privateKeys != null && privateKeys.length > 0) {
out.privateKey = { privateKey: privateKeys[0] }
}

// Custom tokens:
if (token != null) {
const { contractAddress, currencyCode, currencyName, denominations } = token
out.token = {
currencyCode,
denominations,
displayName: currencyName,
networkLocation: {
contractAddress,
type: (token as any).type // Undocumented EVM token type
}
}
}

if (walletConnect != null) {
out.walletConnect = walletConnect
}

return out
}

export function linkToParsedUri(link: EdgeParsedLink): EdgeParsedUri {
const out: EdgeParsedUri = {}

// Payment addresses:
if (link.pay != null) {
const {
publicAddress,
addressType,
label,
message,
memo,
nativeAmount,
minNativeAmount,
tokenId,
expires,
isGateway
} = link.pay
out.publicAddress = publicAddress
if (addressType === 'legacyAddress') out.legacyAddress = publicAddress
if (addressType === 'segwitAddress') out.segwitAddress = publicAddress
out.metadata = {
name: label,
notes: message,
// @ts-expect-error Undocumented feature:
gateway: isGateway
}
out.uniqueIdentifier = memo
out.nativeAmount = nativeAmount
out.minNativeAmount = minNativeAmount
out.tokenId = tokenId
out.expireDate = expires
;(out as any).gateway = isGateway
}

// Payment protocol:
if (link.paymentProtocol != null) {
const { paymentProtocolUrl } = link.paymentProtocol
out.paymentProtocolUrl = paymentProtocolUrl
}

// Private keys:
if (link.privateKey != null) {
const { privateKey } = link.privateKey
out.privateKeys = [privateKey]
}

// Custom tokens:
if (link.token != null) {
out.token = makeMetaToken(link.token)
// @ts-expect-error Undocumented "ERC20" field:
out.token.type = link.token.networkLocation.type
}

if (link.walletConnect != null) {
out.walletConnect = link.walletConnect
}

return out
}
72 changes: 55 additions & 17 deletions src/core/currency/wallet/currency-wallet-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { makeMetaTokens } from '../../account/custom-tokens'
import { toApiInput } from '../../root-pixie'
import { makeStorageWalletApi } from '../../storage/storage-api'
import { getCurrencyMultiplier } from '../currency-selectors'
import { linkToParsedUri } from '../uri-tools'
import { makeCurrencyWalletCallbacks } from './currency-wallet-callbacks'
import {
asEdgeAssetAction,
Expand Down Expand Up @@ -724,31 +725,68 @@ export function makeCurrencyWalletApi(

// URI handling:
async encodeUri(options: EdgeEncodeUri): Promise<string> {
return await tools.encodeUri(
options,
makeMetaTokens(
input.props.state.accounts[accountId].customTokens[pluginId]
const allTokens =
input.props.state.accounts[accountId].allTokens[pluginId]

if (tools.encodePayLink != null) {
const { tokenId = null } = upgradeCurrencyCode({
allTokens,
currencyInfo: plugin.currencyInfo,
currencyCode: options.currencyCode
})
return await tools.encodePayLink(
{ ...options, addressType: 'publicAddress', tokenId },
{ allTokens }
)
)
}

if (tools.encodeUri != null) {
return await tools.encodeUri(
options,
makeMetaTokens(
input.props.state.accounts[accountId].customTokens[pluginId]
)
)
}

return ''
},

async parseUri(uri: string, currencyCode?: string): Promise<EdgeParsedUri> {
const parsedUri = await tools.parseUri(
uri,
currencyCode,
makeMetaTokens(
input.props.state.accounts[accountId].customTokens[pluginId]
)
)
const allTokens =
input.props.state.accounts[accountId].allTokens[pluginId]

if (parsedUri.tokenId === undefined) {
if (tools.parseLink != null) {
const { tokenId = null } = upgradeCurrencyCode({
allTokens: input.props.state.accounts[accountId].allTokens[pluginId],
allTokens,
currencyInfo: plugin.currencyInfo,
currencyCode: parsedUri.currencyCode ?? currencyCode
currencyCode
})
parsedUri.tokenId = tokenId
const out = await tools.parseLink(uri, { allTokens, tokenId })
return linkToParsedUri(out)
}

if (tools.parseUri != null) {
const parsedUri = await tools.parseUri(
uri,
currencyCode,
makeMetaTokens(
input.props.state.accounts[accountId].customTokens[pluginId]
)
)

if (parsedUri.tokenId === undefined) {
const { tokenId = null } = upgradeCurrencyCode({
allTokens,
currencyInfo: plugin.currencyInfo,
currencyCode: parsedUri.currencyCode ?? currencyCode
})
parsedUri.tokenId = tokenId
}
return parsedUri
}
return parsedUri

return {}
},

// Generic:
Expand Down
Loading
Loading