@dynamic-labs/sui-core 4.48.0 → 4.48.1

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.
@@ -0,0 +1,303 @@
1
+ 'use client'
2
+ import { __awaiter } from '../../../_virtual/_tslib.js';
3
+ import { DynamicError } from '@dynamic-labs/utils';
4
+ import { getSuiNetworkIdFromName } from '../../utils/network/networkHelpers.js';
5
+ import { Injected } from '../injected/injected.js';
6
+
7
+ /**
8
+ * Slush Wallet Connector with Multi-Account Support
9
+ *
10
+ * This connector extends the base Injected connector to support multiple
11
+ * wallet accounts per connection prompt. Unlike other Sui wallets that only
12
+ * allow one account at a time, Slush wallet can connect multiple accounts
13
+ * simultaneously.
14
+ *
15
+ * Key features:
16
+ * - Tracks multiple wallet accounts (accounts array)
17
+ * - Filters new accounts to allow only one new address per connection
18
+ * - Supports setting a primary account for operations
19
+ * - Handles account switching for transactions and signing
20
+ */
21
+ class Slush extends Injected {
22
+ constructor() {
23
+ super(...arguments);
24
+ this.name = 'Slush — A Sui wallet';
25
+ this.overrideKey = 'slushsui';
26
+ /** Tracks all wallet accounts returned by the wallet */
27
+ this.accounts = [];
28
+ }
29
+ /** Tracks the primary/active wallet account (first account by default) */
30
+ getPrimaryAccount() {
31
+ return this.accounts[0];
32
+ }
33
+ /**
34
+ * Set a specific account as the primary account by address.
35
+ * This moves the specified account to the front of the accounts array.
36
+ * @param address - The address of the account to set as primary
37
+ * @returns true if the account was found and set as primary, false otherwise
38
+ */
39
+ setPrimaryAccount(account) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ if (!account) {
42
+ this.accounts = [];
43
+ return;
44
+ }
45
+ return this.setPrimaryAccountAddress(account.address);
46
+ });
47
+ }
48
+ setPrimaryAccountAddress(address) {
49
+ return __awaiter(this, void 0, void 0, function* () {
50
+ var _a;
51
+ const accountIndex = this.accounts.findIndex((acc) => acc.address === address);
52
+ if (accountIndex === -1) {
53
+ this.logger.error(`[setPrimaryAccount] Account with address ${address} not found for ${this.name}`);
54
+ return false;
55
+ }
56
+ if (accountIndex === 0) {
57
+ // Already primary
58
+ return true;
59
+ }
60
+ // Move the account to the front
61
+ this.logger.debug(`[${this.name}] [setPrimaryAccount] Moving account to primary position`, {
62
+ accountIndex,
63
+ address,
64
+ currentAccountAddresses: this.accounts.map((acc) => acc.address),
65
+ });
66
+ const [targetAccount] = this.accounts.splice(accountIndex, 1);
67
+ this.accounts.unshift(targetAccount);
68
+ this.logger.debug(`[setPrimaryAccount] Set account ${address} as primary account for ${this.name}`);
69
+ // Update the active network ID based on the new primary account
70
+ const primaryChain = (_a = this.getPrimaryAccount()) === null || _a === void 0 ? void 0 : _a.chains[0];
71
+ if (primaryChain) {
72
+ this.activeNetworkId = getSuiNetworkIdFromName(primaryChain, this.suiNetworks);
73
+ }
74
+ return true;
75
+ });
76
+ }
77
+ /**
78
+ * Connect to the wallet using the standard:connect feature.
79
+ *
80
+ * Any currently connected wallet will be disconnected first.
81
+ * This happens because we want the user to reselect which wallets they want connected.
82
+ *
83
+ * We only allow adding one new wallet connection per manual user connection.
84
+ */
85
+ connect() {
86
+ return __awaiter(this, arguments, void 0, function* ({ silent = false, } = {}) {
87
+ var _a, _b, _c;
88
+ this.logger.debug(`[${this.name}] [connect] Initiating connection`, {
89
+ isConnecting: this.isConnecting,
90
+ silent,
91
+ });
92
+ if (this.isConnecting) {
93
+ return;
94
+ }
95
+ const connectFeature = (_a = this.getFeatures()) === null || _a === void 0 ? void 0 : _a['standard:connect'];
96
+ if (!connectFeature) {
97
+ if (silent) {
98
+ return;
99
+ }
100
+ throw new DynamicError('Wallet does not support standard:connect');
101
+ }
102
+ // Start connecting
103
+ this.isConnecting = true;
104
+ try {
105
+ const disconnectFeature = (_b = this.getFeatures()) === null || _b === void 0 ? void 0 : _b['standard:disconnect'];
106
+ if (disconnectFeature) {
107
+ yield disconnectFeature.disconnect();
108
+ }
109
+ const response = yield connectFeature.connect(silent ? { silent } : undefined);
110
+ this.logger.debug(`[${this.name}] [connect] Connection returned accounts: ${response === null || response === void 0 ? void 0 : response.accounts.length}`);
111
+ this.logger.debug(`[${this.name}] [connect] Received accounts from wallet`, {
112
+ accountAddresses: response === null || response === void 0 ? void 0 : response.accounts.map((acc) => acc.address),
113
+ accountCount: response === null || response === void 0 ? void 0 : response.accounts.length,
114
+ });
115
+ // Store all accounts returned by the wallet (create a mutable copy)
116
+ const newAccounts = (response === null || response === void 0 ? void 0 : response.accounts) ? [...response.accounts] : [];
117
+ if (this.hasAccountsChanged(newAccounts)) {
118
+ const filteredAccounts = this.filterAccountsForSingleWalletConstraint({
119
+ newAccounts,
120
+ });
121
+ this.logger.logVerboseTroubleshootingMessage(`[${this.name}] [connect] Accounts changed, applying filter`, {
122
+ currentAccounts: this.accounts.map((acc) => acc.address),
123
+ newAccounts: filteredAccounts.map((acc) => acc.address),
124
+ });
125
+ this.accounts = filteredAccounts;
126
+ }
127
+ const primaryChain = (_c = this.getPrimaryAccount()) === null || _c === void 0 ? void 0 : _c.chains[0];
128
+ if (primaryChain) {
129
+ this.activeNetworkId = getSuiNetworkIdFromName(primaryChain, this.suiNetworks);
130
+ }
131
+ }
132
+ catch (error) {
133
+ this.logger.error(error);
134
+ if (silent) {
135
+ return;
136
+ }
137
+ throw new DynamicError('Connection failed');
138
+ }
139
+ finally {
140
+ this.isConnecting = false;
141
+ }
142
+ this.setupEventListeners();
143
+ });
144
+ }
145
+ validateActiveWallet(expectedAddress) {
146
+ return __awaiter(this, void 0, void 0, function* () {
147
+ const addresses = yield this.getConnectedAccounts();
148
+ const isWalletActive = addresses.find((address) => address === expectedAddress);
149
+ if (isWalletActive) {
150
+ return;
151
+ }
152
+ return this.handleWalletNotActive({
153
+ activeAddress: (yield this.getConnectedAccounts())[0],
154
+ expectedAddress,
155
+ });
156
+ });
157
+ }
158
+ /** Get the wallet address by connecting to the current account */
159
+ getAddress() {
160
+ return __awaiter(this, void 0, void 0, function* () {
161
+ this.logger.debug(`[${this.name}] [getAddress] called, attempting to obtain the account address`);
162
+ if (this.handleInAppBrowserGetAddress()) {
163
+ return;
164
+ }
165
+ const previousAddresses = this.accounts.map((account) => account.address);
166
+ yield this.connect();
167
+ const newAddresses = this.accounts.map((account) => account.address);
168
+ // If there is a new account, return the new address
169
+ const newAddress = newAddresses.find((address) => !previousAddresses.includes(address));
170
+ if (newAddress) {
171
+ yield this.setPrimaryAccountAddress(newAddress);
172
+ this.logger.debug(`[${this.name}] [getAddress] New account added`, {
173
+ accountAddresses: this.accounts.map((acc) => acc.address),
174
+ newAddress,
175
+ });
176
+ return newAddress;
177
+ }
178
+ this.logger.logVerboseTroubleshootingMessage(`[${this.name}] [setupEventListeners] No new address to add`, {
179
+ accountAddresses: this.accounts.map((acc) => acc.address),
180
+ newAddresses,
181
+ });
182
+ if (newAddresses.length === 0) {
183
+ throw new DynamicError('No account found');
184
+ }
185
+ return newAddresses[0];
186
+ });
187
+ }
188
+ getConnectedAccounts() {
189
+ return __awaiter(this, void 0, void 0, function* () {
190
+ if (this.accounts.length === 0) {
191
+ yield this.connect({ silent: true });
192
+ }
193
+ // Return all connected account addresses
194
+ return this.accounts.map((account) => account.address);
195
+ });
196
+ }
197
+ /**
198
+ * Helper to check if accounts have changed by comparing addresses.
199
+ * Accounts are compared in a sorted order to handle cases where the order changes.
200
+ *
201
+ * @param newAccounts - The new accounts to compare against current accounts
202
+ * @returns true if accounts have changed, false otherwise
203
+ */
204
+ hasAccountsChanged(newAccounts) {
205
+ const currentAddresses = this.accounts
206
+ .map((acc) => acc.address)
207
+ .sort()
208
+ .join(',');
209
+ const newAddresses = newAccounts
210
+ .map((acc) => acc.address)
211
+ .sort()
212
+ .join(',');
213
+ return currentAddresses !== newAddresses;
214
+ }
215
+ /**
216
+ * Filters new accounts to enforce the SDK's single-wallet connection constraint.
217
+ * The Dynamic SDK only supports connecting one wallet at a time.
218
+ * This method ensures that at most one NEW address is added to the existing accounts.
219
+ *
220
+ * @param newAccounts - The new accounts returned by the wallet
221
+ * @returns Filtered accounts with at most one new address added
222
+ */
223
+ filterAccountsForSingleWalletConstraint({ newAccounts, }) {
224
+ if (this.accounts.length === 0) {
225
+ return [newAccounts[0]];
226
+ }
227
+ // Keep all currently connected accounts that are still in the new accounts list
228
+ const currentAddresses = new Set(this.accounts.map((acc) => acc.address.toLowerCase()));
229
+ const filteredAccounts = newAccounts.filter((acc) => currentAddresses.has(acc.address.toLowerCase()));
230
+ // Find new accounts that aren't in the current list
231
+ const trulyNewAccounts = newAccounts.filter((acc) => !currentAddresses.has(acc.address.toLowerCase()));
232
+ // Allow at most one new account to be added
233
+ if (trulyNewAccounts.length > 0) {
234
+ filteredAccounts.push(trulyNewAccounts[0]);
235
+ }
236
+ return filteredAccounts;
237
+ }
238
+ handleAccountChange() {
239
+ // We intentionally ignore account change events from Slush wallets.
240
+ // Slush wallets support connecting multiple accounts simultaneously,
241
+ // but the Dynamic SDK only supports connecting one wallet at a time.
242
+ // To enforce this constraint, we only allow adding new accounts through the connect()
243
+ // method, where we can properly filter and ensure at most one new account is added.
244
+ // Account change events are therefore ignored to prevent bypassing this limitation.
245
+ }
246
+ signMessage(messageToSign_1) {
247
+ const _super = Object.create(null, {
248
+ signMessage: { get: () => super.signMessage }
249
+ });
250
+ return __awaiter(this, arguments, void 0, function* (messageToSign, { address } = {}) {
251
+ if (address) {
252
+ yield this.setPrimaryAccountAddress(address);
253
+ }
254
+ return _super.signMessage.call(this, messageToSign);
255
+ });
256
+ }
257
+ /** Function used to create transactions in the SDK interface */
258
+ createUiTransaction(from) {
259
+ const _super = Object.create(null, {
260
+ createUiTransaction: { get: () => super.createUiTransaction }
261
+ });
262
+ return __awaiter(this, void 0, void 0, function* () {
263
+ if (this.accounts.length === 0) {
264
+ throw new DynamicError('No account connected');
265
+ }
266
+ yield this.setPrimaryAccountAddress(from);
267
+ return _super.createUiTransaction.call(this, from);
268
+ });
269
+ }
270
+ signAndExecuteTransactionFeature(_a) {
271
+ const _super = Object.create(null, {
272
+ signAndExecuteTransactionFeature: { get: () => super.signAndExecuteTransactionFeature }
273
+ });
274
+ return __awaiter(this, arguments, void 0, function* ({ transaction, legacyOptions, }) {
275
+ const { sender } = transaction.getData();
276
+ if (sender) {
277
+ yield this.setPrimaryAccountAddress(sender);
278
+ }
279
+ return _super.signAndExecuteTransactionFeature.call(this, {
280
+ legacyOptions,
281
+ transaction,
282
+ });
283
+ });
284
+ }
285
+ signTransactionFeature(_a) {
286
+ const _super = Object.create(null, {
287
+ signTransactionFeature: { get: () => super.signTransactionFeature }
288
+ });
289
+ return __awaiter(this, arguments, void 0, function* ({ transaction, }) {
290
+ const { sender } = transaction.getData();
291
+ if (sender) {
292
+ yield this.setPrimaryAccountAddress(sender);
293
+ }
294
+ return _super.signTransactionFeature.call(this, { transaction });
295
+ });
296
+ }
297
+ }
298
+ Object.defineProperty(Slush, 'key', {
299
+ value: 'slushsui',
300
+ writable: false,
301
+ });
302
+
303
+ export { Slush };
@@ -0,0 +1 @@
1
+ export { Slush } from './Slush';