diff --git a/packages/engine/src/vaults/impl/nostr/helper/Nostr.test.ts b/packages/engine/src/vaults/impl/nostr/helper/Nostr.test.ts index 70b1167c1f8..07d039ecc10 100644 --- a/packages/engine/src/vaults/impl/nostr/helper/Nostr.test.ts +++ b/packages/engine/src/vaults/impl/nostr/helper/Nostr.test.ts @@ -3,7 +3,11 @@ import * as bip39 from 'bip39'; import { getBitcoinBip32 } from '../../../utils/btcForkChain/utils'; -import { NOSTR_ADDRESS_INDEX, getNostrPath } from './NostrSDK'; +import { + NOSTR_ADDRESS_INDEX, + getNip19EncodedPubkey, + getNostrPath, +} from './NostrSDK'; const fixtures = [ { @@ -39,6 +43,9 @@ describe('test Nostr', () => { fixture.privateKey, ); expect(bytesToHex(node.publicKey.slice(1, 33))).toEqual(fixture.pubkey); + expect( + getNip19EncodedPubkey(bytesToHex(node.publicKey.slice(1, 33))), + ).toEqual(fixture.npub); }); }); diff --git a/packages/engine/src/vaults/impl/nostr/helper/NostrSDK.ts b/packages/engine/src/vaults/impl/nostr/helper/NostrSDK.ts index abe09a7f68f..557732d3732 100644 --- a/packages/engine/src/vaults/impl/nostr/helper/NostrSDK.ts +++ b/packages/engine/src/vaults/impl/nostr/helper/NostrSDK.ts @@ -66,6 +66,11 @@ export function getNostrCredentialId(walletId: string, accountIndex: number) { return `${walletId}--nostr--${accountIndex}`; } +export function getNip19EncodedPubkey(pubkey: string) { + const words = bech32.toWords(Buffer.from(pubkey, 'hex')); + return bech32.encode('npub', words, 1000); +} + class Nostr { private walletId: string; diff --git a/packages/kit-bg/src/providers/ProviderApiNostr.ts b/packages/kit-bg/src/providers/ProviderApiNostr.ts index 975cc8787c4..f8d3640fbca 100644 --- a/packages/kit-bg/src/providers/ProviderApiNostr.ts +++ b/packages/kit-bg/src/providers/ProviderApiNostr.ts @@ -1,6 +1,7 @@ import { web3Errors } from '@onekeyfe/cross-inpage-provider-errors'; import { IInjectedProviderNames } from '@onekeyfe/cross-inpage-provider-types'; +import { getNip19EncodedPubkey } from '@onekeyhq/engine/src/vaults/impl/nostr/helper/NostrSDK'; import type { INostrRelays, NostrEvent, @@ -14,7 +15,10 @@ import { backgroundClass, providerApiMethod, } from '@onekeyhq/shared/src/background/backgroundDecorators'; -import { IMPL_LIGHTNING } from '@onekeyhq/shared/src/engine/engineConsts'; +import { + IMPL_LIGHTNING, + IMPL_NOSTR, +} from '@onekeyhq/shared/src/engine/engineConsts'; import { isHdWallet } from '@onekeyhq/shared/src/engine/engineUtils'; import debugLogger from '@onekeyhq/shared/src/logger/debugLogger'; @@ -78,14 +82,57 @@ class ProviderApiNostr extends ProviderApiBase { public async getPublicKey(request: IJsBridgeMessagePayload): Promise { const { walletId, networkId, accountId } = getActiveWalletAccount(); this.checkWalletSupport(walletId); + const existPubKey = await this.getNostrAccountPubKey(request); + if (existPubKey) { + return existPubKey; + } const pubkey = await this.backgroundApi.serviceDapp.openModal({ request, screens: [ModalRoutes.Nostr, NostrModalRoutes.GetPublicKey], params: { walletId, networkId, accountId }, }); + + if (request.origin) { + this.backgroundApi.serviceDapp.saveConnectedAccounts({ + site: { + origin: request.origin, + }, + address: getNip19EncodedPubkey(pubkey as string), + networkImpl: IMPL_NOSTR, + }); + } + return Promise.resolve(pubkey as string); } + async getNostrAccountPubKey(request: IJsBridgeMessagePayload) { + const { walletId, networkId, accountId } = getActiveWalletAccount(); + const accounts = this.backgroundApi.serviceDapp?.getActiveConnectedAccounts( + { + origin: request.origin as string, + impl: IMPL_NOSTR, + }, + ); + if (!accounts) { + return null; + } + const accountNpubs = accounts.map((account) => account.address); + try { + const nostrAccount = + await this.backgroundApi.serviceNostr.getNostrAccount({ + walletId, + currentNetworkId: networkId, + currentAccountId: accountId, + }); + if (accountNpubs.includes(nostrAccount.address)) { + return nostrAccount.pubKey; + } + return null; + } catch { + return null; + } + } + @providerApiMethod() public async getRelays(): Promise { const result = await this.backgroundApi.serviceNostr.getRelays(); diff --git a/packages/kit-bg/src/providers/ProviderApiWebln.ts b/packages/kit-bg/src/providers/ProviderApiWebln.ts index 6750f3d45c7..fec765c4882 100644 --- a/packages/kit-bg/src/providers/ProviderApiWebln.ts +++ b/packages/kit-bg/src/providers/ProviderApiWebln.ts @@ -77,6 +77,10 @@ class ProviderApiWebln extends ProviderApiBase { @providerApiMethod() public async enable(request: IJsBridgeMessagePayload) { try { + const accountEnabled = await this.getEnabledAccount(request); + if (accountEnabled) { + return { enabled: true }; + } await this.backgroundApi.serviceDapp.openConnectionModal(request); return { enabled: true }; } catch (error) { @@ -85,6 +89,37 @@ class ProviderApiWebln extends ProviderApiBase { } } + private async getEnabledAccount(request: IJsBridgeMessagePayload) { + const { networkId, accountId } = getActiveWalletAccount(); + try { + const accounts = + this.backgroundApi.serviceDapp?.getActiveConnectedAccounts({ + origin: request.origin as string, + impl: IMPL_LIGHTNING, + }); + if (!accounts) { + return false; + } + const accountAddresses = accounts.map((account) => account.address); + + const account = await this.backgroundApi.engine.getAccount( + accountId, + networkId, + ); + if (account.addresses) { + const addresses = JSON.parse(account.addresses) as { + hashAddress?: string; + }; + if (addresses.hashAddress) { + return accountAddresses.includes(addresses.hashAddress); + } + } + return false; + } catch { + return false; + } + } + @providerApiMethod() public async getInfo() { return Promise.resolve({ diff --git a/packages/kit-bg/src/services/ServiceNetwork.ts b/packages/kit-bg/src/services/ServiceNetwork.ts index 3e3455f7aa9..e0dd73ecdaa 100644 --- a/packages/kit-bg/src/services/ServiceNetwork.ts +++ b/packages/kit-bg/src/services/ServiceNetwork.ts @@ -38,6 +38,7 @@ import { } from '@onekeyhq/shared/src/background/backgroundDecorators'; import { IMPL_EVM, + getSupportedFakeNetworks, getSupportedImpls, } from '@onekeyhq/shared/src/engine/engineConsts'; import { @@ -502,7 +503,10 @@ class ServiceNetwork extends ServiceBase { }); for (const network of presetNetworksList) { - if (getSupportedImpls().has(network.impl)) { + if ( + getSupportedImpls().has(network.impl) || + getSupportedFakeNetworks().has(network.impl) + ) { const existingStatus = dbNetworkMap[network.id]; if (typeof existingStatus !== 'undefined') { defaultNetworkList.push([network.id, existingStatus]); diff --git a/packages/kit-bg/src/services/ServiceNostr.ts b/packages/kit-bg/src/services/ServiceNostr.ts index 7c1a2df285c..326a9b71bfe 100644 --- a/packages/kit-bg/src/services/ServiceNostr.ts +++ b/packages/kit-bg/src/services/ServiceNostr.ts @@ -19,6 +19,7 @@ import { backgroundMethod, } from '@onekeyhq/shared/src/background/backgroundDecorators'; import { OnekeyNetwork } from '@onekeyhq/shared/src/config/networkIds'; +import debugLogger from '@onekeyhq/shared/src/logger/debugLogger'; import ServiceBase from './ServiceBase'; @@ -64,6 +65,38 @@ export default class ServiceNostr extends ServiceBase { return index; } + @backgroundMethod() + async getNostrAccount({ + walletId, + currentAccountId, + currentNetworkId, + }: { + walletId: string; + currentAccountId: string; + currentNetworkId: string; + }) { + const accountIndex = await this.getCurrentAccountIndex( + currentAccountId, + currentNetworkId, + ); + const networkId = OnekeyNetwork.nostr; + const path = `${getNostrPath(accountIndex)}/${NOSTR_ADDRESS_INDEX}`; + const accountId = `${walletId}--${path}`; + try { + const account = await this.backgroundApi.engine.getAccount( + accountId, + networkId, + ); + return account; + } catch (e) { + debugLogger.backgroundApi.error( + 'Nostr: get nostr account failed: ', + accountId, + ); + throw e; + } + } + @backgroundMethod() async getOrCreateNostrAccount({ walletId, diff --git a/packages/shared/src/engine/engineConsts.ts b/packages/shared/src/engine/engineConsts.ts index 1f794a4fc68..2f93866c79b 100644 --- a/packages/shared/src/engine/engineConsts.ts +++ b/packages/shared/src/engine/engineConsts.ts @@ -119,8 +119,6 @@ const SUPPORTED_IMPLS = new Set([ IMPL_LIGHTNING, IMPL_LIGHTNING_TESTNET, IMPL_ALLNETWORKS, - IMPL_NOSTR, - COINTYPE_NOSTR, ]); const PRODUCTION_IMPLS = new Set([ @@ -178,6 +176,15 @@ function getSupportedImpls() { return SUPPORTED_IMPLS; } +/** + * Protocols like Nostr are not a chain, + * but for the purpose of account derivation, + * we still treat them as a chain. + */ +function getSupportedFakeNetworks() { + return new Set([IMPL_NOSTR]); +} + export { COINTYPE_ADA, COINTYPE_ALGO, @@ -235,6 +242,7 @@ export { INDEX_PLACEHOLDER, SEPERATOR, getSupportedImpls, + getSupportedFakeNetworks, }; // switch network default rpc to onekey rpc node