diff --git a/.changeset/chatty-shoes-joke.md b/.changeset/chatty-shoes-joke.md new file mode 100644 index 00000000..bc4b01a1 --- /dev/null +++ b/.changeset/chatty-shoes-joke.md @@ -0,0 +1,5 @@ +--- +"@fuel-connectors/burner-wallet-connector": patch +--- + +Refactor burner wallet connector diff --git a/examples/react-app/src/App.tsx b/examples/react-app/src/App.tsx index acdfb07d..e2e7cdc5 100644 --- a/examples/react-app/src/App.tsx +++ b/examples/react-app/src/App.tsx @@ -10,6 +10,7 @@ export default function App() { currentConnector, isConnected, isConnecting, + isLoadingConnectors, isLoading, isFetching, connect, @@ -79,8 +80,12 @@ export default function App() {
diff --git a/examples/react-app/src/hooks/useWallet.ts b/examples/react-app/src/hooks/useWallet.ts index 527d11e2..6ef7bf03 100644 --- a/examples/react-app/src/hooks/useWallet.ts +++ b/examples/react-app/src/hooks/useWallet.ts @@ -20,7 +20,11 @@ const DEFAULT_CONNECTOR: ICurrentConnector = { export const useWallet = () => { const { fuel } = useFuel(); - const { connect, isConnecting } = useConnectUI(); + const { + connect, + isConnecting, + isLoading: isLoadingConnectors, + } = useConnectUI(); const { isConnected, refetch: refetchConnected } = useIsConnected(); const { accounts, @@ -75,6 +79,7 @@ export const useWallet = () => { isConnecting, isLoading, isFetching, + isLoadingConnectors, wallet, connect, refetchConnected, diff --git a/packages/burner-wallet-connector/src/BurnerWalletConnector.ts b/packages/burner-wallet-connector/src/BurnerWalletConnector.ts index 2fd8a41d..dd44bc62 100644 --- a/packages/burner-wallet-connector/src/BurnerWalletConnector.ts +++ b/packages/burner-wallet-connector/src/BurnerWalletConnector.ts @@ -15,7 +15,6 @@ import { } from 'fuels'; import { BETA_5_URL, - BURNER_WALLET_CONNECTED, BURNER_WALLET_ICON, BURNER_WALLET_PRIVATE_KEY, WINDOW, @@ -23,6 +22,7 @@ import { import type { BurnerWalletConfig } from './types'; export class BurnerWalletConnector extends FuelConnector { + static defaultProviderUrl: string = BETA_5_URL; name = 'Burner Wallet'; connected = false; @@ -39,65 +39,49 @@ export class BurnerWalletConnector extends FuelConnector { }, }; - burnerWallet: WalletUnlocked | null = null; - burnerWalletProvider: Provider | null = null; - burnerWalletPrivateKey: string | null = null; - - private config: BurnerWalletConfig = {}; + private burnerWallet: WalletUnlocked | null = null; + private burnerWalletProvider: Provider | null = null; private storage: StorageAbstract; constructor(config: BurnerWalletConfig = {}) { super(); - - this.config = config; this.storage = this.getStorage(config.storage); - - this.configFuelProvider(config); this.setupBurnerWallet(); } - private async configFuelProvider(config: BurnerWalletConfig = {}) { - this.config.fuelProvider = - config.fuelProvider || Provider.create(BETA_5_URL); - } - - private async setupBurnerWallet() { - const privateKey = await this.storage.getItem(BURNER_WALLET_PRIVATE_KEY); - - if (privateKey) { - this.storage.setItem(BURNER_WALLET_ICON, privateKey); + private async getProvider(config: BurnerWalletConfig = {}) { + if (!this.burnerWalletProvider) { + this.burnerWalletProvider = await (config.fuelProvider || + Provider.create(BurnerWalletConnector.defaultProviderUrl)); } + return this.burnerWalletProvider; + } - if (!privateKey) { - this.burnerWallet = Wallet.generate({ - provider: await this.config.fuelProvider, - }); - - this.burnerWalletProvider = this.burnerWallet.provider; - this.burnerWalletPrivateKey = this.burnerWallet.privateKey; - - this.storage.setItem( - BURNER_WALLET_PRIVATE_KEY, - this.burnerWalletPrivateKey, - ); + private generatePrivateKey() { + const privateKey = Wallet.generate().privateKey; + this.storage.setItem(BURNER_WALLET_PRIVATE_KEY, privateKey); + return privateKey; + } - return this.burnerWallet; + private async setupBurnerWallet(createWallet = false) { + if (this.burnerWallet) return; + let privateKey = await this.storage.getItem(BURNER_WALLET_PRIVATE_KEY); + if (createWallet && !privateKey) { + privateKey = this.generatePrivateKey(); } + if (!privateKey) return; this.burnerWallet = Wallet.fromPrivateKey( privateKey, - await this.config.fuelProvider, + await this.getProvider(), ); - this.burnerWalletProvider = this.burnerWallet.provider; - this.burnerWalletPrivateKey = this.burnerWallet.privateKey; - return this.burnerWallet; } private getStorage(storage?: StorageAbstract) { const _storage = - storage ?? (WINDOW.sessionStorage as unknown as StorageAbstract); + storage ?? (WINDOW.localStorage as unknown as StorageAbstract); if (!_storage) { throw new Error('No storage provided'); } @@ -111,9 +95,7 @@ export class BurnerWalletConnector extends FuelConnector { * ============================================================ */ async ping(): Promise { - await this.configFuelProvider(); await this.setupBurnerWallet(); - return true; } @@ -122,41 +104,19 @@ export class BurnerWalletConnector extends FuelConnector { } async isConnected(): Promise { - const connected = - (await this.storage.getItem(BURNER_WALLET_CONNECTED)) === 'true'; - - if (!connected) { - return false; - } - - const account = this.burnerWallet?.address.toString(); - - return !!account && account.length > 0; + await this.setupBurnerWallet(false); + return !!this.burnerWallet; } async connect(): Promise { - if (!(await this.isConnected())) { - if (!this.burnerWalletProvider) { - throw Error('Burner Wallet Provider not found'); - } - - await this.setupBurnerWallet(); - - this.burnerWalletProvider = this.burnerWallet?.connect( - this.burnerWalletProvider, - ) as Provider; - - this.storage.setItem(BURNER_WALLET_CONNECTED, 'true'); - } + await this.setupBurnerWallet(true); + const accountAddress = this.burnerWallet?.address.toAddress(); this.emit(this.events.connection, true); - this.emit( - this.events.currentAccount, - this.burnerWallet?.address.toAddress(), - ); - this.emit(this.events.accounts, [this.burnerWallet?.address.toAddress()]); + this.emit(this.events.currentAccount, accountAddress); + this.emit(this.events.accounts, [accountAddress]); - return this.connected; + return true; } async accounts(): Promise { @@ -174,32 +134,25 @@ export class BurnerWalletConnector extends FuelConnector { } async disconnect(): Promise { - if (await this.isConnected()) { - this.burnerWalletPrivateKey = null; - this.burnerWalletProvider = null; - this.burnerWallet = null; - } - - this.storage.setItem(BURNER_WALLET_CONNECTED, 'false'); + this.burnerWalletProvider = null; + this.burnerWallet = null; this.storage.removeItem(BURNER_WALLET_PRIVATE_KEY); - this.emit(this.events.connection, false); this.emit(this.events.currentAccount, null); this.emit(this.events.accounts, []); - return this.connected; } - async signMessage(_address: string, _message: string): Promise { + async signMessage(address: string, message: string): Promise { if (!this.burnerWallet) { throw Error('Wallet not connected'); } - if (_address !== this.burnerWallet.address.toString()) { + if (address !== this.burnerWallet.address.toString()) { throw Error('Address not found for the connector'); } - const signMessage = await this.burnerWallet.signMessage(_message); + const signMessage = await this.burnerWallet.signMessage(message); return signMessage; } @@ -257,11 +210,12 @@ export class BurnerWalletConnector extends FuelConnector { } async currentNetwork(): Promise { - const network = await this.burnerWalletProvider?.getNetwork(); + const provider = await this.getProvider(); + const network = await provider.getNetwork(); return { chainId: Number(network?.chainId), - url: this.burnerWalletProvider?.url ?? '', + url: provider.url ?? '', }; } diff --git a/packages/burner-wallet-connector/src/constants.ts b/packages/burner-wallet-connector/src/constants.ts index 832e9039..405229b2 100644 --- a/packages/burner-wallet-connector/src/constants.ts +++ b/packages/burner-wallet-connector/src/constants.ts @@ -1,11 +1,7 @@ export const BURNER_WALLET_ICON = 'data:image/svg+xml;utf8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0Ny41IDQ3LjUiPjxkZWZzPjxjbGlwUGF0aCBpZD0iYSI+PHBhdGggZD0iTTAgMzhoMzhWMEgwdjM4WiIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJiIj48cGF0aCBkPSJNMTguNTgzIDI3LjgzM2MtMi45NTctLjIzMS01LjY2NiAyLjU0Mi00LjY2NiA3LjA0Mi0zLjIzOS0yLjM4Ni0zLjMzMi02LjQwMy0yLjMzMy05IDEuMDQxLTIuNzA4LS4wNDItNC45NTgtMi41ODQtNS4yMDgtMi44MzktLjI4LTQuNDE2IDMuMDQyLTIuOTYyIDguMzMzQTE2LjkzNiAxNi45MzYgMCAwIDEgMiAxOEMyIDguNjExIDkuNjExIDEgMTkgMXMxNyA3LjYxMSAxNyAxN2MwIDIuMDYzLS4zNjcgNC4wMzktMS4wNCA1Ljg2OC0uNDYtNS4zODgtMy4zMzMtOC4xNTctNi4zMzUtNi44NjgtMi44MTIgMS4yMDgtLjkxNyA1LjkxNy0uNzc3IDguMTY0LjIzNiAzLjgwOS0uMDEyIDguMTY5LTYuOTMxIDExLjc5NCAyLjg3NS01LjQ5OS4zMzMtOC45MTctMi4zMzQtOS4xMjUiLz48L2NsaXBQYXRoPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjYSkiIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUgMCAwIC0xLjI1IDAgNDcuNSkiPjxwYXRoIGZpbGw9IiNmNDkwMGMiIGQ9Ik0zNiAxOGMwIDIuMDYzLS4zNjcgNC4wMzktMS4wNCA1Ljg2OC0uNDYtNS4zODktMy4zMzMtOC4xNTctNi4zMzUtNi44NjgtMi44MTMgMS4yMDgtLjkxNyA1LjkxNy0uNzc3IDguMTY0LjIzNiAzLjgwOS0uMDEyIDguMTY5LTYuOTMxIDExLjc5NCAyLjg3NS01LjUuMzMzLTguOTE2LTIuMzM0LTkuMTI1LTIuOTU4LS4yMy01LjY2NiAyLjU0Mi00LjY2NiA3LjA0Mi0zLjIzOC0yLjM4Ni0zLjMzMy02LjQwMi0yLjMzNC05IDEuMDQyLTIuNzA4LS4wNDEtNC45NTgtMi41ODMtNS4yMDgtMi44MzktLjI4LTQuNDE3IDMuMDQxLTIuOTYyIDguMzMzQTE2LjkzNiAxNi45MzYgMCAwIDEgMiAxOEMyIDguNjExIDkuNjExIDEgMTkgMXMxNyA3LjYxMSAxNyAxNyIvPjwvZz48ZyBjbGlwLXBhdGg9InVybCgjYikiIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUgMCAwIC0xLjI1IDAgNDcuNSkiPjxwYXRoIGZpbGw9IiNmZmNjNGQiIGQ9Ik0zMSA3YzAgMi4xODctLjU4NCA0LjIzNi0xLjYwNSA2LjAwMS4xNDctMy4wODQtMi41NjItNC4yOTMtNC4wMi0zLjcwOS0yLjEwNS44NDMtMS41NDEgMi4yOTEtMi4wODMgNS4yOTEtLjU0MiAzLTIuNjI1IDUuMDg0LTUuNzA5IDYgMi4yNS02LjMzMy0xLjI0Ny04LjY2Ny0zLjA4LTkuMDg0LTEuODcyLS40MjYtMy43NTMuMDAxLTMuOTY4IDQuMDA3QTExLjk2NyAxMS45NjcgMCAwIDEgNyA3QzcgLjM3MyAxMi4zNzMtNSAxOS01UzMxIC4zNzMgMzEgNyIvPjwvZz48L3N2Zz4='; - export const BETA_5_URL = 'https://beta-5.fuel.network/graphql'; - export const HAS_WINDOW = typeof window !== 'undefined'; // biome-ignore lint/suspicious/noExplicitAny: export const WINDOW: any = HAS_WINDOW ? window : {}; - -export const BURNER_WALLET_CONNECTED = 'burner-wallet-connected'; export const BURNER_WALLET_PRIVATE_KEY = 'burner-wallet-private-key'; diff --git a/packages/burner-wallet-connector/src/tests/burnerWalletConnector.test.ts b/packages/burner-wallet-connector/src/tests/burnerWalletConnector.test.ts index 809cab16..3f50871d 100644 --- a/packages/burner-wallet-connector/src/tests/burnerWalletConnector.test.ts +++ b/packages/burner-wallet-connector/src/tests/burnerWalletConnector.test.ts @@ -9,6 +9,7 @@ import { } from 'fuels'; import { afterAll, + afterEach, beforeAll, beforeEach, describe, @@ -18,25 +19,16 @@ import { import { BurnerWalletConnector } from '../BurnerWalletConnector'; import { BURNER_WALLET_PRIVATE_KEY } from '../constants'; import type { BurnerWalletConfig } from '../types'; -import { mockedStorage } from './mockedStorage'; +import { createMockedStorage } from './mockedStorage'; describe('Burner Wallet Connector', () => { let fuelProvider: Provider; - let connector: BurnerWalletConnector; let stopProvider: () => void; const chainConfigPath = path.join(__dirname, 'chainConfig.json'); beforeAll(async () => { - Object.defineProperty(global, 'sessionStorage', { - value: mockedStorage, - }); - - Object.defineProperty(global, 'localStorage', { - value: mockedStorage, - }); - process.env.GENESIS_SECRET = '0x6e48a022f9d4ae187bca4e2645abd62198ae294ee484766edbdaadf78160dc68'; const { stop, provider } = await launchNodeAndGetWallets({ @@ -45,6 +37,7 @@ describe('Burner Wallet Connector', () => { loggingEnabled: false, }, }); + BurnerWalletConnector.defaultProviderUrl = provider.url; fuelProvider = provider; stopProvider = stop; @@ -55,59 +48,69 @@ describe('Burner Wallet Connector', () => { }); beforeEach(async () => { - connector = new BurnerWalletConnector({ - fuelProvider, + Object.defineProperty(global, 'localStorage', { + value: createMockedStorage(), }); }); + afterEach(async () => { + global.localStorage.clear(); + }); + describe('constructor()', () => { - test('creates a new BurnerWalletConnector instance', () => { + test('Creates a new BurnerWalletConnector instance using default config', async () => { + const connector = new BurnerWalletConnector(); expect(connector).to.be.an.instanceOf(BurnerWalletConnector); - expect(connector.name).to.be.equal('Burner Wallet'); expect(connector.connected).to.be.false; expect(connector.installed).to.be.false; - }); - - test('creates a new BurnerWalletConnector instance with config using localStorage', async () => { - const wallet = Wallet.generate({ - provider: fuelProvider, + expect(await connector.currentNetwork()).to.be.deep.equal({ + chainId: 0, + url: fuelProvider.url, }); + }); - localStorage.setItem(BURNER_WALLET_PRIVATE_KEY, wallet.privateKey); - + test('Creates a new BurnerWalletConnector instance with custom storage', async () => { + const storage = createMockedStorage(); + const wallet = Wallet.generate(); + storage.setItem(BURNER_WALLET_PRIVATE_KEY, wallet.privateKey); const config: BurnerWalletConfig = { - fuelProvider, - storage: localStorage as unknown as StorageAbstract, + storage, }; const connector = new BurnerWalletConnector(config); await connector.connect(); expect(connector).to.be.an.instanceOf(BurnerWalletConnector); - expect(connector.burnerWalletPrivateKey).to.be.equal(wallet.privateKey); + expect(await connector.currentAccount()).to.be.equal( + wallet.address.toString(), + ); }); test('creates a new BurnerWalletConnector instance with config using sessionStorage', async () => { + const storage = createMockedStorage(); const wallet = Wallet.generate({ provider: fuelProvider, }); - sessionStorage.setItem(BURNER_WALLET_PRIVATE_KEY, wallet.privateKey); + storage.setItem(BURNER_WALLET_PRIVATE_KEY, wallet.privateKey); const config: BurnerWalletConfig = { fuelProvider, - storage: sessionStorage as unknown as StorageAbstract, + storage, }; const connector = new BurnerWalletConnector(config); await connector.connect(); expect(connector).to.be.an.instanceOf(BurnerWalletConnector); - expect(connector.burnerWalletPrivateKey).to.be.equal(wallet.privateKey); + expect(await connector.currentAccount()).to.be.equal( + wallet.address.toString(), + ); }); }); describe('connect()', () => { - test('connect to burner wallet', async () => { + test('Connect to burner wallet', async () => { + const connector = new BurnerWalletConnector(); await connector.connect(); const connectedAfterConnect = await connector.isConnected(); @@ -117,12 +120,15 @@ describe('Burner Wallet Connector', () => { describe('isConnected()', () => { test('false when not connected', async () => { - const burnerWalletConnector = new BurnerWalletConnector(); - const connected = await burnerWalletConnector.isConnected(); + const connector = new BurnerWalletConnector(); + const connected = await connector.isConnected(); expect(connected).to.be.false; }); test('true when connected', async () => { + const wallet = Wallet.generate(); + global.localStorage.setItem(BURNER_WALLET_PRIVATE_KEY, wallet.privateKey); + const connector = new BurnerWalletConnector(); await connector.connect(); const connected = await connector.isConnected(); @@ -132,6 +138,7 @@ describe('Burner Wallet Connector', () => { describe('disconnect()', () => { test('disconnect from burner wallet', async () => { + const connector = new BurnerWalletConnector(); await connector.connect(); await connector.disconnect(); @@ -145,44 +152,45 @@ describe('Burner Wallet Connector', () => { describe('accounts()', () => { test('throws error when not connected', async () => { - const burnerWalletConnector = new BurnerWalletConnector(); + const connector = new BurnerWalletConnector(); - await expect(() => burnerWalletConnector.accounts()).rejects.toThrow( + await expect(() => connector.accounts()).rejects.toThrow( 'Wallet not connected', ); }); test('get accounts', async () => { + const connector = new BurnerWalletConnector(); await connector.connect(); const accounts = await connector.accounts(); - expect(accounts).to.deep.equal([ - connector.burnerWallet?.address.toString(), - ]); + expect(accounts.length).to.be.eq(1); }); }); describe('currentAccount()', () => { test('throws error when not connected', async () => { - const burnerWalletConnector = new BurnerWalletConnector(); + const connector = new BurnerWalletConnector(); - await expect(() => - burnerWalletConnector.currentAccount(), - ).rejects.toThrow('Wallet not connected'); + await expect(() => connector.currentAccount()).rejects.toThrow( + 'Wallet not connected', + ); }); test('get current account', async () => { + const connector = new BurnerWalletConnector(); await connector.connect(); const account = await connector.currentAccount(); - expect(account).to.be.equal(connector.burnerWallet?.address.toString()); + expect(account?.length).to.be.equal(63); }); }); describe('network()', () => { test('returns fuel network info', async () => { + const connector = new BurnerWalletConnector(); await connector.connect(); const network = await connector.currentNetwork(); @@ -196,6 +204,7 @@ describe('Burner Wallet Connector', () => { describe('networks()', () => { test('returns an array of fuel network info', async () => { + const connector = new BurnerWalletConnector(); await connector.connect(); const networks = await connector.networks(); @@ -216,12 +225,14 @@ describe('Burner Wallet Connector', () => { describe('assets()', () => { test('returns an empty array', async () => { + const connector = new BurnerWalletConnector(); expect(await connector.assets()).to.deep.equal([]); }); }); describe('addAsset()', () => { test('throws error', async () => { + const connector = new BurnerWalletConnector(); const asset: Asset = { name: '', symbol: '', @@ -236,6 +247,7 @@ describe('Burner Wallet Connector', () => { describe('addAssets()', () => { test('throws error', async () => { + const connector = new BurnerWalletConnector(); await expect(() => connector.addAssets([])).rejects.toThrowError( 'Method not implemented.', ); @@ -244,6 +256,7 @@ describe('Burner Wallet Connector', () => { describe('addAbi()', () => { test('throws error', async () => { + const connector = new BurnerWalletConnector(); await expect(() => connector.addAbi({})).rejects.toThrowError( 'Method not implemented.', ); @@ -252,6 +265,7 @@ describe('Burner Wallet Connector', () => { describe('getAbi()', () => { test('throws error', async () => { + const connector = new BurnerWalletConnector(); await expect(() => connector.getAbi('contractId')).rejects.toThrowError( 'Method not implemented.', ); @@ -270,6 +284,7 @@ describe('Burner Wallet Connector', () => { describe('addNetwork()', () => { test('throws error', async () => { + const connector = new BurnerWalletConnector(); await expect(() => connector.addNetwork('')).rejects.toThrowError( 'Method not implemented.', ); @@ -282,6 +297,7 @@ describe('Burner Wallet Connector', () => { url: '', }; test('throws error', async () => { + const connector = new BurnerWalletConnector(); await expect(() => connector.selectNetwork(network)).rejects.toThrowError( 'Method not implemented.', ); diff --git a/packages/burner-wallet-connector/src/tests/mockedStorage.ts b/packages/burner-wallet-connector/src/tests/mockedStorage.ts index 1b2b7440..73534b19 100644 --- a/packages/burner-wallet-connector/src/tests/mockedStorage.ts +++ b/packages/burner-wallet-connector/src/tests/mockedStorage.ts @@ -1,18 +1,18 @@ -export const mockedStorage: Storage = (() => { +import type { StorageAbstract } from 'fuels'; + +export const createMockedStorage: () => StorageAbstract = () => { let store: Record = {}; return { - getItem: (key: string): string => store[key] ?? null, - setItem: (key: string, value: string): void => { + getItem: async (key: string): Promise => store[key] ?? null, + setItem: async (key: string, value: string): Promise => { store[key] = value.toString(); }, - removeItem: (key: string): void => { + removeItem: async (key: string): Promise => { delete store[key]; }, - clear: (): void => { + clear: async (): Promise => { store = {}; }, - key: (_index: number): string | null => '', - length: Object.keys(store).length, }; -})(); +};