proximity-wallet-connect
Version:
Wallet Connect package for NEAR Wallet Selector (Proximity fork with transaction fixes).
161 lines (140 loc) • 4.84 kB
text/typescript
import Client from "@walletconnect/sign-client";
import type {
SignClientTypes,
EngineTypes,
ISignClient,
} from "@walletconnect/types";
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
let WalletConnectModal: typeof import("@walletconnect/modal").WalletConnectModal;
import("@walletconnect/modal").then((module) => {
WalletConnectModal = module.WalletConnectModal;
});
import type { SessionTypes } from "@walletconnect/types";
import type {
EventEmitterService,
Subscription,
WalletEvents,
} from "proximity-dex-core";
class WalletConnectClient {
private client: Client;
private emitter: EventEmitterService<WalletEvents>;
private modal: typeof WalletConnectModal.prototype;
async init(opts: SignClientTypes.Options) {
this.client = await Client.init(opts);
}
constructor(emitter: EventEmitterService<WalletEvents>) {
this.emitter = emitter;
}
get session(): ISignClient["session"] {
return this.client.session;
}
on<Event extends SignClientTypes.Event>(
event: Event,
callback: (args: SignClientTypes.EventArguments[Event]) => void
): Subscription {
this.client.on(event, callback);
return {
remove: () => this.client.removeListener(event, callback),
};
}
once<Event extends SignClientTypes.Event>(
event: Event,
callback: (args: SignClientTypes.EventArguments[Event]) => void
) {
this.client.once(event, callback);
}
async connect(
params: EngineTypes.ConnectParams,
qrCodeModal: boolean,
projectId: string,
chainId: string
) {
if (!this.modal) {
this.modal = new WalletConnectModal({
projectId,
chains: [chainId],
explorerExcludedWalletIds: "ALL",
});
}
return new Promise<SessionTypes.Struct>((resolve, reject) => {
this.client
.connect(params)
.then(({ uri, approval }) => {
if (uri) {
if (qrCodeModal) {
this.modal.openModal({
uri,
standaloneChains: [chainId],
});
this.modal.subscribeModal(({ open }) => {
if (!open) {
reject(new Error("User cancelled pairing"));
}
});
} else {
this.emitter.emit("uriChanged", { uri });
}
}
approval()
.then(resolve)
.catch(reject)
.finally(() => this.modal.closeModal());
})
.catch(reject);
});
}
async request<Response>(
params: EngineTypes.RequestParams
): Promise<Response> {
console.log('🚨 [WC-CLIENT] request() called with:', {
topic: params.topic,
chainId: params.chainId,
method: params.request.method,
paramsKeys: Object.keys(params.request.params || {}),
timestamp: new Date().toISOString(),
});
// Check if the session is still active
const session = this.client.session.get(params.topic);
console.log('🚨 [WC-CLIENT] Session state:', {
exists: !!session,
topic: session?.topic,
expiry: session ? new Date(session.expiry * 1000).toISOString() : 'N/A',
peerName: session?.peer?.metadata?.name,
connected: session?.acknowledged,
});
// Check active listeners
const eventNames = (this.client as any).events?.eventNames?.() || [];
console.log('🚨 [WC-CLIENT] Active event listeners:', eventNames);
console.log('🚨 [WC-CLIENT] About to call this.client.request()...');
try {
// Add a race condition with a detailed timeout
const requestPromise = this.client.request<Response>(params);
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new Error(`WalletConnect request timed out after 120 seconds. Method: ${params.request.method}. This usually means Fireblocks didn't respond.`));
}, 120000); // 2 minutes
});
const result = await Promise.race([requestPromise, timeoutPromise]);
console.log('🚨 [WC-CLIENT] request() completed successfully:', {
method: params.request.method,
resultType: typeof result,
isArray: Array.isArray(result),
timestamp: new Date().toISOString(),
});
return result;
} catch (error) {
console.error('🚨 [WC-CLIENT] request() FAILED:', {
method: params.request.method,
error: error,
errorMessage: error instanceof Error ? error.message : String(error),
errorStack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString(),
});
throw error;
}
}
async disconnect(params: EngineTypes.DisconnectParams) {
return this.client.disconnect(params);
}
}
export default WalletConnectClient;