@layerswap/wallet-fuel 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/constants.js +1 -1
- package/dist/esm/fuelBalanceProvider.js +2 -2
- package/dist/esm/fuelGasProvider.js +2 -1
- package/dist/esm/index.js +24 -24
- package/dist/esm/init.js +38 -0
- package/dist/esm/service/FuelConnectionService.js +232 -0
- package/dist/esm/service/createFuelConnection.js +45 -0
- package/dist/esm/service/fuelStore.js +26 -0
- package/dist/esm/service/getFuel.js +13 -0
- package/dist/esm/service/syncFuel.js +119 -0
- package/dist/esm/transferProvider/{useFuelTransfer.js → createFuelTransfer.js} +6 -6
- package/dist/esm/utils/index.js +0 -1
- package/dist/esm/utils/resolveIcon.js +3 -8
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/fuelBalanceProvider.d.ts +1 -1
- package/dist/types/fuelGasProvider.d.ts +2 -1
- package/dist/types/fuelGasProvider.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/init.d.ts +8 -0
- package/dist/types/init.d.ts.map +1 -0
- package/dist/types/service/FuelConnectionService.d.ts +31 -0
- package/dist/types/service/FuelConnectionService.d.ts.map +1 -0
- package/dist/types/service/createFuelConnection.d.ts +11 -0
- package/dist/types/service/createFuelConnection.d.ts.map +1 -0
- package/dist/types/service/fuelStore.d.ts +14 -0
- package/dist/types/service/fuelStore.d.ts.map +1 -0
- package/dist/types/service/getFuel.d.ts +5 -0
- package/dist/types/service/getFuel.d.ts.map +1 -0
- package/dist/types/service/syncFuel.d.ts +5 -0
- package/dist/types/service/syncFuel.d.ts.map +1 -0
- package/dist/types/transferProvider/createFuelTransfer.d.ts +3 -0
- package/dist/types/transferProvider/createFuelTransfer.d.ts.map +1 -0
- package/dist/types/transferProvider/transactionBuilder.d.ts +1 -1
- package/dist/types/transferProvider/transactionBuilder.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +0 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/resolveIcon.d.ts +2 -2
- package/dist/types/utils/resolveIcon.d.ts.map +1 -1
- package/package.json +11 -15
- package/dist/esm/FuelProvider.js +0 -36
- package/dist/esm/useFuelConnection.js +0 -205
- package/dist/esm/utils/KnownFuelConnectors.js +0 -24
- package/dist/types/FuelProvider.d.ts +0 -6
- package/dist/types/FuelProvider.d.ts.map +0 -1
- package/dist/types/transferProvider/useFuelTransfer.d.ts +0 -3
- package/dist/types/transferProvider/useFuelTransfer.d.ts.map +0 -1
- package/dist/types/useFuelConnection.d.ts +0 -3
- package/dist/types/useFuelConnection.d.ts.map +0 -1
- package/dist/types/utils/KnownFuelConnectors.d.ts +0 -6
- package/dist/types/utils/KnownFuelConnectors.d.ts.map +0 -1
package/dist/esm/constants.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { retryWithExponentialBackoff, fetchWithTimeout, KnownInternalNames, formatUnits } from "@layerswap/
|
|
2
|
-
import { BalanceProvider } from "@layerswap/widget
|
|
1
|
+
import { retryWithExponentialBackoff, fetchWithTimeout, KnownInternalNames, formatUnits } from "@layerswap/utils";
|
|
2
|
+
import { BalanceProvider } from "@layerswap/widget-types";
|
|
3
3
|
export class FuelBalanceProvider extends BalanceProvider {
|
|
4
4
|
constructor() {
|
|
5
5
|
super(...arguments);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import WatchdogAbi from './jsons/FUELWATCHDOG.json';
|
|
2
|
-
import {
|
|
2
|
+
import { KnownInternalNames, formatUnits } from "@layerswap/utils";
|
|
3
|
+
import { ErrorHandler } from "@layerswap/widget-types";
|
|
3
4
|
export class FuelGasProvider {
|
|
4
5
|
supportsNetwork(network) {
|
|
5
6
|
return (KnownInternalNames.Networks.FuelMainnet.includes(network.name) || KnownInternalNames.Networks.FuelTestnet.includes(network.name));
|
package/dist/esm/index.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
1
|
import { FuelBalanceProvider } from "./fuelBalanceProvider";
|
|
3
2
|
import { FuelGasProvider } from "./fuelGasProvider";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
3
|
+
import { createFuelTransfer } from "./transferProvider/createFuelTransfer";
|
|
4
|
+
import { createFuelConnection } from "./service/createFuelConnection";
|
|
5
|
+
import { initFuelProvider } from "./init";
|
|
6
|
+
import { id } from "./constants";
|
|
7
|
+
// The literal id in the return type lets `defineWalletDescriptor` in
|
|
8
|
+
// `@layerswap/wallets` verify it matches the descriptor id at compile time.
|
|
8
9
|
export function createFuelProvider(config = {}) {
|
|
9
|
-
const {
|
|
10
|
-
const
|
|
11
|
-
|
|
10
|
+
const { customConnection, balanceProviders, gasProviders, transferProviders, } = config;
|
|
11
|
+
const init = (_ctx) => {
|
|
12
|
+
initFuelProvider();
|
|
13
|
+
// No-op disposer; init is idempotent across remounts.
|
|
14
|
+
};
|
|
15
|
+
const createConnection = (props) => {
|
|
16
|
+
initFuelProvider();
|
|
17
|
+
if (customConnection) {
|
|
18
|
+
return customConnection(props);
|
|
19
|
+
}
|
|
20
|
+
return createFuelConnection(props);
|
|
12
21
|
};
|
|
13
|
-
const walletConnectionProvider = customHook || useFuelConnection;
|
|
14
22
|
const defaultBalanceProviders = [new FuelBalanceProvider()];
|
|
15
23
|
const finalBalanceProviders = balanceProviders !== undefined
|
|
16
24
|
? (Array.isArray(balanceProviders) ? balanceProviders : [balanceProviders])
|
|
@@ -19,27 +27,19 @@ export function createFuelProvider(config = {}) {
|
|
|
19
27
|
const finalGasProviders = gasProviders !== undefined
|
|
20
28
|
? (Array.isArray(gasProviders) ? gasProviders : [gasProviders])
|
|
21
29
|
: defaultGasProviders;
|
|
22
|
-
const defaultTransferProviders = [
|
|
30
|
+
const defaultTransferProviders = [createFuelTransfer];
|
|
23
31
|
const finalTransferProviders = transferProviders !== undefined
|
|
24
32
|
? (Array.isArray(transferProviders) ? transferProviders : [transferProviders])
|
|
25
33
|
: defaultTransferProviders;
|
|
26
34
|
return {
|
|
27
|
-
id
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
id,
|
|
36
|
+
init,
|
|
37
|
+
createConnection,
|
|
30
38
|
gasProvider: finalGasProviders,
|
|
31
39
|
balanceProvider: finalBalanceProviders,
|
|
32
40
|
transferProvider: finalTransferProviders,
|
|
33
41
|
};
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
export const FuelProvider = {
|
|
39
|
-
id: "fuel",
|
|
40
|
-
wrapper: FuelProviderWrapper,
|
|
41
|
-
walletConnectionProvider: useFuelConnection,
|
|
42
|
-
gasProvider: [new FuelGasProvider()],
|
|
43
|
-
balanceProvider: [new FuelBalanceProvider()],
|
|
44
|
-
transferProvider: [useFuelTransfer],
|
|
45
|
-
};
|
|
43
|
+
export { createFuelConnection } from "./service/createFuelConnection";
|
|
44
|
+
export { useFuelStore } from "./service/fuelStore";
|
|
45
|
+
export { getFuelInstance, hasFuelInstance } from "./service/getFuel";
|
package/dist/esm/init.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Fuel } from '@fuel-ts/account';
|
|
2
|
+
import { BakoSafeConnector } from './connectors/bako-safe';
|
|
3
|
+
import { BakoRequestAPI } from './connectors/bako-safe/Bako';
|
|
4
|
+
import { FuelWalletConnector } from './connectors/fuel-wallet';
|
|
5
|
+
import { FueletWalletConnector } from './connectors/fuelet-wallet';
|
|
6
|
+
import { hasFuelInstance, setFuelInstance } from './service/getFuel';
|
|
7
|
+
import { attachFuelSync } from './service/syncFuel';
|
|
8
|
+
const HOST_URL = 'https://api.bako.global';
|
|
9
|
+
let _initialized = false;
|
|
10
|
+
/**
|
|
11
|
+
* One-shot initialization of the Fuel SDK + store sync.
|
|
12
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
13
|
+
*/
|
|
14
|
+
export function initFuelProvider() {
|
|
15
|
+
if (_initialized)
|
|
16
|
+
return;
|
|
17
|
+
if (typeof window === 'undefined')
|
|
18
|
+
return;
|
|
19
|
+
if (hasFuelInstance()) {
|
|
20
|
+
_initialized = true;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const fuel = new Fuel({
|
|
24
|
+
connectors: [
|
|
25
|
+
new FuelWalletConnector(),
|
|
26
|
+
new BakoSafeConnector({ api: new BakoRequestAPI(HOST_URL) }),
|
|
27
|
+
new FueletWalletConnector(),
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
setFuelInstance(fuel);
|
|
31
|
+
attachFuelSync(fuel);
|
|
32
|
+
_initialized = true;
|
|
33
|
+
}
|
|
34
|
+
/** Visible for tests. Resets the singleton init so a fresh init can run. */
|
|
35
|
+
export function _resetFuelInit() {
|
|
36
|
+
_initialized = false;
|
|
37
|
+
setFuelInstance(null);
|
|
38
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { sleep } from "@layerswap/utils";
|
|
2
|
+
import { Address } from '@fuel-ts/address';
|
|
3
|
+
import { name as PROVIDER_NAME, id as PROVIDER_ID } from '../constants';
|
|
4
|
+
import { BAKO_STATE } from '../connectors/bako-safe/Bako';
|
|
5
|
+
import { resolveFuelWalletConnectorIcon } from '../utils';
|
|
6
|
+
import { useFuelStore } from './fuelStore';
|
|
7
|
+
export class FuelConnectionService {
|
|
8
|
+
constructor() {
|
|
9
|
+
this._networks = [];
|
|
10
|
+
this._networksKey = '';
|
|
11
|
+
// `connectWallet` mutates the module-global `BAKO_STATE.{state.last_req,
|
|
12
|
+
// period_durtion}` while connecting. Rapid double-clicks or a retry firing
|
|
13
|
+
// while a connect is still in flight would interleave those writes. Chain
|
|
14
|
+
// connects through this promise so they run one-at-a-time.
|
|
15
|
+
this._connectQueue = Promise.resolve();
|
|
16
|
+
}
|
|
17
|
+
setNetworks(networks, networkAdapter) {
|
|
18
|
+
const key = networks.map(network => networkAdapter.getId(network)).join('|');
|
|
19
|
+
if (this._networksKey === key)
|
|
20
|
+
return;
|
|
21
|
+
this._networks = networks;
|
|
22
|
+
this._networkAdapter = networkAdapter;
|
|
23
|
+
this._networksKey = key;
|
|
24
|
+
}
|
|
25
|
+
addWallet(wallet) {
|
|
26
|
+
useFuelStore.getState().connectWallet(wallet);
|
|
27
|
+
}
|
|
28
|
+
removeWallet(connectorName) {
|
|
29
|
+
useFuelStore.getState().disconnectWallet(connectorName);
|
|
30
|
+
}
|
|
31
|
+
getConnectedWallets() {
|
|
32
|
+
return useFuelStore.getState().connectedWallets;
|
|
33
|
+
}
|
|
34
|
+
getSupportedNetworks() {
|
|
35
|
+
return this._networks
|
|
36
|
+
.filter(network => this._networkAdapter?.isFuelNetwork(network))
|
|
37
|
+
.map(network => this._networkAdapter?.getId(network))
|
|
38
|
+
.filter((id) => !!id);
|
|
39
|
+
}
|
|
40
|
+
getNetworkIcon() {
|
|
41
|
+
const network = this._networks.find(item => this._networkAdapter?.isFuelNetwork(item));
|
|
42
|
+
return network && this._networkAdapter ? this._networkAdapter.getIcon(network) : undefined;
|
|
43
|
+
}
|
|
44
|
+
// Connector icons come either as a URL string or as a { dark, light } pair
|
|
45
|
+
// of raw SVG markup; the latter is wrapped as a base64 data URL unless it
|
|
46
|
+
// already is one. Shared by getAvailableConnectors and resolveFuelWallet.
|
|
47
|
+
resolveConnectorImageIcon(image) {
|
|
48
|
+
if (!image)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (typeof image === 'string')
|
|
51
|
+
return image;
|
|
52
|
+
return image.dark.startsWith('data:')
|
|
53
|
+
? image.dark
|
|
54
|
+
: `data:image/svg+xml;base64,${btoa(image.dark)}`;
|
|
55
|
+
}
|
|
56
|
+
getAvailableConnectors() {
|
|
57
|
+
const connectors = useFuelStore.getState().connectors;
|
|
58
|
+
return connectors.map(c => {
|
|
59
|
+
const icon = this.resolveConnectorImageIcon(c.metadata.image);
|
|
60
|
+
const isInstalled = c.installed && !c['dAppWindow'];
|
|
61
|
+
const isLoadable = c.metadata?.install?.action !== 'Install';
|
|
62
|
+
return {
|
|
63
|
+
name: c.name,
|
|
64
|
+
id: c.name,
|
|
65
|
+
icon,
|
|
66
|
+
type: isInstalled ? 'injected' : 'other',
|
|
67
|
+
installUrl: c.metadata.install.link,
|
|
68
|
+
extensionNotFound: !c.installed,
|
|
69
|
+
isLoadable,
|
|
70
|
+
providerName: PROVIDER_NAME,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async resolveFuelWallet(connector, address, addresses) {
|
|
75
|
+
const network = await connector.currentNetwork();
|
|
76
|
+
const chainId = network.chainId ?? (network.url.toLowerCase().includes('testnet') ? 0 : 9889);
|
|
77
|
+
const icon = this.resolveConnectorImageIcon(connector.metadata.image);
|
|
78
|
+
return {
|
|
79
|
+
id: connector.name,
|
|
80
|
+
address,
|
|
81
|
+
addresses,
|
|
82
|
+
isActive: true,
|
|
83
|
+
chainId,
|
|
84
|
+
disconnect: () => this.disconnectWallet(connector.name),
|
|
85
|
+
displayName: `${connector.name} - Fuel`,
|
|
86
|
+
providerName: PROVIDER_NAME,
|
|
87
|
+
icon: resolveFuelWalletConnectorIcon({ connector: connector.name, address, iconUrl: icon }),
|
|
88
|
+
autofillSupportedNetworks: this.getSupportedNetworks(),
|
|
89
|
+
withdrawalSupportedNetworks: this.getSupportedNetworks(),
|
|
90
|
+
asSourceSupportedNetworks: this.getSupportedNetworks(),
|
|
91
|
+
networkIcon: this.getNetworkIcon(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
connectWallet({ connector }) {
|
|
95
|
+
// Serialize against any in-flight connect (see `_connectQueue`). The
|
|
96
|
+
// `.then(run, run)` runs `run` whether the prior connect resolved or
|
|
97
|
+
// rejected, so one failure doesn't wedge the queue.
|
|
98
|
+
const run = () => this.doConnectWallet({ connector });
|
|
99
|
+
const result = this._connectQueue.then(run, run);
|
|
100
|
+
this._connectQueue = result.catch(() => { });
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
async doConnectWallet({ connector }) {
|
|
104
|
+
const attemptConnection = async (isRetry = false) => {
|
|
105
|
+
try {
|
|
106
|
+
const fuelConnector = useFuelStore.getState().connectors.find(w => w.name === connector.name);
|
|
107
|
+
BAKO_STATE.state.last_req = undefined;
|
|
108
|
+
BAKO_STATE.period_durtion = 120000;
|
|
109
|
+
await fuelConnector?.connect();
|
|
110
|
+
const addresses = (await fuelConnector?.accounts())?.map(a => new Address(a).toB256());
|
|
111
|
+
if (!addresses || !fuelConnector)
|
|
112
|
+
return undefined;
|
|
113
|
+
const result = await this.resolveFuelWallet(fuelConnector, addresses[0], addresses);
|
|
114
|
+
this.addWallet(result);
|
|
115
|
+
await this.switchAccount(result);
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
if (connector.name === 'Bako Safe' && e === false && !isRetry) {
|
|
120
|
+
console.log('Bako Safe connection failed with false, retrying once...');
|
|
121
|
+
await sleep(1000);
|
|
122
|
+
return await attemptConnection(true);
|
|
123
|
+
}
|
|
124
|
+
throw e instanceof Error ? e : new Error(typeof e === 'string' ? e : JSON.stringify(e));
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
return attemptConnection();
|
|
128
|
+
}
|
|
129
|
+
async disconnectWallet(connectorName) {
|
|
130
|
+
try {
|
|
131
|
+
const fuelConnector = useFuelStore.getState().connectors.find(c => c.name === connectorName);
|
|
132
|
+
if (!fuelConnector)
|
|
133
|
+
throw new Error('Connector not found');
|
|
134
|
+
await fuelConnector.disconnect();
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
// Disconnect is best-effort — log but do not rethrow; the wallet is
|
|
138
|
+
// removed from the store in `finally` regardless.
|
|
139
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
140
|
+
console.error(`[Fuel] Failed to disconnect ${connectorName}: ${msg}`);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
this.removeWallet(connectorName);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async disconnectWallets() {
|
|
147
|
+
try {
|
|
148
|
+
BAKO_STATE.state.last_req = undefined;
|
|
149
|
+
BAKO_STATE.period_durtion = 10000;
|
|
150
|
+
const connectors = useFuelStore.getState().connectors;
|
|
151
|
+
for (const connector of connectors.filter(c => c.connected)) {
|
|
152
|
+
await connector.disconnect();
|
|
153
|
+
this.removeWallet();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
// Best-effort bulk disconnect — log but do not rethrow.
|
|
158
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
159
|
+
console.error(`[Fuel] Failed to disconnect wallets: ${msg}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async switchAccount(wallet) {
|
|
163
|
+
try {
|
|
164
|
+
const fuel = useFuelStore.getState().fuel;
|
|
165
|
+
if (!fuel)
|
|
166
|
+
throw new Error('Fuel instance not available');
|
|
167
|
+
const res = await fuel.selectConnector(wallet.id);
|
|
168
|
+
if (!res)
|
|
169
|
+
throw new Error('Could not switch account');
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
173
|
+
console.error(`[Fuel] Failed to switch account to ${wallet.id}: ${msg}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async switchChain(connector, chainId) {
|
|
177
|
+
try {
|
|
178
|
+
const fuelConnector = useFuelStore.getState().connectors.find(c => c.name === connector.id);
|
|
179
|
+
if (!fuelConnector)
|
|
180
|
+
throw new Error('Connector not found');
|
|
181
|
+
const res = await fuelConnector.selectNetwork({ chainId: Number(chainId) });
|
|
182
|
+
if (!res)
|
|
183
|
+
throw new Error('Could not switch chain');
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
187
|
+
console.error(`[Fuel] Failed to switch chain to ${chainId}: ${msg}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async resolveConnectedWallets() {
|
|
191
|
+
const connectors = useFuelStore.getState().connectors.filter(c => c.connected);
|
|
192
|
+
const wallets = [];
|
|
193
|
+
for (const connector of connectors) {
|
|
194
|
+
try {
|
|
195
|
+
const addresses = (await connector.accounts()).map(a => Address.fromAddressOrString(a).toB256());
|
|
196
|
+
if (connector.connected && addresses.length > 0) {
|
|
197
|
+
const w = await this.resolveFuelWallet(connector, addresses[0], addresses);
|
|
198
|
+
wallets.push(w);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
203
|
+
console.error(`[Fuel] Failed to resolve connected wallet for ${connector.name}: ${msg}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return wallets;
|
|
207
|
+
}
|
|
208
|
+
async syncConnectedWallets() {
|
|
209
|
+
const wallets = await this.resolveConnectedWallets();
|
|
210
|
+
for (const wallet of wallets) {
|
|
211
|
+
this.addWallet(wallet);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
buildProvider() {
|
|
215
|
+
const connectedWallets = this.getConnectedWallets();
|
|
216
|
+
return {
|
|
217
|
+
connectWallet: this.connectWallet.bind(this),
|
|
218
|
+
disconnectWallets: this.disconnectWallets.bind(this),
|
|
219
|
+
switchAccount: this.switchAccount.bind(this),
|
|
220
|
+
switchChain: this.switchChain.bind(this),
|
|
221
|
+
availableConnectors: this.getAvailableConnectors(),
|
|
222
|
+
autofillSupportedNetworks: this.getSupportedNetworks(),
|
|
223
|
+
withdrawalSupportedNetworks: this.getSupportedNetworks(),
|
|
224
|
+
asSourceSupportedNetworks: this.getSupportedNetworks(),
|
|
225
|
+
activeWallet: connectedWallets[0],
|
|
226
|
+
connectedWallets,
|
|
227
|
+
name: PROVIDER_NAME,
|
|
228
|
+
id: PROVIDER_ID,
|
|
229
|
+
ready: useFuelStore.getState().ready,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createMemoizedConnectionStore } from "@layerswap/wallet-core";
|
|
2
|
+
import { createFuelTransfer } from '../transferProvider/createFuelTransfer';
|
|
3
|
+
import { FuelConnectionService } from './FuelConnectionService';
|
|
4
|
+
import { registerFuelWalletSynchronizer } from './syncFuel';
|
|
5
|
+
import { useFuelStore } from './fuelStore';
|
|
6
|
+
/**
|
|
7
|
+
* Vanilla external-store factory for the Fuel wallet connection. Replaces the
|
|
8
|
+
* old `useFuelConnection` hook.
|
|
9
|
+
*/
|
|
10
|
+
export function createFuelConnection(initialProps, options = {}) {
|
|
11
|
+
const { extraMultiStepHandlers = [] } = options;
|
|
12
|
+
let networks = initialProps.networks;
|
|
13
|
+
let networkAdapter = initialProps.networkAdapter;
|
|
14
|
+
const fuelConnectionService = new FuelConnectionService();
|
|
15
|
+
fuelConnectionService.setNetworks(networks, networkAdapter);
|
|
16
|
+
const unregisterWalletSynchronizer = registerFuelWalletSynchronizer(() => fuelConnectionService.syncConnectedWallets());
|
|
17
|
+
const transferProvider = createFuelTransfer();
|
|
18
|
+
const transfer = transferProvider.executeTransfer;
|
|
19
|
+
return createMemoizedConnectionStore({
|
|
20
|
+
computeInputs: () => {
|
|
21
|
+
const fuelState = useFuelStore.getState();
|
|
22
|
+
return {
|
|
23
|
+
connectors: fuelState.connectors,
|
|
24
|
+
fuel: fuelState.fuel,
|
|
25
|
+
ready: fuelState.ready,
|
|
26
|
+
connectedWallets: fuelState.connectedWallets,
|
|
27
|
+
networks,
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
buildSnapshot: () => ({
|
|
31
|
+
...fuelConnectionService.buildProvider(),
|
|
32
|
+
transfer,
|
|
33
|
+
multiStepHandlers: extraMultiStepHandlers,
|
|
34
|
+
}),
|
|
35
|
+
subscribe: sync => [
|
|
36
|
+
useFuelStore.subscribe(sync),
|
|
37
|
+
],
|
|
38
|
+
onUpdateProps: nextProps => {
|
|
39
|
+
networks = nextProps.networks;
|
|
40
|
+
networkAdapter = nextProps.networkAdapter;
|
|
41
|
+
fuelConnectionService.setNetworks(networks, networkAdapter);
|
|
42
|
+
},
|
|
43
|
+
onDestroy: unregisterWalletSynchronizer,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
export const useFuelStore = create()((set) => ({
|
|
3
|
+
connectors: [],
|
|
4
|
+
fuel: undefined,
|
|
5
|
+
ready: false,
|
|
6
|
+
connectedWallets: [],
|
|
7
|
+
// `fuel.connectors()` returns the SAME array instance on every call and
|
|
8
|
+
// mutates `installed`/`connected` on the connector objects in place. A
|
|
9
|
+
// stored reference would defeat every downstream identity memo — the
|
|
10
|
+
// provider snapshot would never recompute after its first build, freezing
|
|
11
|
+
// stale `extensionNotFound` flags in the UI. Copy on write so every
|
|
12
|
+
// publish is a fresh identity.
|
|
13
|
+
_setConnectors: (connectors) => set({ connectors: [...connectors], ready: connectors.length > 0 }),
|
|
14
|
+
_setFuel: (fuel) => set({ fuel }),
|
|
15
|
+
// Mirrors the global wallet store's dedupe-by-(id,address) semantics so the
|
|
16
|
+
// same connector reconnecting replaces its entry instead of appending.
|
|
17
|
+
connectWallet: (wallet) => set((state) => {
|
|
18
|
+
const filtered = state.connectedWallets.filter(w => !(w.id === wallet.id && w.address === wallet.address));
|
|
19
|
+
return { connectedWallets: [...filtered, wallet] };
|
|
20
|
+
}),
|
|
21
|
+
disconnectWallet: (connectorName) => set((state) => ({
|
|
22
|
+
connectedWallets: connectorName
|
|
23
|
+
? state.connectedWallets.filter(w => w.id !== connectorName)
|
|
24
|
+
: [],
|
|
25
|
+
})),
|
|
26
|
+
}));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
let _instance = null;
|
|
2
|
+
export function setFuelInstance(instance) {
|
|
3
|
+
_instance = instance;
|
|
4
|
+
}
|
|
5
|
+
export function getFuelInstance() {
|
|
6
|
+
if (!_instance) {
|
|
7
|
+
throw new Error('Fuel instance requested before initFuelProvider() ran');
|
|
8
|
+
}
|
|
9
|
+
return _instance;
|
|
10
|
+
}
|
|
11
|
+
export function hasFuelInstance() {
|
|
12
|
+
return _instance !== null;
|
|
13
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { FuelConnectorEventTypes } from '@fuel-ts/account';
|
|
2
|
+
import { useFuelStore } from './fuelStore';
|
|
3
|
+
let _attached = false;
|
|
4
|
+
let _dispose = null;
|
|
5
|
+
let _fuel = null;
|
|
6
|
+
const walletSynchronizers = new Set();
|
|
7
|
+
export function registerFuelWalletSynchronizer(sync) {
|
|
8
|
+
walletSynchronizers.add(sync);
|
|
9
|
+
return () => {
|
|
10
|
+
walletSynchronizers.delete(sync);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
async function syncConnectedWallets() {
|
|
14
|
+
await Promise.allSettled([...walletSynchronizers].map(sync => sync()));
|
|
15
|
+
}
|
|
16
|
+
export function attachFuelSync(fuel) {
|
|
17
|
+
if (_fuel === fuel && _dispose)
|
|
18
|
+
return _dispose;
|
|
19
|
+
_dispose?.();
|
|
20
|
+
useFuelStore.getState()._setFuel(fuel);
|
|
21
|
+
const refreshConnectors = async () => {
|
|
22
|
+
try {
|
|
23
|
+
const connectors = await fuel.connectors();
|
|
24
|
+
useFuelStore.getState()._setConnectors(connectors);
|
|
25
|
+
// Re-attach per-connector network listeners whenever the list changes
|
|
26
|
+
attachConnectorNetworkListeners(connectors);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// swallow
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const onConnection = () => {
|
|
33
|
+
syncConnectedWallets().catch(() => { });
|
|
34
|
+
};
|
|
35
|
+
const onCurrentConnector = () => {
|
|
36
|
+
syncConnectedWallets().catch(() => { });
|
|
37
|
+
};
|
|
38
|
+
let perConnectorDisposers = [];
|
|
39
|
+
function attachConnectorNetworkListeners(connectors) {
|
|
40
|
+
// Detach previous listeners
|
|
41
|
+
perConnectorDisposers.forEach(fn => fn());
|
|
42
|
+
perConnectorDisposers = [];
|
|
43
|
+
for (const c of connectors) {
|
|
44
|
+
const handler = async () => {
|
|
45
|
+
await syncConnectedWallets();
|
|
46
|
+
};
|
|
47
|
+
c.on(FuelConnectorEventTypes.currentNetwork, handler);
|
|
48
|
+
perConnectorDisposers.push(() => c.off(FuelConnectorEventTypes.currentNetwork, handler));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Refresh connector detection flags, then sync sessions the refreshed
|
|
52
|
+
// flags reveal. Shared by initial population, bounded re-detection and
|
|
53
|
+
// the window-focus refresh below.
|
|
54
|
+
const refreshAll = () => refreshConnectors()
|
|
55
|
+
.then(() => syncConnectedWallets())
|
|
56
|
+
.catch(() => { });
|
|
57
|
+
const onConnectors = () => { void refreshAll(); };
|
|
58
|
+
let retryTimers = [];
|
|
59
|
+
const onWindowFocus = () => { void refreshAll(); };
|
|
60
|
+
let disposed = false;
|
|
61
|
+
const dispose = () => {
|
|
62
|
+
if (disposed)
|
|
63
|
+
return;
|
|
64
|
+
disposed = true;
|
|
65
|
+
fuel.off(FuelConnectorEventTypes.connectors, onConnectors);
|
|
66
|
+
fuel.off(FuelConnectorEventTypes.connection, onConnection);
|
|
67
|
+
fuel.off(FuelConnectorEventTypes.currentConnector, onCurrentConnector);
|
|
68
|
+
perConnectorDisposers.forEach(fn => fn());
|
|
69
|
+
perConnectorDisposers = [];
|
|
70
|
+
retryTimers.forEach(t => clearTimeout(t));
|
|
71
|
+
retryTimers = [];
|
|
72
|
+
if (typeof window !== 'undefined')
|
|
73
|
+
window.removeEventListener('focus', onWindowFocus);
|
|
74
|
+
if (_dispose !== dispose)
|
|
75
|
+
return;
|
|
76
|
+
_attached = false;
|
|
77
|
+
_dispose = null;
|
|
78
|
+
_fuel = null;
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
fuel.on(FuelConnectorEventTypes.connectors, onConnectors);
|
|
82
|
+
fuel.on(FuelConnectorEventTypes.connection, onConnection);
|
|
83
|
+
fuel.on(FuelConnectorEventTypes.currentConnector, onCurrentConnector);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
dispose();
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
_attached = true;
|
|
90
|
+
_fuel = fuel;
|
|
91
|
+
_dispose = dispose;
|
|
92
|
+
// Initial population. The SDK restores the stored connector during Fuel's
|
|
93
|
+
// constructor with `emitEvents: false`, which skips the `connection` event,
|
|
94
|
+
// and its early `currentConnector` emit races the store population above —
|
|
95
|
+
// so no event reliably announces a restored session. `fuel.connectors()`
|
|
96
|
+
// refreshes each connector's `connected` flag, so a sync chained after it
|
|
97
|
+
// picks the restored session up deterministically.
|
|
98
|
+
void refreshAll();
|
|
99
|
+
// Detection is not one-shot-reliable: extension content scripts and MV3
|
|
100
|
+
// service workers can come up after the initial ping, and a wallet that
|
|
101
|
+
// announced itself before our listeners attached is silent forever after.
|
|
102
|
+
// The pre-refactor @fuels/react layer re-polled connectors; mirror that
|
|
103
|
+
// with a bounded re-detection pass (spaced beyond the SDK's 5s ping cache)
|
|
104
|
+
// plus a refresh on window focus — which also picks up an extension the
|
|
105
|
+
// user just installed from the "Install" prompt, or unlocked via popup.
|
|
106
|
+
if (typeof window !== 'undefined') {
|
|
107
|
+
const RETRY_DELAYS_MS = [6000, 14000];
|
|
108
|
+
retryTimers = RETRY_DELAYS_MS.map(delay => setTimeout(() => {
|
|
109
|
+
const connectors = useFuelStore.getState().connectors;
|
|
110
|
+
if (connectors.length === 0 || connectors.some(c => !c.installed))
|
|
111
|
+
void refreshAll();
|
|
112
|
+
}, delay));
|
|
113
|
+
window.addEventListener('focus', onWindowFocus);
|
|
114
|
+
}
|
|
115
|
+
return _dispose;
|
|
116
|
+
}
|
|
117
|
+
export function isFuelSyncAttached() {
|
|
118
|
+
return _attached;
|
|
119
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { ActionMessageType } from
|
|
2
|
-
import { useFuel } from '@fuels/react';
|
|
1
|
+
import { ActionMessageType } from '@layerswap/widget-types';
|
|
3
2
|
import { Provider } from '@fuel-ts/account';
|
|
4
3
|
import { transactionBuilder } from "./transactionBuilder";
|
|
5
|
-
import { KnownInternalNames } from "@layerswap/
|
|
4
|
+
import { KnownInternalNames } from "@layerswap/utils";
|
|
5
|
+
import { getFuelInstance, hasFuelInstance } from "../service/getFuel";
|
|
6
6
|
const supportedNetworks = [
|
|
7
7
|
KnownInternalNames.Networks.FuelTestnet,
|
|
8
8
|
KnownInternalNames.Networks.FuelDevnet,
|
|
9
9
|
KnownInternalNames.Networks.FuelMainnet
|
|
10
10
|
];
|
|
11
|
-
export function
|
|
12
|
-
const { fuel } = useFuel();
|
|
11
|
+
export function createFuelTransfer() {
|
|
13
12
|
return {
|
|
14
13
|
supportsNetwork(network) {
|
|
15
14
|
return supportedNetworks.includes(network.name);
|
|
16
15
|
},
|
|
17
16
|
async executeTransfer(params) {
|
|
18
|
-
if (!
|
|
17
|
+
if (!hasFuelInstance()) {
|
|
19
18
|
throw new Error("Fuel not initialized");
|
|
20
19
|
}
|
|
20
|
+
const fuel = getFuelInstance();
|
|
21
21
|
const { callData, network, selectedWallet, swapId } = params;
|
|
22
22
|
const fuelProvider = new Provider(network.node_url);
|
|
23
23
|
const fuelWallet = await fuel.getWallet(selectedWallet.address, fuelProvider);
|
package/dist/esm/utils/index.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const knownConnector = KnownFuelConnectors.find(c => c.id.toLowerCase() === connector?.toLowerCase());
|
|
5
|
-
if (knownConnector && knownConnector.icon)
|
|
6
|
-
return knownConnector.icon;
|
|
7
|
-
else
|
|
8
|
-
return walletIconResolver(address, iconUrl);
|
|
1
|
+
import { resolveWalletIconString } from "@layerswap/wallet-core";
|
|
2
|
+
export const resolveFuelWalletConnectorIcon = ({ connector, iconUrl }) => {
|
|
3
|
+
return resolveWalletIconString({ id: connector, iconUrl });
|
|
9
4
|
};
|