Skip to content

Commit f91aa15

Browse files
authored
feat: wallet connector (FuelLabs#1699)
1 parent 9c321f0 commit f91aa15

40 files changed

+2184
-52
lines changed

.changeset/tasty-falcons-sing.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@fuel-ts/account": patch
3+
---
4+
5+
implement wallet connectors

.eslintrc.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ module.exports = {
2929
],
3030
'@typescript-eslint/no-non-null-assertion': 1,
3131
// Disable error on devDependencies importing since this isn't a TS library
32+
'require-await': 'off',
33+
'@typescript-eslint/require-await': 'error',
3234
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
3335
'no-await-in-loop': 0,
3436
'prefer-destructuring': 0,
3537
'no-bitwise': 0,
3638
'no-underscore-dangle': 'off',
3739
'class-methods-use-this': 'off',
3840
'no-plusplus': 'off',
39-
'no-param-reassign': ['error', { props: false }],
4041
'@typescript-eslint/no-inferrable-types': 'off',
4142
'@typescript-eslint/lines-between-class-members': [
4243
'error',

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"test:watch": "vitest --watch --config vite.node.config.mts $(scripts/tests-find.sh --node)",
2323
"test:validate": "./scripts/tests-validate.sh",
2424
"test:browser": "vitest --run --coverage --config vite.browser.config.mts $(scripts/tests-find.sh --browser)",
25+
"test:browser:filter": "vitest --run --coverage --config vite.browser.config.mts",
2526
"test:e2e": "vitest --run --config vite.node.config.mts $(scripts/tests-find.sh --e2e)",
2627
"lint": "run-s lint:check prettier:check",
2728
"lint:check": "eslint . --ext .ts --max-warnings 0",

packages/abi-coder/src/coders/v0/struct.ts

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export class StructCoder<TCoders extends Record<string, Coder>> extends Coder<
7474
newOffset += getWordSizePadding(newOffset);
7575
}
7676

77+
// eslint-disable-next-line no-param-reassign
7778
obj[fieldName as keyof DecodedValueOf<TCoders>] = decoded;
7879
return obj;
7980
}, {} as DecodedValueOf<TCoders>);

packages/abi-coder/src/coders/v1/struct.ts

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class StructCoder<TCoders extends Record<string, Coder>> extends Coder<
4242
let decoded;
4343
[decoded, newOffset] = fieldCoder.decode(data, newOffset);
4444

45+
// eslint-disable-next-line no-param-reassign
4546
obj[fieldName as keyof DecodedValueOf<TCoders>] = decoded;
4647
return obj;
4748
}, {} as DecodedValueOf<TCoders>);

packages/account/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,11 @@
5656
"@fuel-ts/hasher": "workspace:*",
5757
"@fuel-ts/interfaces": "workspace:*",
5858
"@fuel-ts/math": "workspace:*",
59+
"@fuel-ts/merkle": "workspace:*",
5960
"@fuel-ts/transactions": "workspace:*",
6061
"@fuel-ts/utils": "workspace:*",
61-
"@fuel-ts/merkle": "workspace:*",
6262
"@fuel-ts/versions": "workspace:*",
63+
"@fuels/assets": "^0.1.4",
6364
"@fuels/vm-asm": "0.42.1",
6465
"graphql": "^16.6.0",
6566
"graphql-request": "5.0.0",
@@ -68,7 +69,9 @@
6869
"tai64": "^1.0.0",
6970
"events": "^3.3.0",
7071
"@noble/curves": "^1.3.0",
72+
"dexie-observable": "4.0.1-beta.13",
7173
"ethers": "^6.7.1",
74+
"json-rpc-2.0": "^1.7.0",
7275
"portfinder": "^1.0.32",
7376
"tree-kill": "^1.2.2",
7477
"uuid": "^9.0.0"

packages/account/src/account.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { bn } from '@fuel-ts/math';
88
import { getBytesCopy } from 'ethers';
99
import type { BytesLike } from 'ethers';
1010

11+
import type { FuelConnector } from './connectors';
1112
import type {
1213
TransactionRequestLike,
1314
CallResult,
@@ -18,10 +19,10 @@ import type {
1819
Message,
1920
Resource,
2021
ExcludeResourcesOption,
21-
TransactionResponse,
2222
Provider,
2323
ScriptTransactionRequestLike,
2424
ProviderSendTxParams,
25+
TransactionResponse,
2526
} from './providers';
2627
import {
2728
withdrawScript,
@@ -50,15 +51,18 @@ export class Account extends AbstractAccount {
5051
*/
5152
protected _provider?: Provider;
5253

54+
protected _connector?: FuelConnector;
55+
5356
/**
5457
* Creates a new Account instance.
5558
*
5659
* @param address - The address of the account.
5760
* @param provider - A Provider instance (optional).
5861
*/
59-
constructor(address: string | AbstractAddress, provider?: Provider) {
62+
constructor(address: string | AbstractAddress, provider?: Provider, connector?: FuelConnector) {
6063
super();
6164
this._provider = provider;
65+
this._connector = connector;
6266
this.address = Address.fromDynamicInput(address);
6367
}
6468

@@ -478,6 +482,13 @@ export class Account extends AbstractAccount {
478482
return this.sendTransaction(request);
479483
}
480484

485+
async signMessage(message: string): Promise<string> {
486+
if (!this._connector) {
487+
throw new FuelError(ErrorCode.MISSING_CONNECTOR, 'A connector is required to sign messages.');
488+
}
489+
return this._connector.signMessage(this.address.toString(), message);
490+
}
491+
481492
/**
482493
* Sends a transaction to the network.
483494
*
@@ -488,6 +499,11 @@ export class Account extends AbstractAccount {
488499
transactionRequestLike: TransactionRequestLike,
489500
options?: Pick<ProviderSendTxParams, 'awaitExecution'>
490501
): Promise<TransactionResponse> {
502+
if (this._connector) {
503+
return this.provider.getTransactionResponse(
504+
await this._connector.sendTransaction(this.address.toString(), transactionRequestLike)
505+
);
506+
}
491507
const transactionRequest = transactionRequestify(transactionRequestLike);
492508
await this.provider.estimateTxDependencies(transactionRequest);
493509
return this.provider.sendTransaction(transactionRequest, {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/* eslint-disable @typescript-eslint/require-await */
2+
import { EventEmitter } from 'events';
3+
4+
import type { TransactionRequestLike } from '../providers';
5+
6+
import { FuelConnectorEventTypes } from './types';
7+
import type {
8+
FuelConnectorEvents,
9+
ConnectorMetadata,
10+
FuelABI,
11+
Network,
12+
FuelEventArg,
13+
Version,
14+
Asset,
15+
} from './types';
16+
17+
/**
18+
* @name FuelConnector
19+
*
20+
* Wallet Connector is a interface that represents a Wallet Connector and all the methods
21+
* that should be implemented to be compatible with the Fuel SDK.
22+
*/
23+
export abstract class FuelConnector extends EventEmitter {
24+
name: string = '';
25+
metadata: ConnectorMetadata = {} as ConnectorMetadata;
26+
connected: boolean = false;
27+
installed: boolean = false;
28+
events = FuelConnectorEventTypes;
29+
30+
/**
31+
* Should return true if the connector is loaded
32+
* in less then one second.
33+
*
34+
* @returns Always true.
35+
*/
36+
async ping(): Promise<boolean> {
37+
throw new Error('Method not implemented.');
38+
}
39+
40+
/**
41+
* Should return the current version of the connector
42+
* and the network version that is compatible.
43+
*
44+
* @returns boolean - connection status.
45+
*/
46+
async version(): Promise<Version> {
47+
throw new Error('Method not implemented.');
48+
}
49+
50+
/**
51+
* Should return true if the connector is connected
52+
* to any of the accounts available.
53+
*
54+
* @returns The connection status.
55+
*/
56+
async isConnected(): Promise<boolean> {
57+
throw new Error('Method not implemented.');
58+
}
59+
60+
/**
61+
* Should return all the accounts authorized for the
62+
* current connection.
63+
*
64+
* @returns The accounts addresses strings
65+
*/
66+
async accounts(): Promise<Array<string>> {
67+
throw new Error('Method not implemented.');
68+
}
69+
70+
/**
71+
* Should start the connection process and return
72+
* true if the account authorize the connection.
73+
*
74+
* and return false if the user reject the connection.
75+
*
76+
* @emits accounts
77+
* @returns boolean - connection status.
78+
*/
79+
async connect(): Promise<boolean> {
80+
throw new Error('Method not implemented.');
81+
}
82+
83+
/**
84+
* Should disconnect the current connection and
85+
* return false if the disconnection was successful.
86+
*
87+
* @emits assets connection
88+
* @returns The connection status.
89+
*/
90+
async disconnect(): Promise<boolean> {
91+
throw new Error('Method not implemented.');
92+
}
93+
94+
/**
95+
* Should start the sign message process and return
96+
* the signed message.
97+
*
98+
* @param address - The address to sign the message
99+
* @param message - The message to sign all text will be treated as text utf-8
100+
*
101+
* @returns Message signature
102+
*/
103+
async signMessage(_address: string, _message: string): Promise<string> {
104+
throw new Error('Method not implemented.');
105+
}
106+
107+
/**
108+
* Should start the send transaction process and return
109+
* the transaction id submitted to the network.
110+
*
111+
* If the network is not available for the connection
112+
* it should throw an error to avoid the transaction
113+
* to be sent to the wrong network and lost.
114+
*
115+
* @param address - The address to sign the transaction
116+
* @param transaction - The transaction to send
117+
*
118+
* @returns The transaction id
119+
*/
120+
async sendTransaction(_address: string, _transaction: TransactionRequestLike): Promise<string> {
121+
throw new Error('Method not implemented.');
122+
}
123+
124+
/**
125+
* Should return the current account selected inside the connector, if the account
126+
* is authorized for the connection.
127+
*
128+
* If the account is not authorized it should return null.
129+
*
130+
* @returns The current account selected otherwise null.
131+
*/
132+
async currentAccount(): Promise<string | null> {
133+
throw new Error('Method not implemented.');
134+
}
135+
136+
/**
137+
* Should add the the assets metadata to the connector and return true if the asset
138+
* was added successfully.
139+
*
140+
* If the asset already exists it should throw an error.
141+
*
142+
* @emits assets
143+
* @param assets - The assets to add the metadata to the connection.
144+
* @throws Error if the asset already exists
145+
* @returns True if the asset was added successfully
146+
*/
147+
async addAssets(_assets: Array<Asset>): Promise<boolean> {
148+
throw new Error('Method not implemented.');
149+
}
150+
151+
/**
152+
* Should add the the asset metadata to the connector and return true if the asset
153+
* was added successfully.
154+
*
155+
* If the asset already exists it should throw an error.
156+
*
157+
* @emits assets
158+
* @param asset - The asset to add the metadata to the connection.
159+
* @throws Error if the asset already exists
160+
* @returns True if the asset was added successfully
161+
*/
162+
async addAsset(_asset: Asset): Promise<boolean> {
163+
throw new Error('Method not implemented.');
164+
}
165+
166+
/**
167+
* Should return all the assets added to the connector. If a connection is already established.
168+
*
169+
* @returns Array of assets metadata from the connector vinculated to the all accounts from a specific Wallet.
170+
*/
171+
async assets(): Promise<Array<Asset>> {
172+
throw new Error('Method not implemented.');
173+
}
174+
175+
/**
176+
* Should start the add network process and return true if the network was added successfully.
177+
*
178+
* @emits networks
179+
* @throws Error if the network already exists
180+
* @param networkUrl - The URL of the network to be added.
181+
* @returns Return true if the network was added successfully
182+
*/
183+
async addNetwork(_networkUrl: string): Promise<boolean> {
184+
throw new Error('Method not implemented.');
185+
}
186+
187+
/**
188+
* Should start the select network process and return true if the network has change successfully.
189+
*
190+
* @emits networks
191+
* @throws Error if the network already exists
192+
* @param network - The network to be selected.
193+
* @returns Return true if the network was added successfully
194+
*/
195+
async selectNetwork(_network: Network): Promise<boolean> {
196+
throw new Error('Method not implemented.');
197+
}
198+
199+
/**
200+
* Should return all the networks available from the connector. If the connection is already established.
201+
*
202+
* @returns Return all the networks added to the connector.
203+
*/
204+
async networks(): Promise<Array<Network>> {
205+
throw new Error('Method not implemented.');
206+
}
207+
208+
/**
209+
* Should return the current network selected inside the connector. Even if the connection is not established.
210+
*
211+
* @returns Return the current network selected inside the connector.
212+
*/
213+
async currentNetwork(): Promise<Network> {
214+
throw new Error('Method not implemented.');
215+
}
216+
217+
/**
218+
* Should add the ABI to the connector and return true if the ABI was added successfully.
219+
*
220+
* @param contractId - The contract id to add the ABI.
221+
* @param abi - The JSON ABI that represents a contract.
222+
* @returns Return true if the ABI was added successfully.
223+
*/
224+
async addABI(_contractId: string, _abi: FuelABI): Promise<boolean> {
225+
throw new Error('Method not implemented.');
226+
}
227+
228+
/**
229+
* Should return the ABI from the connector vinculated to the all accounts from a specific Wallet.
230+
*
231+
* @param id - The contract id to get the ABI.
232+
* @returns The ABI if it exists, otherwise return null.
233+
*/
234+
async getABI(_id: string): Promise<FuelABI | null> {
235+
throw new Error('Method not implemented.');
236+
}
237+
238+
/**
239+
* Should return true if the abi exists in the connector vinculated to the all accounts from a specific Wallet.
240+
*
241+
* @param id - The contract id to get the abi
242+
* @returns Returns true if the abi exists or false if not.
243+
*/
244+
async hasABI(_id: string): Promise<boolean> {
245+
throw new Error('Method not implemented.');
246+
}
247+
248+
/**
249+
* Event listener for the connector.
250+
*
251+
* @param eventName - The event name to listen
252+
* @param listener - The listener function
253+
*/
254+
on<E extends FuelConnectorEvents['type'], D extends FuelEventArg<E>>(
255+
eventName: E,
256+
listener: (data: D) => void
257+
): this {
258+
super.on(eventName, listener);
259+
return this;
260+
}
261+
}

0 commit comments

Comments
 (0)