cruzo-web3 0.1.1 → 0.1.3
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/README.md +6 -9
- package/lib/components/web3-signer/web3-signer.component.ts +4 -1
- package/lib/components/web3-signing/web3-signing.component.ts +0 -7
- package/lib/providers/tonconnect.provider.ts +33 -11
- package/lib/utils/wallet-error.ts +67 -0
- package/lib/web3.service.ts +34 -4
- package/package.json +6 -18
package/README.md
CHANGED
|
@@ -8,11 +8,6 @@ Web3 addon for [cruzo](https://github.com/MaratBektemirov/cruzo): wallet connect
|
|
|
8
8
|
npm install cruzo cruzo-web3
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
For mobile wallets, also install optional peer dependencies:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npm install @tonconnect/sdk @tonconnect/ui @walletconnect/ethereum-provider
|
|
15
|
-
```
|
|
16
11
|
|
|
17
12
|
## Public API
|
|
18
13
|
|
|
@@ -83,12 +78,14 @@ Without a Project ID, the **Mobile wallet (WalletConnect)** option stays disable
|
|
|
83
78
|
TON wallets (extension and mobile app) require a public `tonconnect-manifest.json`.
|
|
84
79
|
|
|
85
80
|
1. Add the manifest file to your site root (must be reachable over HTTPS in production).
|
|
86
|
-
2. Point `web3Service` to
|
|
81
|
+
2. Point `web3Service` to the absolute manifest URL at app bootstrap:
|
|
87
82
|
|
|
88
83
|
```ts
|
|
89
|
-
web3Service
|
|
90
|
-
|
|
91
|
-
);
|
|
84
|
+
import { web3Service } from "cruzo-web3";
|
|
85
|
+
|
|
86
|
+
const tonManifestUrl = new URL("/tonconnect-manifest.json", window.location.href).href;
|
|
87
|
+
|
|
88
|
+
web3Service.setTonManifestUrl(tonManifestUrl);
|
|
92
89
|
```
|
|
93
90
|
|
|
94
91
|
Example manifest:
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import type { SignerState, SignerWallet } from "../../types/signer-state";
|
|
17
17
|
import { buildSigningPageUrl } from "../../signing-url";
|
|
18
18
|
import { pubKeyToText } from "../../utils/format-pub-key";
|
|
19
|
+
import { formatWalletError, isWalletUserCancellation } from "../../utils/wallet-error";
|
|
19
20
|
import { web3Service } from "../../web3.service";
|
|
20
21
|
import { isCustomWallet } from "../../web3-wallet";
|
|
21
22
|
|
|
@@ -309,7 +310,9 @@ export class Web3SignerComponent extends AbstractComponent<SignerConfig, any, Si
|
|
|
309
310
|
|
|
310
311
|
action()
|
|
311
312
|
.catch((error: unknown) => {
|
|
312
|
-
|
|
313
|
+
if (isWalletUserCancellation(error)) return;
|
|
314
|
+
|
|
315
|
+
this.error$.update(formatWalletError(error));
|
|
313
316
|
})
|
|
314
317
|
.finally(() => {
|
|
315
318
|
this.busy$.update(false);
|
|
@@ -85,7 +85,6 @@ export class Web3SigningComponent extends AbstractComponent {
|
|
|
85
85
|
connectedCallback() {
|
|
86
86
|
componentsRegistryService.connectBucket(this.innerBucket);
|
|
87
87
|
super.connectedCallback();
|
|
88
|
-
this.ensureTonManifest();
|
|
89
88
|
this.updateWalletHint();
|
|
90
89
|
this.setupUrlSync();
|
|
91
90
|
}
|
|
@@ -218,12 +217,6 @@ export class Web3SigningComponent extends AbstractComponent {
|
|
|
218
217
|
return { pubKey: null, signed: false, wallet: null };
|
|
219
218
|
}
|
|
220
219
|
|
|
221
|
-
private ensureTonManifest() {
|
|
222
|
-
if (web3Service.getTonManifestUrl()) return;
|
|
223
|
-
|
|
224
|
-
web3Service.setTonManifestUrl(new URL("/tonconnect-manifest.json", window.location.href).href);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
220
|
private updateWalletHint() {
|
|
228
221
|
const extensions = detectInjectedWallets();
|
|
229
222
|
const hints = ["Click Connect wallet to choose a provider (extension or mobile app)."];
|
|
@@ -70,9 +70,12 @@ export class TonConnectProvider implements Web3Provider {
|
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
return this.waitForAccount(
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
return this.waitForAccount(
|
|
74
|
+
() => {
|
|
75
|
+
void this.ui.openModal();
|
|
76
|
+
},
|
|
77
|
+
{ watchModalClose: true },
|
|
78
|
+
);
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
async disconnect() {
|
|
@@ -80,6 +83,7 @@ export class TonConnectProvider implements Web3Provider {
|
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
async signMessage(message: string | Uint8Array) {
|
|
86
|
+
await this.ui.connectionRestored;
|
|
83
87
|
const account = this.requireAccount();
|
|
84
88
|
const text = new TextDecoder().decode(toMessageBytes(message));
|
|
85
89
|
|
|
@@ -114,28 +118,46 @@ export class TonConnectProvider implements Web3Provider {
|
|
|
114
118
|
return account;
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
private waitForAccount(start: () => void) {
|
|
121
|
+
private waitForAccount(start: () => void, options?: { watchModalClose?: boolean }) {
|
|
118
122
|
return new Promise<PubKey>((resolve, reject) => {
|
|
119
|
-
|
|
123
|
+
let settled = false;
|
|
124
|
+
|
|
125
|
+
const finish = (action: () => void) => {
|
|
126
|
+
if (settled) return;
|
|
127
|
+
|
|
128
|
+
settled = true;
|
|
129
|
+
unsubscribeStatus();
|
|
130
|
+
unsubscribeModal?.();
|
|
131
|
+
action();
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const unsubscribeStatus = this.ui.onStatusChange(
|
|
120
135
|
(wallet) => {
|
|
121
136
|
if (!wallet?.account) return;
|
|
122
137
|
|
|
123
|
-
unsubscribe();
|
|
124
138
|
const pubKey = this.toPubKey(wallet.account);
|
|
125
139
|
this.onAccountChange?.(pubKey);
|
|
126
|
-
resolve(pubKey);
|
|
140
|
+
finish(() => resolve(pubKey));
|
|
127
141
|
},
|
|
128
142
|
(error) => {
|
|
129
|
-
|
|
130
|
-
reject(error);
|
|
143
|
+
finish(() => reject(error));
|
|
131
144
|
},
|
|
132
145
|
);
|
|
133
146
|
|
|
147
|
+
const unsubscribeModal = options?.watchModalClose
|
|
148
|
+
? this.ui.onModalStateChange((state) => {
|
|
149
|
+
if (state.status === "opened" || this.ui.account) return;
|
|
150
|
+
|
|
151
|
+
if (state.closeReason === "action-cancelled") {
|
|
152
|
+
finish(() => reject(new Error("Connection cancelled")));
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
: null;
|
|
156
|
+
|
|
134
157
|
try {
|
|
135
158
|
start();
|
|
136
159
|
} catch (error) {
|
|
137
|
-
|
|
138
|
-
reject(error);
|
|
160
|
+
finish(() => reject(error));
|
|
139
161
|
}
|
|
140
162
|
});
|
|
141
163
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
function readMessage(error: unknown): string {
|
|
2
|
+
if (typeof error === "string") return error;
|
|
3
|
+
|
|
4
|
+
if (error instanceof Error) {
|
|
5
|
+
return error.message || error.name;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (error && typeof error === "object") {
|
|
9
|
+
const record = error as Record<string, unknown>;
|
|
10
|
+
const message = record.message;
|
|
11
|
+
|
|
12
|
+
if (typeof message === "string" && message.length) return message;
|
|
13
|
+
if (message && typeof message === "object") {
|
|
14
|
+
const nested = message as Record<string, unknown>;
|
|
15
|
+
|
|
16
|
+
if (typeof nested.message === "string" && nested.message.length) {
|
|
17
|
+
return nested.message;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof record.reason === "string" && record.reason.length) {
|
|
22
|
+
return record.reason;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isUserCancellationMessage(message: string) {
|
|
30
|
+
const text = message.toLowerCase();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
text.includes("user rejected") ||
|
|
34
|
+
text.includes("rejected the request") ||
|
|
35
|
+
text.includes("request rejected") ||
|
|
36
|
+
text.includes("action rejected") ||
|
|
37
|
+
text.includes("connection cancelled") ||
|
|
38
|
+
text.includes("connection canceled") ||
|
|
39
|
+
text.includes("wallet was not connected") ||
|
|
40
|
+
text.includes("was not sent") ||
|
|
41
|
+
text.includes("sign data canceled") ||
|
|
42
|
+
text.includes("sign data cancelled") ||
|
|
43
|
+
text.includes("sign message canceled") ||
|
|
44
|
+
text.includes("sign message cancelled") ||
|
|
45
|
+
text.includes("transaction canceled") ||
|
|
46
|
+
text.includes("transaction cancelled")
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isWalletUserCancellation(error: unknown) {
|
|
51
|
+
if (error && typeof error === "object") {
|
|
52
|
+
const record = error as Record<string, unknown>;
|
|
53
|
+
|
|
54
|
+
if (record.code === 4001 || record.code === "4001") return true;
|
|
55
|
+
if (record.name === "UserRejectsError") return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const message = readMessage(error);
|
|
59
|
+
|
|
60
|
+
return message.length > 0 && isUserCancellationMessage(message);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function formatWalletError(error: unknown) {
|
|
64
|
+
const message = readMessage(error);
|
|
65
|
+
|
|
66
|
+
return message || "Wallet request failed";
|
|
67
|
+
}
|
package/lib/web3.service.ts
CHANGED
|
@@ -118,6 +118,7 @@ export class Web3Service extends AbstractService {
|
|
|
118
118
|
readonly setup$ = this.newRx(0);
|
|
119
119
|
|
|
120
120
|
private provider: Web3Provider | null = null;
|
|
121
|
+
private activeProviderKey: string | null = null;
|
|
121
122
|
private tonManifestUrl: string | null = null;
|
|
122
123
|
private walletConnectProjectId: string | null = null;
|
|
123
124
|
private builtinProviders: Web3WalletSlot[] | null = null;
|
|
@@ -170,11 +171,20 @@ export class Web3Service extends AbstractService {
|
|
|
170
171
|
return getWalletModeLabel(wallet.kind, wallet.transport);
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
useProvider(provider: Web3Provider) {
|
|
174
|
+
useProvider(provider: Web3Provider, key: string | null = null) {
|
|
174
175
|
this.provider = provider;
|
|
176
|
+
this.activeProviderKey = key;
|
|
175
177
|
return this;
|
|
176
178
|
}
|
|
177
179
|
|
|
180
|
+
private walletProviderKey(kind: WalletKind, transport: WalletTransport) {
|
|
181
|
+
return `${kind}:${transport}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private customProviderKey(providerId: string) {
|
|
185
|
+
return `custom:${providerId}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
178
188
|
getProvider() {
|
|
179
189
|
return this.provider;
|
|
180
190
|
}
|
|
@@ -218,6 +228,12 @@ export class Web3Service extends AbstractService {
|
|
|
218
228
|
kind: InjectedWalletKind = "ethereum",
|
|
219
229
|
options: InjectedProviderOptions = {},
|
|
220
230
|
) {
|
|
231
|
+
const key = this.walletProviderKey(kind, "extension");
|
|
232
|
+
|
|
233
|
+
if (this.provider && this.activeProviderKey === key) {
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
|
|
221
237
|
const provider = createInjectedProvider(
|
|
222
238
|
kind,
|
|
223
239
|
(pubKey) => {
|
|
@@ -228,7 +244,7 @@ export class Web3Service extends AbstractService {
|
|
|
228
244
|
},
|
|
229
245
|
);
|
|
230
246
|
|
|
231
|
-
return this.useProvider(provider);
|
|
247
|
+
return this.useProvider(provider, key);
|
|
232
248
|
}
|
|
233
249
|
|
|
234
250
|
async useWalletProvider(
|
|
@@ -236,6 +252,12 @@ export class Web3Service extends AbstractService {
|
|
|
236
252
|
transport: WalletTransport = "auto",
|
|
237
253
|
options: WalletProviderOptions = {},
|
|
238
254
|
) {
|
|
255
|
+
const key = this.walletProviderKey(kind, transport);
|
|
256
|
+
|
|
257
|
+
if (this.provider && this.activeProviderKey === key) {
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
|
|
239
261
|
const provider = await createWalletProvider(
|
|
240
262
|
kind,
|
|
241
263
|
transport,
|
|
@@ -245,7 +267,7 @@ export class Web3Service extends AbstractService {
|
|
|
245
267
|
this.walletOptions(options),
|
|
246
268
|
);
|
|
247
269
|
|
|
248
|
-
return this.useProvider(provider);
|
|
270
|
+
return this.useProvider(provider, key);
|
|
249
271
|
}
|
|
250
272
|
|
|
251
273
|
ensureInjectedProvider(
|
|
@@ -286,6 +308,8 @@ export class Web3Service extends AbstractService {
|
|
|
286
308
|
}
|
|
287
309
|
|
|
288
310
|
await this.provider.disconnect();
|
|
311
|
+
this.provider = null;
|
|
312
|
+
this.activeProviderKey = null;
|
|
289
313
|
this.userPubKey$.update(null);
|
|
290
314
|
}
|
|
291
315
|
|
|
@@ -314,8 +338,14 @@ export class Web3Service extends AbstractService {
|
|
|
314
338
|
}
|
|
315
339
|
|
|
316
340
|
async useCustomProvider(providerId: string) {
|
|
341
|
+
const key = this.customProviderKey(providerId);
|
|
342
|
+
|
|
343
|
+
if (this.provider && this.activeProviderKey === key) {
|
|
344
|
+
return this;
|
|
345
|
+
}
|
|
346
|
+
|
|
317
347
|
const provider = await this.resolveCustomProvider(providerId);
|
|
318
|
-
return this.useProvider(provider);
|
|
348
|
+
return this.useProvider(provider, key);
|
|
319
349
|
}
|
|
320
350
|
|
|
321
351
|
async connectCustom(providerId: string) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cruzo-web3",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Web3 addon for cruzo: wallet providers, sign, verify",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,28 +31,16 @@
|
|
|
31
31
|
"typecheck": "tsc -p tsconfig.json",
|
|
32
32
|
"prepublishOnly": "npm run typecheck"
|
|
33
33
|
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@tonconnect/sdk": "^3.4.1",
|
|
36
|
+
"@tonconnect/ui": "^3.0.0",
|
|
37
|
+
"@walletconnect/ethereum-provider": "^2.23.9"
|
|
38
|
+
},
|
|
34
39
|
"peerDependencies": {
|
|
35
|
-
"@tonconnect/sdk": ">=3.0.0",
|
|
36
|
-
"@tonconnect/ui": ">=3.0.0",
|
|
37
|
-
"@walletconnect/ethereum-provider": ">=2.0.0",
|
|
38
40
|
"cruzo": "0.9.889"
|
|
39
41
|
},
|
|
40
|
-
"peerDependenciesMeta": {
|
|
41
|
-
"@tonconnect/sdk": {
|
|
42
|
-
"optional": true
|
|
43
|
-
},
|
|
44
|
-
"@tonconnect/ui": {
|
|
45
|
-
"optional": true
|
|
46
|
-
},
|
|
47
|
-
"@walletconnect/ethereum-provider": {
|
|
48
|
-
"optional": true
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
42
|
"devDependencies": {
|
|
52
|
-
"@tonconnect/sdk": "^3.4.1",
|
|
53
|
-
"@tonconnect/ui": "^3.0.0",
|
|
54
43
|
"@types/node": "20.0.0",
|
|
55
|
-
"@walletconnect/ethereum-provider": "^2.23.9",
|
|
56
44
|
"cruzo": "0.9.889",
|
|
57
45
|
"typescript": "5.9.3"
|
|
58
46
|
}
|