diff --git a/signers/signer-evm/package.json b/signers/signer-evm/package.json index 36b2026206..6d37ffb894 100644 --- a/signers/signer-evm/package.json +++ b/signers/signer-evm/package.json @@ -24,6 +24,7 @@ "test:coverage": "vitest run --coverage" }, "dependencies": { + "@rango-dev/wallets-core": "^0.44.0", "ethers": "^6.13.2", "rango-types": "^0.1.85" }, diff --git a/signers/signer-evm/src/hub.ts b/signers/signer-evm/src/hub.ts new file mode 100644 index 0000000000..1d30593daf --- /dev/null +++ b/signers/signer-evm/src/hub.ts @@ -0,0 +1,255 @@ +import type { ProxiedNamespace } from '@rango-dev/wallets-core'; +import type { EvmActions } from '@rango-dev/wallets-core/namespaces/evm'; +import type { EvmTransaction } from 'rango-types/mainApi'; + +import { + isError, + type TransactionRequest, + type TransactionResponse, +} from 'ethers'; +import { BrowserProvider } from 'ethers'; +import { + type GenericSigner, + RPCErrorCode as RangoRPCErrorCode, + SignerError, + SignerErrorCode, +} from 'rango-types'; + +import { cleanEvmError, getTenderlyError, waitMs } from './helper.js'; + +const waitWithMempoolCheck = async ( + namespace: ProxiedNamespace, + tx: TransactionResponse, + txHash: string, + confirmations?: number +) => { + const TIMEOUT = 3_000; + let finished = false; + return await Promise.race([ + (async () => { + await tx.wait(confirmations); + finished = true; + })(), + (async () => { + while (!finished) { + await waitMs(TIMEOUT); + if (finished) { + return null; + } + try { + const mempoolTx = await namespace.getTransaction(txHash); + if (!mempoolTx) { + return null; + } + } catch (error) { + console.log({ error }); + return null; + } + } + return null; + })(), + ]); +}; + +const checkChainIdChanged = async ( + namespace: ProxiedNamespace, + chainId: string +) => { + const evmInstance = namespace.getInstance(); + if (!evmInstance) { + return true; + } + const provider = new BrowserProvider(evmInstance); + const signerChainId = (await provider.getNetwork()).chainId; + if ( + !signerChainId || + Number(chainId).toString() !== signerChainId.toString() + ) { + return true; + } + + return false; +}; + +export class HubEvmSigner implements GenericSigner { + private namespace: ProxiedNamespace; + + constructor(namespace: ProxiedNamespace) { + this.namespace = namespace; + } + + static buildTx(evmTx: EvmTransaction, disableV2 = false): TransactionRequest { + const TO_STRING_BASE = 16; + let tx: TransactionRequest = {}; + /* + * it's better to pass 0x instead of undefined, otherwise some wallets could face issue + * https://github.com/WalletConnect/web3modal/issues/1082#issuecomment-1637793242 + */ + tx = { + data: evmTx.data || '0x', + }; + if (evmTx.from) { + tx = { ...tx, from: evmTx.from }; + } + if (evmTx.to) { + tx = { ...tx, to: evmTx.to }; + } + if (evmTx.value) { + tx = { ...tx, value: evmTx.value }; + } + if (evmTx.nonce) { + tx = { ...tx, nonce: parseInt(evmTx.nonce) }; + } + if (evmTx.gasLimit) { + tx = { ...tx, gasLimit: evmTx.gasLimit }; + } + if (!disableV2 && evmTx.maxFeePerGas && evmTx.maxPriorityFeePerGas) { + tx = { + ...tx, + maxFeePerGas: evmTx.maxFeePerGas, + maxPriorityFeePerGas: evmTx.maxPriorityFeePerGas, + }; + } else if (evmTx.gasPrice) { + tx = { + ...tx, + gasPrice: '0x' + parseInt(evmTx.gasPrice).toString(TO_STRING_BASE), + }; + } + return tx; + } + + async signMessage(msg: string): Promise { + try { + return this.namespace.signMessage(msg); + } catch (error) { + throw new SignerError(SignerErrorCode.SIGN_TX_ERROR, undefined, error); + } + } + + async signAndSendTx( + tx: EvmTransaction, + address: string, + chainId: string | null + ): Promise<{ hash: string; response: TransactionResponse }> { + try { + try { + const transaction = HubEvmSigner.buildTx(tx); + const response = await this.namespace.sendTransaction( + transaction, + address, + chainId + ); + return { hash: response.hash, response }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + // retrying EIP-1559 without v2 related fields + if ( + !!error?.message && + typeof error.message === 'string' && + error.message.indexOf('EIP-1559') !== -1 + ) { + console.log('retrying EIP-1559 error without v2 fields ...'); + const transaction = HubEvmSigner.buildTx(tx, true); + const response = await this.namespace.sendTransaction( + transaction, + address, + chainId + ); + return { hash: response.hash, response }; + } + throw error; + } + } catch (error) { + throw cleanEvmError(error); + } + } + + async wait( + txHash: string, + chainId?: string, + txResponse?: TransactionResponse, + confirmations?: number + ): Promise<{ hash: string; response?: TransactionResponse }> { + try { + /* + * if we have transaction response, use that to wait + * otherwise, try to get tx response from the wallet provider + */ + if (txResponse) { + // if we use waitWithMempoolCheck here, we can't detect replaced tx anymore + await txResponse?.wait(confirmations); + return { hash: txHash }; + } + + // ignore wait if namespace is not connected yet + if (!this.namespace.state()[0]?.()?.connected) { + return { hash: txHash }; + } + + // ignore wait if namespace does not support getTransaction + if (!('getTransaction' in this.namespace)) { + return { hash: txHash }; + } + + /* + * don't proceed if signer chain changed or chain id is not specified + * because if user change the wallet network, we receive null on getTransaction + */ + if (!chainId) { + return { hash: txHash }; + } + + const hasChainIdChanged = await checkChainIdChanged( + this.namespace, + chainId + ); + if (hasChainIdChanged) { + return { hash: txHash }; + } + + const tx = await this.namespace.getTransaction(txHash); + if (!tx) { + throw Error(`Transaction hash '${txHash}' not found in blockchain.`); + } + + await waitWithMempoolCheck(this.namespace, tx, txHash, confirmations); + return { hash: txHash }; + } catch (error) { + if (isError(error, 'TRANSACTION_REPLACED')) { + const reason = error.reason; + if (reason === 'cancelled') { + throw new SignerError( + SignerErrorCode.SEND_TX_ERROR, + undefined, + 'Transaction replaced and canceled by user', + undefined, + error + ); + } + return { hash: error.replacement.hash, response: error.replacement }; + } else if (isError(error, 'CALL_EXCEPTION')) { + const tError = await getTenderlyError(chainId, txHash); + if (!!tError) { + throw new SignerError( + SignerErrorCode.TX_FAILED_IN_BLOCKCHAIN, + 'Trannsaction failed in blockchain', + tError, + RangoRPCErrorCode.CALL_EXCEPTION, + error + ); + } else { + /** + * In cases where the is no error returen from tenderly, we could ignore + * the error and proceed with check status flow. + */ + return { hash: txHash }; + } + } + /** + * Ignore other errors in confirming transaction and proceed with check status flow, + * Some times rpc gives internal error or other type of errors even if the transaction succeeded + */ + return { hash: txHash }; + } + } +} diff --git a/signers/signer-evm/src/index.ts b/signers/signer-evm/src/index.ts index e78ccc5711..774a743084 100644 --- a/signers/signer-evm/src/index.ts +++ b/signers/signer-evm/src/index.ts @@ -1,2 +1,3 @@ export { DefaultEvmSigner } from './signer.js'; export { waitMs, cleanEvmError } from './helper.js'; +export { HubEvmSigner } from './hub.js'; diff --git a/signers/signer-solana/package.json b/signers/signer-solana/package.json index 13c37f9528..002a0de693 100644 --- a/signers/signer-solana/package.json +++ b/signers/signer-solana/package.json @@ -21,6 +21,7 @@ "lint": "eslint \"**/*.{ts,tsx}\"" }, "dependencies": { + "@rango-dev/wallets-core": "^0.44.0", "@solana/web3.js": "^1.91.4", "bs58": "^5.0.0", "promise-retry": "^2.0.1", diff --git a/signers/signer-solana/src/hub.ts b/signers/signer-solana/src/hub.ts new file mode 100644 index 0000000000..86637dd890 --- /dev/null +++ b/signers/signer-solana/src/hub.ts @@ -0,0 +1,69 @@ +import type { ProxiedNamespace } from '@rango-dev/wallets-core'; +import type { SolanaActions } from '@rango-dev/wallets-core/namespaces/solana'; +import type { SolanaTransaction } from 'rango-types/mainApi'; + +import { type GenericSigner, SignerError, SignerErrorCode } from 'rango-types'; + +import { + generalSolanaTransactionExecutor, + type SolanaWeb3Signer, +} from './index.js'; + +export class HubSolanaSigner implements GenericSigner { + private namespace: ProxiedNamespace; + + constructor(namespace: ProxiedNamespace) { + this.namespace = namespace; + } + async signMessage(msg: string): Promise { + return this.namespace.signMessage(msg); + } + + async signAndSendTx(tx: SolanaTransaction): Promise<{ hash: string }> { + const DefaultSolanaSigner: SolanaWeb3Signer = async ( + solanaWeb3Transaction + ) => { + const solanaProvider = this.namespace.getInstance(); + + if (!solanaProvider) { + throw new SignerError( + SignerErrorCode.SIGN_TX_ERROR, + 'Solana instance is not available.' + ); + } + + if (!solanaProvider.publicKey) { + throw new SignerError( + SignerErrorCode.SIGN_TX_ERROR, + 'Please make sure the required account is connected properly.' + ); + } + + if (tx.from !== solanaProvider.publicKey?.toString()) { + throw new SignerError( + SignerErrorCode.SIGN_TX_ERROR, + `Your connected account doesn't match with the required account. Please ensure that you are connected with the correct account and try again.` + ); + } + + try { + const signedTransaction = await this.namespace.signTransaction( + solanaWeb3Transaction + ); + return signedTransaction.serialize(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + const REJECTION_CODE = 4001; + if (e && Object.hasOwn(e, 'code') && e.code === REJECTION_CODE) { + throw new SignerError(SignerErrorCode.REJECTED_BY_USER, undefined, e); + } + throw new SignerError(SignerErrorCode.SIGN_TX_ERROR, undefined, e); + } + }; + const hash = await generalSolanaTransactionExecutor( + tx, + DefaultSolanaSigner + ); + return { hash }; + } +} diff --git a/signers/signer-solana/src/index.ts b/signers/signer-solana/src/index.ts index 43729efd24..e6ec27349b 100644 --- a/signers/signer-solana/src/index.ts +++ b/signers/signer-solana/src/index.ts @@ -1,4 +1,5 @@ export { DefaultSolanaSigner } from './signer.js'; +export { HubSolanaSigner } from './hub.js'; export { executeSolanaTransaction, generalSolanaTransactionExecutor, diff --git a/wallets/core/package.json b/wallets/core/package.json index 20a32bbfcd..88c1cff730 100644 --- a/wallets/core/package.json +++ b/wallets/core/package.json @@ -69,7 +69,9 @@ "react-dom": "^17.0.0 || ^18.0.0" }, "dependencies": { + "@solana/web3.js": "^1.91.4", "caip": "^1.1.1", + "ethers": "^6.13.2", "immer": "^10.0.4", "rango-types": "^0.1.85", "zustand": "^4.5.2" diff --git a/wallets/core/src/namespaces/evm/actions.ts b/wallets/core/src/namespaces/evm/actions.ts index fa9070aa89..bf23c10a81 100644 --- a/wallets/core/src/namespaces/evm/actions.ts +++ b/wallets/core/src/namespaces/evm/actions.ts @@ -15,6 +15,10 @@ import { recommended as commonRecommended } from '../common/actions.js'; import { CAIP_NAMESPACE } from './constants.js'; import { getAccounts, switchOrAddNetwork } from './utils.js'; +export { sendTransaction } from './actions/sendTransaction.js'; +export { signMessage } from './actions/signMessage.js'; +export { getTransaction } from './actions/getTransaction.js'; + export const recommended = [...commonRecommended]; export function connect( diff --git a/wallets/core/src/namespaces/evm/actions/getTransaction.ts b/wallets/core/src/namespaces/evm/actions/getTransaction.ts new file mode 100644 index 0000000000..70d14fedee --- /dev/null +++ b/wallets/core/src/namespaces/evm/actions/getTransaction.ts @@ -0,0 +1,21 @@ +import type { Context } from '../../../hub/namespaces/mod.js'; +import type { FunctionWithContext } from '../../../types/actions.js'; +import type { EvmActions, ProviderAPI } from '../types.js'; + +import { BrowserProvider } from 'ethers'; + +export function getTransaction( + instance: () => ProviderAPI | undefined +): FunctionWithContext { + return async (_context, hash: string) => { + const evmInstance = instance(); + if (!evmInstance) { + throw new Error( + 'Do your wallet injected correctly and is evm compatible?' + ); + } + const provider = new BrowserProvider(evmInstance); + + return await provider.getTransaction(hash); + }; +} diff --git a/wallets/core/src/namespaces/evm/actions/sendTransaction.ts b/wallets/core/src/namespaces/evm/actions/sendTransaction.ts new file mode 100644 index 0000000000..e565a8ca0e --- /dev/null +++ b/wallets/core/src/namespaces/evm/actions/sendTransaction.ts @@ -0,0 +1,56 @@ +import type { Context } from '../../../hub/namespaces/mod.js'; +import type { FunctionWithContext } from '../../../types/actions.js'; +import type { EvmActions, ProviderAPI } from '../types.js'; + +import { BrowserProvider, type TransactionRequest } from 'ethers'; +import { SignerError, SignerErrorCode } from 'rango-types'; + +export function sendTransaction( + instance: () => ProviderAPI | undefined +): FunctionWithContext { + return async ( + _context, + transaction: TransactionRequest, + address, + chainId + ) => { + const evmInstance = instance(); + if (!evmInstance) { + throw new Error( + 'Do your wallet injected correctly and is evm compatible?' + ); + } + + const provider = new BrowserProvider(evmInstance); + const signer = await provider.getSigner(); + + const signerChainId = (await provider.getNetwork()).chainId; + const signerAddress = await signer.getAddress(); + + if ( + !!chainId && + !!signerChainId && + signerChainId.toString() !== Number(chainId).toString() + ) { + throw new SignerError( + SignerErrorCode.UNEXPECTED_BEHAVIOUR, + undefined, + `Signer chainId: '${signerChainId}' doesn't match with required chainId: '${chainId}' for tx.` + ); + } + if ( + !!signerAddress && + !!address && + signerAddress.toLowerCase() !== address.toLowerCase() + ) { + throw new SignerError( + SignerErrorCode.UNEXPECTED_BEHAVIOUR, + undefined, + `Signer address: '${signerAddress.toLowerCase()}' doesn't match with required address: '${address.toLowerCase()}' for tx.` + ); + } + + const response = await signer.sendTransaction(transaction); + return response; + }; +} diff --git a/wallets/core/src/namespaces/evm/actions/signMessage.ts b/wallets/core/src/namespaces/evm/actions/signMessage.ts new file mode 100644 index 0000000000..ca0ef56947 --- /dev/null +++ b/wallets/core/src/namespaces/evm/actions/signMessage.ts @@ -0,0 +1,21 @@ +import type { Context } from '../../../hub/namespaces/mod.js'; +import type { FunctionWithContext } from '../../../types/actions.js'; +import type { EvmActions, ProviderAPI } from '../types.js'; + +import { BrowserProvider } from 'ethers'; + +export function signMessage( + instance: () => ProviderAPI | undefined +): FunctionWithContext { + return async (_context, msg: string) => { + const evmInstance = instance(); + if (!evmInstance) { + throw new Error( + 'Do your wallet injected correctly and is evm compatible?' + ); + } + const provider = new BrowserProvider(evmInstance); + const signer = await provider.getSigner(); + return await signer.signMessage(msg); + }; +} diff --git a/wallets/core/src/namespaces/evm/builders.ts b/wallets/core/src/namespaces/evm/builders.ts index baabd05148..7382575250 100644 --- a/wallets/core/src/namespaces/evm/builders.ts +++ b/wallets/core/src/namespaces/evm/builders.ts @@ -7,6 +7,9 @@ import { intoConnectionFinished, } from '../common/mod.js'; +export const getInstance = () => + new ActionBuilder('getInstance'); + export const connect = () => new ActionBuilder('connect') .and(connectAndUpdateStateForMultiNetworks) @@ -15,3 +18,12 @@ export const connect = () => export const canEagerConnect = () => new ActionBuilder('canEagerConnect'); + +export const signMessage = () => + new ActionBuilder('signMessage'); + +export const sendTransaction = () => + new ActionBuilder('sendTransaction'); + +export const getTransaction = () => + new ActionBuilder('getTransaction'); diff --git a/wallets/core/src/namespaces/evm/types.ts b/wallets/core/src/namespaces/evm/types.ts index fa3692396b..8c0ca9ed1a 100644 --- a/wallets/core/src/namespaces/evm/types.ts +++ b/wallets/core/src/namespaces/evm/types.ts @@ -1,15 +1,24 @@ -import type { AddEthereumChainParameter } from './eip1193.js'; +import type { AddEthereumChainParameter, EIP1193Provider } from './eip1193.js'; import type { AccountsWithActiveChain } from '../../types/accounts.js'; import type { AutoImplementedActionsByRecommended, CommonActions, } from '../common/types.js'; +import type { TransactionRequest, TransactionResponse } from 'ethers'; export interface EvmActions extends AutoImplementedActionsByRecommended, CommonActions { + getInstance: () => EIP1193Provider | undefined; connect: (chain?: Chain | ChainId) => Promise; canEagerConnect: () => Promise; + signMessage: (message: string) => Promise; + sendTransaction: ( + tx: TransactionRequest, + address: string, + chainId: string | null + ) => Promise; + getTransaction: (hash: string) => Promise; } export type { EIP1193Provider as ProviderAPI } from './eip1193.js'; diff --git a/wallets/core/src/namespaces/solana/actions.ts b/wallets/core/src/namespaces/solana/actions.ts index cbacb03109..ff5d35d1d8 100644 --- a/wallets/core/src/namespaces/solana/actions.ts +++ b/wallets/core/src/namespaces/solana/actions.ts @@ -13,6 +13,9 @@ import { getAccounts } from './utils.js'; export const recommended = [...commonRecommended]; +export { signTransaction } from './actions/signTransaction.js'; +export { signMessage } from './actions/signMessage.js'; + export function changeAccountSubscriber( instance: () => ProviderAPI | undefined ): [Subscriber, SubscriberCleanUp] { diff --git a/wallets/core/src/namespaces/solana/actions/signMessage.ts b/wallets/core/src/namespaces/solana/actions/signMessage.ts new file mode 100644 index 0000000000..69c5e37e10 --- /dev/null +++ b/wallets/core/src/namespaces/solana/actions/signMessage.ts @@ -0,0 +1,26 @@ +import type { Context } from '../../../hub/namespaces/mod.js'; +import type { FunctionWithContext } from '../../../types/actions.js'; +import type { ProviderAPI, SolanaActions } from '../types.js'; + +import base58 from 'bs58'; + +export function signMessage( + instance: () => ProviderAPI | undefined +): FunctionWithContext { + return async (_context, msg: string) => { + const solanaInstance = instance(); + if (!solanaInstance) { + throw new Error( + 'Do your wallet injected correctly and is evm compatible?' + ); + } + const encodedMessage = new TextEncoder().encode(msg); + const { signature } = await solanaInstance.request({ + method: 'signMessage', + params: { + message: encodedMessage, + }, + }); + return base58.encode(signature); + }; +} diff --git a/wallets/core/src/namespaces/solana/actions/signTransaction.ts b/wallets/core/src/namespaces/solana/actions/signTransaction.ts new file mode 100644 index 0000000000..220a2db7e2 --- /dev/null +++ b/wallets/core/src/namespaces/solana/actions/signTransaction.ts @@ -0,0 +1,22 @@ +import type { Context } from '../../../hub/namespaces/mod.js'; +import type { FunctionWithContext } from '../../../types/actions.js'; +import type { ProviderAPI, SolanaActions } from '../types.js'; + +export function signTransaction( + instance: () => ProviderAPI | undefined +): FunctionWithContext { + return async (_context, transaction) => { + const solanaInstance = instance(); + if (!solanaInstance) { + throw new Error( + 'Do your wallet injected correctly and is evm compatible?' + ); + } + const solanaProvider = instance(); + + if (!solanaProvider) { + throw new Error('Solana provider is not available.'); + } + return await solanaProvider.signTransaction(transaction); + }; +} diff --git a/wallets/core/src/namespaces/solana/builders.ts b/wallets/core/src/namespaces/solana/builders.ts index 3669a2661a..b24ee62b4e 100644 --- a/wallets/core/src/namespaces/solana/builders.ts +++ b/wallets/core/src/namespaces/solana/builders.ts @@ -7,8 +7,17 @@ import { intoConnectionFinished, } from '../common/mod.js'; +export const getInstance = () => + new ActionBuilder('getInstance'); + export const connect = () => new ActionBuilder('connect') .and(connectAndUpdateStateForSingleNetwork) .before(intoConnecting) .after(intoConnectionFinished); + +export const signMessage = () => + new ActionBuilder('signMessage'); + +export const signTransaction = () => + new ActionBuilder('signTransaction'); diff --git a/wallets/core/src/namespaces/solana/types.ts b/wallets/core/src/namespaces/solana/types.ts index f4969cd59d..5e12b1818b 100644 --- a/wallets/core/src/namespaces/solana/types.ts +++ b/wallets/core/src/namespaces/solana/types.ts @@ -3,12 +3,18 @@ import type { AutoImplementedActionsByRecommended, CommonActions, } from '../common/types.js'; +import type { Transaction, VersionedTransaction } from '@solana/web3.js'; export interface SolanaActions extends AutoImplementedActionsByRecommended, CommonActions { connect: () => Promise; canEagerConnect: () => Promise; + getInstance: () => ProviderAPI | undefined; + signMessage: (message: string) => Promise; + signTransaction: ( + transaction: Transaction | VersionedTransaction + ) => Promise; } /* diff --git a/wallets/provider-phantom/src/namespaces/evm.ts b/wallets/provider-phantom/src/namespaces/evm.ts index 4a9b569ab7..823d239657 100644 --- a/wallets/provider-phantom/src/namespaces/evm.ts +++ b/wallets/provider-phantom/src/namespaces/evm.ts @@ -13,6 +13,11 @@ import { evmPhantom } from '../utils.js'; const [changeAccountSubscriber, changeAccountCleanup] = actions.changeAccountSubscriber(evmPhantom); +const getInstance = builders + .getInstance() + .action(() => evmPhantom()) + .build(); + /* * TODO: If user imported a private key for EVM, it hasn't solana. * when trying to connect to solana for this user we go through `-32603` which is an internal error. @@ -37,10 +42,29 @@ const canEagerConnect = builders .action(actions.canEagerConnect(evmPhantom)) .build(); +const signMessage = builders + .signMessage() + .action(actions.signMessage(evmPhantom)) + .build(); + +const sendTransaction = builders + .sendTransaction() + .action(actions.sendTransaction(evmPhantom)) + .build(); + +const getTransaction = builders + .getTransaction() + .action(actions.getTransaction(evmPhantom)) + .build(); + const evm = new NamespaceBuilder('EVM', WALLET_ID) + .action(getInstance) .action(connect) .action(disconnect) .action(canEagerConnect) + .action(signMessage) + .action(sendTransaction) + .action(getTransaction) .build(); export { evm }; diff --git a/wallets/provider-phantom/src/namespaces/solana.ts b/wallets/provider-phantom/src/namespaces/solana.ts index 9baa71d5b2..517b6e6131 100644 --- a/wallets/provider-phantom/src/namespaces/solana.ts +++ b/wallets/provider-phantom/src/namespaces/solana.ts @@ -21,6 +21,11 @@ import { solanaPhantom } from '../utils.js'; const [changeAccountSubscriber, changeAccountCleanup] = actions.changeAccountSubscriber(solanaPhantom); +const getInstance = builders + .getInstance() + .action(() => solanaPhantom()) + .build(); + /* * TODO: If user imported a private key for EVM, it hasn't solana. * when trying to connect to solana for this user we go through `-32603` which is an internal error. @@ -87,10 +92,23 @@ const canEagerConnect = new ActionBuilder( .action(canEagerConnectAction) .build(); +const signMessage = builders + .signMessage() + .action(actions.signMessage(solanaPhantom)) + .build(); + +const signTransaction = builders + .signTransaction() + .action(actions.signTransaction(solanaPhantom)) + .build(); + const solana = new NamespaceBuilder('Solana', WALLET_ID) + .action(getInstance) .action(connect) .action(disconnect) .action(canEagerConnect) + .action(signMessage) + .action(signTransaction) .build(); export { solana }; diff --git a/wallets/provider-phantom/src/namespaces/sui.ts b/wallets/provider-phantom/src/namespaces/sui.ts index abc6af3578..d88cc50cb4 100644 --- a/wallets/provider-phantom/src/namespaces/sui.ts +++ b/wallets/provider-phantom/src/namespaces/sui.ts @@ -1,4 +1,3 @@ -import type { SolanaActions } from '@rango-dev/wallets-core/namespaces/solana'; import type { SuiActions } from '@rango-dev/wallets-core/namespaces/sui'; import { ActionBuilder, NamespaceBuilder } from '@rango-dev/wallets-core'; @@ -36,7 +35,7 @@ const disconnect = commonBuilders * This is a temporary workaround due to Phantom's limitation in silently connecting to a Sui account. * Once Phantom introduces support for silent Sui connections, this implementation should be updated accordingly. */ -const canEagerConnect = new ActionBuilder( +const canEagerConnect = new ActionBuilder( 'canEagerConnect' ) .action(solanaCanEagerConnectAction) diff --git a/wallets/provider-phantom/src/namespaces/utxo.ts b/wallets/provider-phantom/src/namespaces/utxo.ts index 0215cb342d..0577584f04 100644 --- a/wallets/provider-phantom/src/namespaces/utxo.ts +++ b/wallets/provider-phantom/src/namespaces/utxo.ts @@ -1,4 +1,3 @@ -import type { SolanaActions } from '@rango-dev/wallets-core/namespaces/solana'; import type { ProviderAPI, UtxoActions, @@ -155,7 +154,7 @@ const disconnect = commonBuilders * This is a temporary workaround due to Phantom's limitation in silently connecting to a BTC account. * Once Phantom introduces support for silent BTC connections, this implementation should be updated accordingly. */ -const canEagerConnect = new ActionBuilder( +const canEagerConnect = new ActionBuilder( 'canEagerConnect' ) .action(solanaCanEagerConnectAction) diff --git a/wallets/react/package.json b/wallets/react/package.json index f690e6a8ac..fafaa4a52d 100644 --- a/wallets/react/package.json +++ b/wallets/react/package.json @@ -35,6 +35,8 @@ "react-dom": "^17.0.0 || ^18.0.0" }, "dependencies": { + "@rango-dev/signer-evm": "^0.36.0", + "@rango-dev/signer-solana": "^0.40.0", "@rango-dev/wallets-core": "^0.44.0", "@rango-dev/wallets-shared": "^0.45.0", "rango-types": "^0.1.85", @@ -47,4 +49,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/wallets/react/src/hub/useHubAdapter.ts b/wallets/react/src/hub/useHubAdapter.ts index 28a68ad71d..34419edfdb 100644 --- a/wallets/react/src/hub/useHubAdapter.ts +++ b/wallets/react/src/hub/useHubAdapter.ts @@ -1,10 +1,15 @@ import type { AllProxiedNamespaces, ExtensionLink } from './types.js'; import type { ProviderContext, Providers } from '../index.js'; -import type { Provider } from '@rango-dev/wallets-core'; +import type { Provider, ProxiedNamespace } from '@rango-dev/wallets-core'; import type { LegacyNamespaceInputForConnect } from '@rango-dev/wallets-core/legacy'; +import type { EvmActions } from '@rango-dev/wallets-core/namespaces/evm'; +import type { SolanaActions } from '@rango-dev/wallets-core/namespaces/solana'; import type { VersionedProviders } from '@rango-dev/wallets-core/utils'; +import { HubEvmSigner } from '@rango-dev/signer-evm'; +import { HubSolanaSigner } from '@rango-dev/signer-solana'; import { type WalletInfo } from '@rango-dev/wallets-shared'; +import { TransactionType } from 'rango-types'; import { useEffect, useRef, useState } from 'react'; import { Ok, Result } from 'ts-results'; @@ -271,7 +276,27 @@ export function useHubAdapter(params: UseAdapterParams): ProviderContext { }, async getSigners(type) { const provider = getLegacyProvider(params.allVersionedProviders, type); - return provider.getSigners(provider.getInstance()); + const signers = await provider.getSigners(provider.getInstance()); + const wallet = getHub().get(type); + if (!wallet) { + throw new Error(`You should add ${type} to provider first.`); + } + + wallet.getAll().forEach((namespace) => { + if (namespace.namespaceId === 'evm') { + signers.registerSigner( + TransactionType.EVM, + new HubEvmSigner(namespace as ProxiedNamespace) + ); + } else if (namespace.namespaceId === 'solana') { + signers.registerSigner( + TransactionType.SOLANA, + new HubSolanaSigner(namespace as ProxiedNamespace) + ); + } + }); + + return signers; }, getWalletInfo(type) { const wallet = getHub().get(type);