Skip to content

Commit

Permalink
feat: solana auth provider (#2709)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoruka authored Aug 22, 2024
1 parent d9e6bd2 commit febc9d6
Show file tree
Hide file tree
Showing 23 changed files with 735 additions and 118 deletions.
158 changes: 134 additions & 24 deletions packages/base/adapters/solana/web3js/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ import type {
} from '@web3modal/scaffold-utils/solana'
import { watchStandard } from './utils/watchStandard.js'
import { WalletConnectProvider } from './providers/WalletConnectProvider.js'
import { AuthProvider } from './providers/AuthProvider.js'
import {
W3mFrameHelpers,
W3mFrameProvider,
W3mFrameRpcConstants,
type W3mFrameTypes
} from '@web3modal/wallet'
import { withSolanaNamespace } from './utils/withSolanaNamespace.js'
import type { AppKit } from '../../../src/client.js'
import type { AppKitOptions } from '../../../utils/TypesUtil.js'
import type { OptionsControllerState } from '@web3modal/core'
Expand All @@ -50,9 +58,6 @@ export interface Web3ModalClientOptions
wallets?: BaseWalletAdapter[]
}

export type ExtendedBaseWalletAdapter = BaseWalletAdapter & {
isAnnounced: boolean
}
export type Web3ModalOptions = Omit<Web3ModalClientOptions, '_sdkVersion' | 'isUniversalProvider'>

// -- Client --------------------------------------------------------------------
Expand Down Expand Up @@ -210,6 +215,12 @@ export class SolanaWeb3JsClient {
relayUrl: 'wss://relay.walletconnect.com',
metadata: clientOptions.metadata,
projectId: options.projectId

/**
* Auth configuration will be disabled until secure site is updated
*
* ...clientOptions.solanaConfig.auth
*/
})

this.syncRequestedNetworks(chains, this.options?.chainImages)
Expand All @@ -236,11 +247,15 @@ export class SolanaWeb3JsClient {
this.syncNetwork()
})

SolStoreUtil.subscribeKey('isConnected', isConnected => {
this.appKit?.setIsConnected(isConnected, 'solana')
})

AssetController.subscribeNetworkImages(() => {
this.syncNetwork()
})

NetworkController.subscribeKey('caipNetwork', (newCaipNetwork: CaipNetwork | undefined) => {
NetworkController.subscribeKey('caipNetwork', (newCaipNetwork?: CaipNetwork) => {
const newChain = chains.find(_chain => _chain.chainId === newCaipNetwork?.id.split(':')[1])

if (!newChain) {
Expand Down Expand Up @@ -270,10 +285,6 @@ export class SolanaWeb3JsClient {
})
}

public setAddress(address = '') {
SolStoreUtil.setAddress(address)
}

public disconnect() {
return this.getProvider().disconnect()
}
Expand All @@ -298,19 +309,17 @@ export class SolanaWeb3JsClient {
private async syncAccount() {
const address = SolStoreUtil.state.address
const chainId = SolStoreUtil.state.currentChain?.chainId
const isConnected = SolStoreUtil.state.isConnected
this.appKit?.resetAccount(this.chain)

if (isConnected && address && chainId) {
if (address && chainId) {
const caipAddress: CaipAddress = `${ConstantsUtil.INJECTED_CONNECTOR_ID}:${chainId}:${address}`
this.appKit?.setIsConnected(isConnected, this.chain)
this.appKit?.setCaipAddress(caipAddress, this.chain)
await this.syncBalance(address)

this.hasSyncedConnectedAccount = true
} else if (!isConnected && this.hasSyncedConnectedAccount) {
} else if (this.hasSyncedConnectedAccount) {
this.appKit?.resetWcConnection()
this.appKit?.resetNetwork()
this.appKit?.resetAccount('solana')
}
}

Expand Down Expand Up @@ -353,6 +362,11 @@ export class SolanaWeb3JsClient {

public async switchNetwork(caipNetwork: CaipNetwork) {
const caipChainId = caipNetwork.id

if (this.provider instanceof AuthProvider) {
await this.provider.switchNetwork(caipChainId)
}

const chain = SolHelpersUtil.getChainFromCaip(this.chains, caipChainId)
SolStoreUtil.setCaipChainId(chain.id)
SolStoreUtil.setCurrentChain(chain)
Expand All @@ -366,7 +380,6 @@ export class SolanaWeb3JsClient {
const chainImages = this.options?.chainImages
const address = SolStoreUtil.state.address
const storeChainId = SolStoreUtil.state.caipChainId
const isConnected = SolStoreUtil.state.isConnected

if (this.chains) {
const chain = SolHelpersUtil.getChainFromCaip(this.chains, storeChainId)
Expand All @@ -387,7 +400,8 @@ export class SolanaWeb3JsClient {
imageUrl: chainImages?.[chain.chainId],
chain: this.chain
})
if (isConnected && address) {

if (address) {
if (chain.explorerUrl) {
const url = `${chain.explorerUrl}/account/${address}`
this.appKit?.setAddressExplorerUrl(url, this.chain)
Expand Down Expand Up @@ -416,38 +430,107 @@ export class SolanaWeb3JsClient {
provider.chains.find(chain => chain.chainId === SolStoreUtil.state.currentChain?.chainId) ||
provider.chains[0]

if (connectionChain) {
await this.switchNetwork(
SolHelpersUtil.getChainFromCaip(this.chains, `solana:${connectionChain.chainId}`)
)
} else {
if (!connectionChain) {
provider.disconnect()
throw new Error('The wallet does not support any of the required chains')
}

SolStoreUtil.setIsConnected(true)
SolStoreUtil.setAddress(address)
await this.switchNetwork(
SolHelpersUtil.getChainFromCaip(this.chains, `solana:${connectionChain.chainId}`)
)
SolStoreUtil.setProvider(provider)
this.provider = provider
this.setAddress(address)

window?.localStorage.setItem(SolConstantsUtil.WALLET_ID, provider.name)

await this.appKit?.setApprovedCaipNetworksData(this.chain)

this.watchProvider(provider)
SolStoreUtil.setIsConnected(true)
} finally {
this.appKit?.setLoading(false)
}
}

private watchProvider(provider: Provider) {
/*
* The auth RPC request handlers should be moved to the primary scaffold (Web3ModalScaffold).
* They are replicated in wagmi and ethers clients and the behavior should be kept the same
* between any client.
*/

// eslint-disable-next-line func-style
const rpcRequestHandler = (request: W3mFrameTypes.RPCRequest) => {
if (!this.appKit) {
return
}

if (W3mFrameHelpers.checkIfRequestExists(request)) {
if (!W3mFrameHelpers.checkIfRequestIsAllowed(request)) {
if (this.appKit.isOpen()) {
if (this.appKit.isTransactionStackEmpty()) {
return
}
if (this.appKit.isTransactionShouldReplaceView()) {
this.appKit.replace('ApproveTransaction')
} else {
this.appKit.redirect('ApproveTransaction')
}
} else {
this.appKit.open({ view: 'ApproveTransaction' })
}
}
} else {
this.appKit.open()
// eslint-disable-next-line no-console
console.error(W3mFrameRpcConstants.RPC_METHOD_NOT_ALLOWED_MESSAGE, {
method: request.method
})
setTimeout(() => {
this.appKit?.showErrorMessage(W3mFrameRpcConstants.RPC_METHOD_NOT_ALLOWED_UI_MESSAGE)
}, 300)
}
}

// eslint-disable-next-line func-style
const rpcSuccessHandler = (_response: W3mFrameTypes.FrameEvent) => {
if (!this.appKit) {
return
}

if (this.appKit.isTransactionStackEmpty()) {
this.appKit.close()
} else {
this.appKit.popTransactionStack()
}
}

// eslint-disable-next-line func-style
const rpcErrorHandler = (_error: Error) => {
if (!this.appKit) {
return
}

if (this.appKit.isOpen()) {
if (this.appKit.isTransactionStackEmpty()) {
this.appKit.close()
} else {
this.appKit.popTransactionStack(true)
}
}
}

function disconnectHandler() {
localStorage.removeItem(SolConstantsUtil.WALLET_ID)
SolStoreUtil.reset()

provider.removeListener('disconnect', disconnectHandler)
provider.removeListener('accountsChanged', accountsChangedHandler)
provider.removeListener('connect', accountsChangedHandler)
provider.removeListener('auth_rpcRequest', rpcRequestHandler)
provider.removeListener('auth_rpcSuccess', rpcSuccessHandler)
provider.removeListener('auth_rpcError', rpcErrorHandler)
}

function accountsChangedHandler(publicKey: PublicKey) {
Expand All @@ -463,6 +546,9 @@ export class SolanaWeb3JsClient {
provider.on('disconnect', disconnectHandler)
provider.on('accountsChanged', accountsChangedHandler)
provider.on('connect', accountsChangedHandler)
provider.on('auth_rpcRequest', rpcRequestHandler)
provider.on('auth_rpcSuccess', rpcSuccessHandler)
provider.on('auth_rpcError', rpcErrorHandler)
}

private getProvider() {
Expand All @@ -473,7 +559,7 @@ export class SolanaWeb3JsClient {
return this.provider
}

private async initializeProviders(opts: UniversalProviderOpts) {
private async initializeProviders(opts: UniversalProviderOpts & Provider['auth']) {
if (CoreHelperUtil.isClient()) {
this.addProvider(
new WalletConnectProvider({
Expand All @@ -483,6 +569,29 @@ export class SolanaWeb3JsClient {
})
)

if (opts.email || opts.socials) {
if (!opts.projectId) {
throw new Error('projectId is required for AuthProvider')
}

this.addProvider(
new AuthProvider({
provider: new W3mFrameProvider(
opts.projectId,
withSolanaNamespace(SolStoreUtil.state.currentChain?.chainId)
),
getActiveChain: () => SolStoreUtil.state.currentChain,
auth: {
email: opts.email,
socials: opts.socials,
showWallets: opts.showWallets,
walletFeatures: opts.walletFeatures
},
chains: this.chains
})
)
}

watchStandard(standardAdapters => this.addProvider.bind(this)(...standardAdapters))
}
}
Expand All @@ -509,7 +618,8 @@ export class SolanaWeb3JsClient {
imageUrl: provider.icon,
name: provider.name,
provider,
chain: CommonConstantsUtil.CHAIN.SOLANA
chain: CommonConstantsUtil.CHAIN.SOLANA,
...provider.auth
}))

this.appKit?.setConnectors(connectors)
Expand Down
Loading

0 comments on commit febc9d6

Please sign in to comment.