Update dependencies and enhance App component functionality
- Added new dependencies: @polkadot/util and @gear-js/api to package.json and example/package.json. - Implemented a test function in App component to interact with GearApi for balance transfer. - Updated transaction signing logic to support both raw and payload types. - Refactored Wallet class to improve socket connection handling and transaction management. - Enhanced WalletStore by removing unnecessary listener setup code.
This commit is contained in:
1 parent
deae91a3e6
commit
4b7a39d332
12 files changed
+1343
-200
No files matched your search
@@ -10,6 +10,8 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@gear-js/api": "^0.44.2",
|
||||||
|
"@polkadot/api": "^16.4.9",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1"
|
"react-dom": "^19.1.1"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+726
-8
File diff suppressed because it is too large.
Load diff
+19
-1
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
import { GearApi } from '@gear-js/api';
|
||||||
|
|
||||||
const RAW_TRANSACTION = '0xd10204680359836ffbbdaee026cac6008a4c1f68e9fee5fc2949e0ab3ac3f478b7446a8a0dd90114506f6b657220526567697374657218506c617965724cd84143c22eb3da602607c1bd992dbd6db170e21d1b45b99278dbca78831af7028be58d11229c7b53a181793ada6c38754c3b30cc27cb607741d99e7bbadc7c687d165102b787e3e17957eee3435455613ff857a91389b1ae0a24e0618f1afcd6266268020000000000000000000000000000000000000001'
|
const RAW_TRANSACTION = '0xd10204680359836ffbbdaee026cac6008a4c1f68e9fee5fc2949e0ab3ac3f478b7446a8a0dd90114506f6b657220526567697374657218506c617965724cd84143c22eb3da602607c1bd992dbd6db170e21d1b45b99278dbca78831af7028be58d11229c7b53a181793ada6c38754c3b30cc27cb607741d99e7bbadc7c687d165102b787e3e17957eee3435455613ff857a91389b1ae0a24e0618f1afcd6266268020000000000000000000000000000000000000001'
|
||||||
|
|
||||||
@@ -24,6 +25,22 @@ function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const test = async () => {
|
||||||
|
const extension = await varanWallet.enable?.('current');
|
||||||
|
const address = (await extension?.accounts.get())?.[0].address;
|
||||||
|
if (!address) {
|
||||||
|
console.log('[VARANINJECT] address not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const apiPromise = new GearApi({ providerAddress: 'wss://rpc.vara.network' });
|
||||||
|
await apiPromise.isReady;
|
||||||
|
const tx = apiPromise.balance.transfer('kGi1Ui7VXBFmPmaoMD5xgWd2VHNixZ5BbLNhHFYD39T85rUi3', 100)
|
||||||
|
console.log('[VARANINJECT] tx', tx);
|
||||||
|
// @ts-expect-error - signer is not typed
|
||||||
|
const result = await tx.signAndSend(address, { signer: extension?.signer })
|
||||||
|
console.log('[VARANINJECT] result', result);
|
||||||
|
}
|
||||||
|
|
||||||
const signTransaction = async () => {
|
const signTransaction = async () => {
|
||||||
const extension = await varanWallet.enable?.('current');
|
const extension = await varanWallet.enable?.('current');
|
||||||
const address = (await extension?.accounts.get())?.[0].address;
|
const address = (await extension?.accounts.get())?.[0].address;
|
||||||
@@ -34,7 +51,7 @@ function App() {
|
|||||||
const signature = await extension?.signer?.signRaw?.({
|
const signature = await extension?.signer?.signRaw?.({
|
||||||
address: address,
|
address: address,
|
||||||
data: RAW_TRANSACTION,
|
data: RAW_TRANSACTION,
|
||||||
type: 'payload',
|
type: 'bytes',
|
||||||
});
|
});
|
||||||
console.log('[VARANINJECT] signature', signature);
|
console.log('[VARANINJECT] signature', signature);
|
||||||
setSignature(signature?.signature || '');
|
setSignature(signature?.signature || '');
|
||||||
@@ -53,6 +70,7 @@ function App() {
|
|||||||
<button onClick={checkData}>Check Data</button>
|
<button onClick={checkData}>Check Data</button>
|
||||||
<div>DATA</div>
|
<div>DATA</div>
|
||||||
<pre>{data}</pre>
|
<pre>{data}</pre>
|
||||||
|
<button onClick={test}>Test</button>
|
||||||
{isConnected &&
|
{isConnected &&
|
||||||
<>
|
<>
|
||||||
<button onClick={signTransaction}>Sign Transaction</button>
|
<button onClick={signTransaction}>Sign Transaction</button>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import App from './App.tsx'
|
|||||||
import { injectVaranWallet } from 'varan-inject'
|
import { injectVaranWallet } from 'varan-inject'
|
||||||
import WebApp from '@twa-dev/sdk'
|
import WebApp from '@twa-dev/sdk'
|
||||||
injectVaranWallet(WebApp, {
|
injectVaranWallet(WebApp, {
|
||||||
callbackUrl: 'test_y_maxim_bbbbot/start?startapp='
|
callbackUrl: 'test_y_maxim_bbbbot/start?startapp=',
|
||||||
// callbackUrl: 'ZkPokerTestBot/zkPoker?startapp='
|
// callbackUrl: 'ZkPokerTestBot/zkPoker?startapp='
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://gitlab.mind-dev.com/pb/varan-connect#readme",
|
"homepage": "https://gitlab.mind-dev.com/pb/varan-connect#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@polkadot/util": "13.5.7",
|
||||||
"@twa-dev/sdk": "^8.0.2"
|
"@twa-dev/sdk": "^8.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
import type {
|
||||||
|
ServerMessage,
|
||||||
|
ConnectResponseMessage,
|
||||||
|
SignTransactionResponseMessage
|
||||||
|
} from './socket';
|
||||||
|
|
||||||
|
const API_URL = 'https://connect.varan.app';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a connect response message to the API
|
||||||
|
* This will be received by the client's WebSocket and handled by the socket
|
||||||
|
* @param message The connect response message
|
||||||
|
* @param connectionId The unique connection ID from WebSocket
|
||||||
|
* @returns Promise resolving when the message is sent
|
||||||
|
*/
|
||||||
|
export async function sendConnect(
|
||||||
|
message: ConnectResponseMessage,
|
||||||
|
connectionId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await sendServerMessage(message, connectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a signed transaction response message to the API
|
||||||
|
* This will be received by the client's WebSocket and handled by the socket
|
||||||
|
* @param message The sign transaction response message
|
||||||
|
* @param connectionId The unique connection ID from WebSocket
|
||||||
|
* @returns Promise resolving when the message is sent
|
||||||
|
*/
|
||||||
|
export async function sendSignedTransaction(
|
||||||
|
message: SignTransactionResponseMessage,
|
||||||
|
connectionId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await sendServerMessage(message, connectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends any server message to the API
|
||||||
|
* This will be received by the client's WebSocket and handled by the socket
|
||||||
|
* @param message The server message to send
|
||||||
|
* @param connectionId The unique connection ID from WebSocket
|
||||||
|
* @returns Promise resolving when the message is sent
|
||||||
|
*/
|
||||||
|
async function sendServerMessage(
|
||||||
|
message: ServerMessage,
|
||||||
|
connectionId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}/send/${connectionId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(message),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[VARANINJECT] Error sending server message:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1 +1,9 @@
|
|||||||
export { injectVaranWallet } from './varan-wallet';
|
export { injectVaranWallet } from './varan-wallet';
|
||||||
|
export { sendConnect, sendSignedTransaction } from './api';
|
||||||
|
export type {
|
||||||
|
ConnectRequest,
|
||||||
|
SignTransactionRequest,
|
||||||
|
ConnectResponseMessage,
|
||||||
|
SignTransactionResponseMessage,
|
||||||
|
ServerMessage
|
||||||
|
} from './socket';
|
||||||
+351
@@ -0,0 +1,351 @@
|
|||||||
|
// Base WebSocket message structure
|
||||||
|
export interface WebSocketMessage {
|
||||||
|
type: 'connection' | 'acknowledgment' | 'server_message' | 'data';
|
||||||
|
id?: string;
|
||||||
|
data?: unknown;
|
||||||
|
message?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection message - sent by server when client connects
|
||||||
|
export interface ConnectionMessage extends WebSocketMessage {
|
||||||
|
type: 'connection';
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acknowledgment message - sent by server to acknowledge received data
|
||||||
|
export interface AcknowledgmentMessage extends WebSocketMessage {
|
||||||
|
type: 'acknowledgment';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server message wrapper - contains actual message data
|
||||||
|
export interface ServerMessageWrapper extends WebSocketMessage {
|
||||||
|
type: 'server_message';
|
||||||
|
message: ServerMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data message wrapper - contains data payload
|
||||||
|
export interface DataMessageWrapper extends WebSocketMessage {
|
||||||
|
type: 'data';
|
||||||
|
data: ServerMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server messages that can be sent/received
|
||||||
|
export type ServerMessage =
|
||||||
|
| ConnectResponseMessage
|
||||||
|
| SignTransactionResponseMessage
|
||||||
|
| GenericServerMessage;
|
||||||
|
|
||||||
|
// Connect response - sent when wallet connection is established
|
||||||
|
export interface ConnectResponseMessage {
|
||||||
|
type: 'connect';
|
||||||
|
success: boolean;
|
||||||
|
wallets?: string[];
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign transaction response - sent when transaction signing is complete
|
||||||
|
export interface SignTransactionResponseMessage {
|
||||||
|
type: 'signTransaction';
|
||||||
|
id: number;
|
||||||
|
success: boolean;
|
||||||
|
signedTx?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic server message for extensibility
|
||||||
|
export interface GenericServerMessage {
|
||||||
|
type: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client request messages sent via API
|
||||||
|
export interface ConnectRequest {
|
||||||
|
method: 'connect';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignTransactionRequest {
|
||||||
|
method: 'sT';
|
||||||
|
id: number;
|
||||||
|
tx: `p:${string}` | `bytes:${string}`;
|
||||||
|
wallet?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClientRequest = ConnectRequest | SignTransactionRequest;
|
||||||
|
|
||||||
|
// Message handler type
|
||||||
|
export interface MessageHandler<T = ServerMessage> {
|
||||||
|
(data: T): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Socket configuration
|
||||||
|
export interface SocketConfig {
|
||||||
|
wsUrl?: string;
|
||||||
|
apiUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WalletSocket {
|
||||||
|
private ws: WebSocket | null = null;
|
||||||
|
private uniqueId: string | null = null;
|
||||||
|
private reconnectAttempts = 0;
|
||||||
|
private maxReconnectAttempts = 5;
|
||||||
|
private reconnectDelay = 1000;
|
||||||
|
private isConnecting = false;
|
||||||
|
private messageHandlers: Map<string, MessageHandler<ServerMessage>> = new Map();
|
||||||
|
private connectionPromise: Promise<void> | null = null;
|
||||||
|
private connectionResolve: (() => void) | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly wsUrl: string = 'wss://connect.varan.app',
|
||||||
|
private readonly apiUrl: string = 'https://connect.varan.app'
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async connect(): Promise<string> {
|
||||||
|
if (this.connectionPromise) {
|
||||||
|
return this.connectionPromise.then(() => this.uniqueId!);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionPromise = new Promise((resolve, reject) => {
|
||||||
|
if (this.ws?.readyState === WebSocket.OPEN && this.uniqueId) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isConnecting) {
|
||||||
|
// Wait for existing connection attempt
|
||||||
|
const checkInterval = setInterval(() => {
|
||||||
|
if (!this.isConnecting && this.uniqueId) {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isConnecting = true;
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.ws = new WebSocket(this.wsUrl);
|
||||||
|
|
||||||
|
this.ws.onopen = (): void => {
|
||||||
|
console.log('[VARANINJECT] WebSocket connection opened');
|
||||||
|
this.isConnecting = false;
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onmessage = (event: MessageEvent): void => {
|
||||||
|
try {
|
||||||
|
const message: WebSocketMessage = JSON.parse(event.data as string);
|
||||||
|
this.handleMessage(message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[VARANINJECT] Error parsing WebSocket message:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onerror = (error: Event): void => {
|
||||||
|
console.error('[VARANINJECT] WebSocket error:', error);
|
||||||
|
this.isConnecting = false;
|
||||||
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||||
|
this.attemptReconnect();
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onclose = (): void => {
|
||||||
|
console.log('[VARANINJECT] WebSocket connection closed');
|
||||||
|
this.isConnecting = false;
|
||||||
|
if (!this.uniqueId) {
|
||||||
|
// Connection closed before we got an ID, attempt reconnect
|
||||||
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||||
|
this.attemptReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store resolve function to call when connection message is received
|
||||||
|
this.connectionResolve = (): void => {
|
||||||
|
this.connectionResolve = null;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.isConnecting = false;
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.connectionPromise;
|
||||||
|
return this.uniqueId!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMessage(message: WebSocketMessage): void {
|
||||||
|
if (message.type === 'connection' && message.id) {
|
||||||
|
const connMessage = message as ConnectionMessage;
|
||||||
|
this.uniqueId = connMessage.id;
|
||||||
|
console.log(`[VARANINJECT] Unique ID received: ${this.uniqueId}`);
|
||||||
|
// Resolve connection promise if pending
|
||||||
|
if (this.connectionResolve) {
|
||||||
|
this.connectionResolve();
|
||||||
|
}
|
||||||
|
} else if (message.type === 'acknowledgment') {
|
||||||
|
console.log('[VARANINJECT] Data acknowledged by server');
|
||||||
|
} else if (message.type === 'server_message') {
|
||||||
|
console.log('[VARANINJECT] Received server message');
|
||||||
|
const serverWrapper = message as ServerMessageWrapper;
|
||||||
|
if (serverWrapper.message) {
|
||||||
|
this.handleServerMessage(serverWrapper.message);
|
||||||
|
}
|
||||||
|
} else if (message.type === 'data' && message.data) {
|
||||||
|
console.log('[VARANINJECT] Received data message');
|
||||||
|
const dataWrapper = message as DataMessageWrapper;
|
||||||
|
if (dataWrapper.data) {
|
||||||
|
// Type guard to ensure data is a ServerMessage
|
||||||
|
const serverData = dataWrapper.data as ServerMessage;
|
||||||
|
this.handleServerMessage(serverData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleServerMessage(data: ServerMessage): void {
|
||||||
|
// Check if there's a handler for this message type
|
||||||
|
if (this.messageHandlers.has(data.type)) {
|
||||||
|
const handler = this.messageHandlers.get(data.type);
|
||||||
|
handler?.(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for request ID handlers (for signTransaction messages)
|
||||||
|
if ('id' in data && typeof data.id === 'number') {
|
||||||
|
const requestId = String(data.id);
|
||||||
|
const handler = this.messageHandlers.get(`request_${requestId}`);
|
||||||
|
if (handler) {
|
||||||
|
handler(data);
|
||||||
|
this.messageHandlers.delete(`request_${requestId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(data: ClientRequest): Promise<void> {
|
||||||
|
const id = await this.ensureConnected();
|
||||||
|
|
||||||
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||||
|
throw new Error('WebSocket is not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
...data,
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendViaAPI(data: ClientRequest): Promise<unknown> {
|
||||||
|
const id = await this.ensureConnected();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.apiUrl}/send/${id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[VARANINJECT] Error sending data via API:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData(): Promise<unknown> {
|
||||||
|
const id = await this.ensureConnected();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.apiUrl}/data/${id}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[VARANINJECT] Error getting data:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage<T extends ServerMessage = ServerMessage>(
|
||||||
|
type: string,
|
||||||
|
handler: MessageHandler<T>
|
||||||
|
): () => void {
|
||||||
|
this.messageHandlers.set(type, handler as MessageHandler<ServerMessage>);
|
||||||
|
|
||||||
|
// Return unsubscribe function
|
||||||
|
return () => {
|
||||||
|
this.messageHandlers.delete(type);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForResponse<T extends ServerMessage = ServerMessage>(
|
||||||
|
requestId: string | number,
|
||||||
|
timeout: number = 30000
|
||||||
|
): Promise<T> {
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
this.messageHandlers.delete(`request_${requestId}`);
|
||||||
|
reject(new Error(`Timeout waiting for response to request ${requestId}`));
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
this.messageHandlers.set(`request_${requestId}`, (data: ServerMessage) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
resolve(data as T);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureConnected(): Promise<string> {
|
||||||
|
if (!this.uniqueId || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||||
|
return await this.connect();
|
||||||
|
}
|
||||||
|
return this.uniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private attemptReconnect(): void {
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
||||||
|
|
||||||
|
console.log(`[VARANINJECT] Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.connectionPromise = null;
|
||||||
|
this.connectionResolve = null;
|
||||||
|
this.connect().catch(console.error);
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(): void {
|
||||||
|
if (this.ws) {
|
||||||
|
this.ws.close();
|
||||||
|
this.ws = null;
|
||||||
|
}
|
||||||
|
this.uniqueId = null;
|
||||||
|
this.connectionPromise = null;
|
||||||
|
this.connectionResolve = null;
|
||||||
|
this.messageHandlers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
getUniqueId(): string | null {
|
||||||
|
return this.uniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected(): boolean {
|
||||||
|
return this.ws !== null && this.ws.readyState === WebSocket.OPEN && !!this.uniqueId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -21,16 +21,11 @@ const defaultStore: WalletStoreType = {
|
|||||||
|
|
||||||
export class WalletStore {
|
export class WalletStore {
|
||||||
private readonly store: WalletStorage;
|
private readonly store: WalletStorage;
|
||||||
private readonly storeSubscribe: (cb: (ev: StorageEvent) => void) => void;
|
|
||||||
private _store: WalletStoreType;
|
private _store: WalletStoreType;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.store = window.localStorage as WalletStorage;
|
this.store = window.localStorage as WalletStorage;
|
||||||
this._store = Object.assign(defaultStore, JSON.parse(this.store.getItem(VARAN_WALLET_LS_KEY) || '{}'));
|
this._store = Object.assign(defaultStore, JSON.parse(this.store.getItem(VARAN_WALLET_LS_KEY) || '{}'));
|
||||||
this.storeSubscribe = (cb): void => {
|
|
||||||
window.addEventListener('storage', cb);
|
|
||||||
};
|
|
||||||
this._setupListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private callbacks: {
|
private callbacks: {
|
||||||
@@ -44,69 +39,6 @@ export class WalletStore {
|
|||||||
transactions: [],
|
transactions: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
private _setupListeners(): void {
|
|
||||||
let currentState = this._store;
|
|
||||||
this.storeSubscribe(ev => {
|
|
||||||
ev.stopImmediatePropagation();
|
|
||||||
if (ev.key !== VARAN_WALLET_LS_KEY) return;
|
|
||||||
|
|
||||||
const data = JSON.parse(ev.newValue || '{}') as WalletStoreType;
|
|
||||||
const keys = Object.keys(data) as (keyof WalletStoreType)[];
|
|
||||||
for (const key of keys) {
|
|
||||||
if (key === 'injectedMetadataKnown') continue;
|
|
||||||
if (key === 'accounts') {
|
|
||||||
const currentAccounts = currentState[key];
|
|
||||||
const dataAccounts = data[key];
|
|
||||||
if (currentAccounts.length !== dataAccounts.length) {
|
|
||||||
currentState = data;
|
|
||||||
this._store = data;
|
|
||||||
this.dispatch(key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let shouldDispatch = false;
|
|
||||||
dataAccounts.forEach(account => {
|
|
||||||
if (!currentAccounts.includes(account)) {
|
|
||||||
shouldDispatch = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (shouldDispatch) {
|
|
||||||
currentState = data;
|
|
||||||
this._store = data;
|
|
||||||
this.dispatch(key);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (key === 'transactions') {
|
|
||||||
const currentTransactions = currentState[key];
|
|
||||||
const dataTransactions = data[key];
|
|
||||||
if (currentTransactions.length !== dataTransactions.length) {
|
|
||||||
currentState = data;
|
|
||||||
this._store = data;
|
|
||||||
this.dispatch(key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let shouldDispatch = false;
|
|
||||||
dataTransactions.forEach(transaction => {
|
|
||||||
const currentTransaction = currentTransactions.find(t => t.id === transaction.id);
|
|
||||||
if (currentTransaction && currentTransaction.status !== transaction.status) {
|
|
||||||
shouldDispatch = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (shouldDispatch) {
|
|
||||||
currentState = data;
|
|
||||||
this._store = data;
|
|
||||||
this.dispatch(key);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private dispatch<Key extends keyof WalletStoreType>(key: Key): void {
|
private dispatch<Key extends keyof WalletStoreType>(key: Key): void {
|
||||||
const callbacks = this.callbacks[key];
|
const callbacks = this.callbacks[key];
|
||||||
if (!callbacks || callbacks.length === 0) return;
|
if (!callbacks || callbacks.length === 0) return;
|
||||||
|
|||||||
+3
-9
@@ -7,12 +7,6 @@ declare global {
|
|||||||
|
|
||||||
export type WalletStorage = Storage;
|
export type WalletStorage = Storage;
|
||||||
|
|
||||||
export type InjectionConfig = {
|
|
||||||
callbackUrl: string;
|
|
||||||
connectTimeout?: number;
|
|
||||||
closeAppOnActions?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WebApp = typeof WebAppConst;
|
export type WebApp = typeof WebAppConst;
|
||||||
export interface ConnectRequest {
|
export interface ConnectRequest {
|
||||||
method: 'connect';
|
method: 'connect';
|
||||||
@@ -20,10 +14,10 @@ export interface ConnectRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SignTransactionRequest {
|
export interface SignTransactionRequest {
|
||||||
method: 'signTransaction';
|
method: 'sT';
|
||||||
id: number;
|
id: number;
|
||||||
tx: string;
|
tx: `p:${string}` | `bytes:${string}`;
|
||||||
wallet: string;
|
wallet?: string;
|
||||||
callback: string;
|
callback: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-15
@@ -1,24 +1,12 @@
|
|||||||
import type { WebApp, InjectionConfig } from './types';
|
import type { WebApp } from './types';
|
||||||
import { Wallet } from './wallet';
|
import { Wallet } from './wallet';
|
||||||
|
|
||||||
const defaultInjectionConfig: Required<InjectionConfig> = {
|
const injectVaranWallet = (webApp: WebApp): void => {
|
||||||
callbackUrl: '',
|
|
||||||
connectTimeout: 30_000,
|
|
||||||
closeAppOnActions: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const injectVaranWallet = (webApp: WebApp, config: InjectionConfig = defaultInjectionConfig): void => {
|
|
||||||
const isInTelegram = webApp.platform !== 'unknown';
|
const isInTelegram = webApp.platform !== 'unknown';
|
||||||
|
|
||||||
if (!isInTelegram) return;
|
if (!isInTelegram) return;
|
||||||
|
|
||||||
const callbackUrl = config.callbackUrl || defaultInjectionConfig.callbackUrl;
|
const wallet = new Wallet(webApp);
|
||||||
|
|
||||||
if (!callbackUrl) {
|
|
||||||
throw new Error('Callback URL must be provided to injectVaranWallet');
|
|
||||||
}
|
|
||||||
|
|
||||||
const wallet = new Wallet(webApp, { ...defaultInjectionConfig, ...config });
|
|
||||||
|
|
||||||
window.injectedWeb3 = {
|
window.injectedWeb3 = {
|
||||||
varan: wallet,
|
varan: wallet,
|
||||||
|
|||||||
+164
-98
@@ -1,30 +1,28 @@
|
|||||||
import type { ConnectRequest, SignTransactionRequest, WebApp } from "./types";
|
import type { WebApp } from "./types";
|
||||||
import type { InjectionConfig } from "./types";
|
|
||||||
import { WalletStore } from "./store";
|
import { WalletStore } from "./store";
|
||||||
import type { Injected, InjectedAccount, InjectedExtension, InjectedMetadataKnown, InjectedWindowProvider } from "@polkadot/extension-inject/types";
|
import type { Injected, InjectedAccount, InjectedExtension, InjectedMetadataKnown, InjectedWindowProvider } from "@polkadot/extension-inject/types";
|
||||||
import { encodeRequest } from "./encryption";
|
import { SignerPayloadJSON, SignerPayloadRaw, SignerResult } from "@polkadot/types/types";
|
||||||
import { SignerPayloadRaw, SignerResult } from "@polkadot/types/types";
|
import { generateRequestId } from "./helpers";
|
||||||
import { generateRequestId, parseSignedTxFromStartParam, parseWalletsFromStartParam } from "./helpers";
|
|
||||||
import { mockAccountsSubscribe } from "./constants";
|
import { mockAccountsSubscribe } from "./constants";
|
||||||
|
import {
|
||||||
|
WalletSocket,
|
||||||
|
type ConnectResponseMessage,
|
||||||
|
type SignTransactionResponseMessage,
|
||||||
|
type ConnectRequest,
|
||||||
|
type SignTransactionRequest
|
||||||
|
} from "./socket";
|
||||||
export class Wallet implements InjectedWindowProvider {
|
export class Wallet implements InjectedWindowProvider {
|
||||||
private readonly varanWalletUrl!: string;
|
|
||||||
private readonly store: WalletStore;
|
private readonly store: WalletStore;
|
||||||
|
private readonly socket: WalletSocket;
|
||||||
private readonly name = 'Varan Wallet';
|
private readonly name = 'Varan Wallet';
|
||||||
private readonly callbackUrl: string;
|
private readonly varanWalletUrl = 'https://t.me/varanwalletbot/start?startapp=';
|
||||||
private readonly connectTimeout: number;
|
|
||||||
private readonly closeAppOnActions: boolean;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly webApp: WebApp,
|
private readonly webApp: WebApp,
|
||||||
{ callbackUrl, connectTimeout, closeAppOnActions }: Required<InjectionConfig>,
|
|
||||||
) {
|
) {
|
||||||
this.varanWalletUrl = 'https://t.me/varanwalletbot/start?startapp=';
|
|
||||||
this.store = new WalletStore();
|
this.store = new WalletStore();
|
||||||
this.callbackUrl = callbackUrl;
|
this.socket = new WalletSocket();
|
||||||
this.connectTimeout = connectTimeout;
|
void this._initializeSocket();
|
||||||
this.closeAppOnActions = closeAppOnActions;
|
|
||||||
void this._startParamsCheck();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get version(): string {
|
get version(): string {
|
||||||
@@ -34,48 +32,44 @@ export class Wallet implements InjectedWindowProvider {
|
|||||||
private get signer(): InjectedExtension['signer'] {
|
private get signer(): InjectedExtension['signer'] {
|
||||||
return {
|
return {
|
||||||
signRaw: (raw) => this.signRawTransaction(raw),
|
signRaw: (raw) => this.signRawTransaction(raw),
|
||||||
|
signPayload: (payload) => this.signPayloadTransaction(payload),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _startParamsCheck(): Promise<void> {
|
private async _initializeSocket(): Promise<void> {
|
||||||
let actions = 0;
|
try {
|
||||||
const newWalletAddresses = parseWalletsFromStartParam(this.webApp);
|
await this.socket.connect();
|
||||||
const currentAccounts = await this.accounts;
|
|
||||||
|
|
||||||
if (newWalletAddresses) {
|
// Listen for wallet connection messages
|
||||||
const allWallets = Array.from(
|
this.socket.onMessage<ConnectResponseMessage>('connect', (data) => {
|
||||||
new Set(
|
if (data.success && data.wallets && Array.isArray(data.wallets)) {
|
||||||
currentAccounts
|
const allWallets = Array.from(
|
||||||
.map(a => a.address)
|
new Set(
|
||||||
.concat(newWalletAddresses)
|
this.store.get('accounts').concat(data.wallets)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
|
this.store.set('accounts', allWallets);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.store.set('accounts', allWallets);
|
// Listen for transaction signing responses
|
||||||
actions += 1;
|
this.socket.onMessage<SignTransactionResponseMessage>('signTransaction', (data) => {
|
||||||
}
|
const currentTransactions = this.store.get('transactions');
|
||||||
|
const pendingTransactionIndex = currentTransactions.findIndex(
|
||||||
|
t => t.id === data.id && t.status === 'signing'
|
||||||
|
);
|
||||||
|
|
||||||
const tx = parseSignedTxFromStartParam(this.webApp);
|
if (pendingTransactionIndex !== -1) {
|
||||||
|
currentTransactions[pendingTransactionIndex] = {
|
||||||
if (tx) {
|
...currentTransactions[pendingTransactionIndex],
|
||||||
const currentTransactions = this.store.get('transactions');
|
status: data.success ? 'success' : 'failed',
|
||||||
|
signedTx: data.signedTx || '',
|
||||||
const pendingTransactionIndex = currentTransactions.findIndex(t => t.id === tx.id && t.status === 'signing');
|
};
|
||||||
if (pendingTransactionIndex === -1) return;
|
this.store.set('transactions', currentTransactions);
|
||||||
|
}
|
||||||
currentTransactions[pendingTransactionIndex] = {
|
});
|
||||||
...currentTransactions[pendingTransactionIndex],
|
} catch (error) {
|
||||||
status: tx.success ? 'success' : 'failed',
|
console.error('[VARANINJECT] Error initializing socket:', error);
|
||||||
signedTx: tx.signedTx ? tx.signedTx : '',
|
|
||||||
};
|
|
||||||
|
|
||||||
this.store.set('transactions', currentTransactions);
|
|
||||||
actions += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (actions && this.closeAppOnActions) {
|
|
||||||
this.webApp.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +94,7 @@ export class Wallet implements InjectedWindowProvider {
|
|||||||
|
|
||||||
private get extensionMetadata(): InjectedExtension['metadata'] {
|
private get extensionMetadata(): InjectedExtension['metadata'] {
|
||||||
const metadata = this.store.get('injectedMetadataKnown');
|
const metadata = this.store.get('injectedMetadataKnown');
|
||||||
const setMetadata = async (metadata: InjectedMetadataKnown) => {
|
const setMetadata = async (metadata: InjectedMetadataKnown): Promise<boolean> => {
|
||||||
await this.store.set('injectedMetadataKnown', metadata);
|
await this.store.set('injectedMetadataKnown', metadata);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -114,87 +108,159 @@ export class Wallet implements InjectedWindowProvider {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async signPayloadTransaction(payload: SignerPayloadJSON): Promise<SignerResult> {
|
||||||
|
const accounts = await this.accounts;
|
||||||
|
if (!accounts.length) throw new Error('No accounts found');
|
||||||
|
|
||||||
|
const address = accounts.find(a => a.address === payload.address);
|
||||||
|
if (!address) throw new Error('Provided account was not found');
|
||||||
|
|
||||||
|
const requestId = generateRequestId();
|
||||||
|
const request: SignTransactionRequest = {
|
||||||
|
method: 'sT',
|
||||||
|
tx: `p:${JSON.stringify(payload)}`,
|
||||||
|
id: requestId,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[VARANINJECT] signPayloadTransaction request', request);
|
||||||
|
|
||||||
|
// Store transaction as pending
|
||||||
|
const currentTransactions = this.store.get('transactions');
|
||||||
|
currentTransactions.push({
|
||||||
|
id: requestId,
|
||||||
|
signedTx: '',
|
||||||
|
status: 'signing',
|
||||||
|
});
|
||||||
|
this.store.set('transactions', currentTransactions);
|
||||||
|
|
||||||
|
// Send via WebSocket
|
||||||
|
await this.socket.sendViaAPI(request);
|
||||||
|
|
||||||
|
const botUrl = `${this.varanWalletUrl}${this.socket.getUniqueId()}`;
|
||||||
|
|
||||||
|
this.webApp.openTelegramLink(botUrl);
|
||||||
|
|
||||||
|
// Wait for response
|
||||||
|
return new Promise<SignerResult>((resolve, reject) => {
|
||||||
|
const unsubscribe = this.socket.onMessage<SignTransactionResponseMessage>('signTransaction', (data) => {
|
||||||
|
if (data.id === requestId) {
|
||||||
|
unsubscribe();
|
||||||
|
|
||||||
|
const currentTransactions = this.store.get('transactions');
|
||||||
|
const transaction = currentTransactions.find(t => t.id === requestId);
|
||||||
|
|
||||||
|
if (transaction && transaction.status !== 'signing') {
|
||||||
|
if (transaction.status === 'success') {
|
||||||
|
resolve({
|
||||||
|
id: transaction.id,
|
||||||
|
signature: transaction.signedTx,
|
||||||
|
} as SignerResult);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Transaction signing failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async signRawTransaction(raw: SignerPayloadRaw): Promise<SignerResult> {
|
private async signRawTransaction(raw: SignerPayloadRaw): Promise<SignerResult> {
|
||||||
|
if (raw.type === 'payload') {
|
||||||
|
console.log('[VARANINJECT] signRawTransaction payload', raw.data);
|
||||||
|
return this.signPayloadTransaction(JSON.parse(raw.data));
|
||||||
|
}
|
||||||
|
|
||||||
const accounts = await this.accounts;
|
const accounts = await this.accounts;
|
||||||
if (!accounts.length) throw new Error('No accounts found');
|
if (!accounts.length) throw new Error('No accounts found');
|
||||||
|
|
||||||
const address = accounts.find(a => a.address === raw.address);
|
const address = accounts.find(a => a.address === raw.address);
|
||||||
if (!address) throw new Error('Provided account was not found');
|
if (!address) throw new Error('Provided account was not found');
|
||||||
|
|
||||||
|
const requestId = generateRequestId();
|
||||||
const request: SignTransactionRequest = {
|
const request: SignTransactionRequest = {
|
||||||
method: 'signTransaction',
|
method: 'sT',
|
||||||
tx: raw.data,
|
tx: `bytes:${raw.data}`,
|
||||||
wallet: raw.address,
|
wallet: raw.address,
|
||||||
callback: this.callbackUrl,
|
id: requestId,
|
||||||
id: generateRequestId(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const encodedRequest = encodeRequest(request);
|
// Store transaction as pending
|
||||||
const botUrl = `${this.varanWalletUrl}${encodedRequest}`;
|
const currentTransactions = this.store.get('transactions');
|
||||||
this.store.set('transactions', [{
|
currentTransactions.push({
|
||||||
id: request.id,
|
id: requestId,
|
||||||
signedTx: '',
|
signedTx: '',
|
||||||
status: 'signing',
|
status: 'signing',
|
||||||
}]);
|
});
|
||||||
|
this.store.set('transactions', currentTransactions);
|
||||||
|
|
||||||
this.webApp.openTelegramLink(botUrl);
|
// Send via WebSocket
|
||||||
|
await this.socket.sendViaAPI(request);
|
||||||
|
|
||||||
return new Promise<SignerResult>((resolve) => {
|
// Wait for response
|
||||||
const id = this.store.subscribe('transactions', (transactions) => {
|
return new Promise<SignerResult>((resolve, reject) => {
|
||||||
const pendingTransaction = transactions.find(t => t.id === request.id && t.status !== 'signing');
|
const unsubscribe = this.socket.onMessage<SignTransactionResponseMessage>('signTransaction', (data) => {
|
||||||
if (pendingTransaction) {
|
if (data.id === requestId) {
|
||||||
this.store.unsubscribe('transactions', id);
|
unsubscribe();
|
||||||
resolve({
|
|
||||||
id: pendingTransaction.id,
|
const currentTransactions = this.store.get('transactions');
|
||||||
signature: pendingTransaction.signedTx,
|
const transaction = currentTransactions.find(t => t.id === requestId);
|
||||||
} as SignerResult);
|
|
||||||
|
if (transaction && transaction.status !== 'signing') {
|
||||||
|
if (transaction.status === 'success') {
|
||||||
|
resolve({
|
||||||
|
id: transaction.id,
|
||||||
|
signature: transaction.signedTx,
|
||||||
|
} as SignerResult);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Transaction signing failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _connect(): Promise<void> {
|
private async _connect(): Promise<void> {
|
||||||
const request: ConnectRequest = {
|
try {
|
||||||
method: 'connect',
|
// Ensure socket is connected
|
||||||
callback: this.callbackUrl,
|
await this.socket.connect();
|
||||||
};
|
|
||||||
|
|
||||||
const encodedRequest = encodeRequest(request);
|
// Send connect request via WebSocket
|
||||||
console.log('encodedRequest', encodedRequest);
|
await this.socket.sendViaAPI({
|
||||||
const botUrl = `${this.varanWalletUrl}${encodedRequest}`;
|
method: 'connect',
|
||||||
console.log('botUrl', botUrl);
|
} as ConnectRequest);
|
||||||
await this.webApp.openTelegramLink(botUrl);
|
|
||||||
|
|
||||||
return new Promise<void>((resolve) => {
|
const botUrl = `${this.varanWalletUrl}${this.socket.getUniqueId()}`;
|
||||||
const id = this.store.subscribe('accounts', (accounts) => {
|
await this.webApp.openTelegramLink(botUrl);
|
||||||
if (accounts.length) {
|
|
||||||
this.store.unsubscribe('accounts', id);
|
// Wait for accounts to be populated
|
||||||
resolve();
|
return new Promise<void>((resolve) => {
|
||||||
}
|
const unsubscribe = this.socket.onMessage<ConnectResponseMessage>('connect', (data) => {
|
||||||
|
if (data.success && data.wallets && data.wallets.length > 0) {
|
||||||
|
unsubscribe();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error('[VARANINJECT] Connect error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(): Promise<InjectedExtension> {
|
async connect(): Promise<InjectedExtension> {
|
||||||
const accounts = await this.accounts;
|
const accounts = await this.accounts;
|
||||||
if (!accounts.length) {
|
if (!accounts.length) {
|
||||||
await Promise.race([
|
await this._connect();
|
||||||
this._connect(),
|
|
||||||
new Promise<void>((_, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
reject(new Error('Connect timeout'));
|
|
||||||
}, this.connectTimeout);
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve({
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
version: this.version,
|
version: this.version,
|
||||||
accounts: this.extensionAccounts,
|
accounts: this.extensionAccounts,
|
||||||
signer: this.signer,
|
signer: this.signer,
|
||||||
metadata: this.extensionMetadata,
|
metadata: this.extensionMetadata,
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async enable(): Promise<Injected> {
|
async enable(): Promise<Injected> {
|
||||||
|
|||||||
Reference in new issue
Block a user