UNPKG

@intuweb3/sdk

Version:

INTU SDK - Modern blockchain interaction toolkit

1,230 lines (1,229 loc) 140 kB
import Web3 from "web3"; import { loadJson, JSON_PATHS } from "../utils/json-imports.js"; import { preRegister, getPolybaseKey, registerStepOne, registerStepTwo, registerStepThree, } from "./cryptography/index.js"; import { getPublicKey, finalizeEvent } from "nostr-tools/pure"; import { hexToBytes } from "@noble/hashes/utils"; import { getGraphEndpoint } from "../tools/constants.js"; import { getQuery as getGraphQLQueryString } from "../tools/graph.js"; import getContractsDetails from "./web3/contracts/contractInfos.js"; import { ethers } from "ethers"; export function parseProxyUrl(proxyUrl) { let url; if (typeof window === "undefined") { const NodeURL = eval('require("url").URL'); url = new NodeURL(proxyUrl); } else { url = new window.URL(proxyUrl); } const config = { host: url.hostname, port: parseInt(url.port) || (url.protocol === "https:" ? 443 : 8080), protocol: url.protocol.replace(":", ""), }; if (url.username && url.password) { config.auth = { username: decodeURIComponent(url.username), password: decodeURIComponent(url.password), }; } return config; } export async function createProxiedProvider(originalRpcUrl, proxyConfig) { if (typeof window !== "undefined") { console.log("🌐 Browser: Creating standard Web3 provider (proxy handled by browser/network)"); const provider = new Web3.providers.HttpProvider(originalRpcUrl); const web3 = new Web3(provider); return web3; } const proxyUrl = proxyConfig.auth ? `${proxyConfig.protocol || "http"}://${proxyConfig.auth.username}:${proxyConfig.auth.password}@${proxyConfig.host}:${proxyConfig.port}` : `${proxyConfig.protocol || "http"}://${proxyConfig.host}:${proxyConfig.port}`; class ProxiedProvider { rpcUrl; proxyUrlString; constructor(rpcUrl, proxyUrlString) { this.rpcUrl = rpcUrl; this.proxyUrlString = proxyUrlString; } send(payload, callback) { if (callback) { this._sendAsync(payload, callback); return; } throw new Error("Web3.js 1.10.4 requires callback for send method"); } async _sendAsync(payload, callback) { const unsupported = new Set([ "eth_subscribe", "eth_unsubscribe", "eth_newFilter", "eth_newBlockFilter", "eth_newPendingTransactionFilter", "eth_getFilterChanges", "eth_getFilterLogs", ]); if (unsupported.has(payload.method)) { return callback(null, { id: payload.id, jsonrpc: "2.0", error: { code: -32601, message: "notifications not supported" }, }); } try { const result = await makeProxiedRpcCall(this.rpcUrl, payload.method, payload.params || [], this.proxyUrlString); const response = { id: payload.id, jsonrpc: "2.0", result: result, }; callback(null, response); } catch (error) { callback(error, null); } } sendAsync(payload, callback) { this._sendAsync(payload, callback); } connected = true; isConnecting = false; timeout = 60000; headers = {}; get host() { return this.rpcUrl; } disconnect() { this.connected = false; } _listeners = {}; on(event, listener) { if (!this._listeners[event]) { this._listeners[event] = []; } this._listeners[event].push(listener); return this; } removeListener(event, listener) { if (this._listeners[event]) { const index = this._listeners[event].indexOf(listener); if (index > -1) { this._listeners[event].splice(index, 1); } } return this; } removeAllListeners(event) { if (event) { delete this._listeners[event]; } else { this._listeners = {}; } return this; } emit(event, ...args) { if (this._listeners[event]) { this._listeners[event].forEach((listener) => listener(...args)); } return this; } supportsSubscriptions() { return false; } toString() { return `ProxiedProvider(${this.rpcUrl})`; } } const provider = new ProxiedProvider(originalRpcUrl, proxyUrl); const web3 = new Web3(provider); return web3; } export async function createProxiedSigner(privateKey, proxyUrlOrConfig, rpcUrlToUse) { let proxyConfig; if (typeof proxyUrlOrConfig === "string") { proxyConfig = parseProxyUrl(proxyUrlOrConfig); } else { proxyConfig = proxyUrlOrConfig; } const web3 = await createProxiedProvider(rpcUrlToUse, proxyConfig); const account = web3.eth.accounts.privateKeyToAccount(privateKey); web3.eth.accounts.wallet.add(account); web3.eth.defaultAccount = account.address; return { web3, account }; } export async function createProxiedWebSocket(url, proxyConfig, protocols) { if (typeof window !== "undefined") { return new WebSocket(url, protocols); } const net = await import("net"); const tls = await import("tls"); const ws = await import("ws"); const wsUrl = new URL(url); const isSecure = wsUrl.protocol === "wss:"; const targetPort = wsUrl.port || (isSecure ? 443 : 80); console.log(`🔗 Creating ${isSecure ? "WSS" : "WS"} proxy tunnel to ${wsUrl.hostname}:${targetPort}`); return new Promise((resolve, reject) => { const proxySocket = net.createConnection(proxyConfig.port, proxyConfig.host); proxySocket.on("connect", () => { const connectReq = `CONNECT ${wsUrl.hostname}:${targetPort} HTTP/1.1\r\nHost: ${wsUrl.hostname}:${targetPort}\r\n\r\n`; proxySocket.write(connectReq); }); let connectResponseReceived = false; proxySocket.on("data", (data) => { if (!connectResponseReceived) { const response = data.toString(); if (response.includes("200")) { connectResponseReceived = true; proxySocket.removeAllListeners("data"); if (isSecure) { const tlsSocket = tls.connect({ socket: proxySocket, servername: wsUrl.hostname, }, () => { const proxiedWebSocket = new ws.WebSocket(url, protocols, { createConnection: () => tlsSocket, }); resolve(proxiedWebSocket); }); tlsSocket.on("error", (error) => { console.error("❌ TLS socket error:", error); reject(error); }); } else { const proxiedWebSocket = new ws.WebSocket(url, protocols, { createConnection: () => proxySocket, }); resolve(proxiedWebSocket); } } else { reject(new Error(`CONNECT failed: ${response}`)); } } }); proxySocket.on("error", (error) => { console.error("❌ Proxy socket error:", error); reject(error); }); }); } function createProxiedWebSocketSync(url, proxyUrl, protocols) { if (typeof window !== "undefined") { return new WebSocket(url, protocols); } const nodeRequire = eval("require"); const { WebSocket: NodeWS } = nodeRequire("ws"); const { HttpsProxyAgent } = nodeRequire("https-proxy-agent"); const agent = new HttpsProxyAgent(proxyUrl); return new NodeWS(url, protocols, { agent }); } export function subscribeToQueryWithProxy(queryName, params, proxyUrl, callbacks) { let createClient; const initializeModules = async () => { const graphqlWs = await import("graphql-ws"); createClient = graphqlWs.createClient; if (typeof window === "undefined") { if (proxyUrl && applyWebSocketPatch) { try { await applyWebSocketPatch(proxyUrl); } catch (e) { console.warn("[subscribeToQueryWithProxy] Failed to apply WS patch:", e); } } } }; let subscription = null; let isActive = true; const startSubscription = async () => { try { await initializeModules(); const client = createClient({ url: "wss://indexer.hyperindex.xyz/501af95/v1/graphql", webSocketImpl: typeof window === "undefined" ? (url, protocols) => createProxiedWebSocketSync(url.toString(), proxyUrl, protocols) : WebSocket, connectionParams: { headers: { "Sec-WebSocket-Protocol": "graphql-ws", }, }, on: { connected: () => { }, error: (err) => { console.error("❌ Proxied WebSocket Error:", err); if (isActive) callbacks.error(err); }, closed: () => { if (isActive) callbacks.complete(); }, }, retryAttempts: 10, shouldRetry: () => isActive, }); const getQuery = async () => { const graphModule = await import("../tools/graph.js"); console.log("⚠️ Using simplified query approach - arbitrumSepoliaSubscriptions not exported"); const subscriptionQueries = { subscribeToVaultUserPreRegister: ({ vaultAddress }) => ` subscription { VaultUserPreRegister( where: { vaultAddress: { _ilike: "${vaultAddress}" } } order_by: { blockNumber: asc } ) { user dbKey encSharedKey megaPublicKey parisEncKey } } `, subscribeToVaultUserRegisteredAll: ({ vaultAddress }) => ` subscription { VaultUserRegisteredAll( where: { vaultAddress: { _ilike: "${vaultAddress}" } } order_by: { blockNumber: asc } ) { user step3Crypto blockNumber blockTimestamp } } `, }; if (!subscriptionQueries[queryName]) { throw new Error(`Missing subscription query: ${queryName}`); } return subscriptionQueries[queryName](params); }; subscription = client.subscribe({ query: await getQuery(), }, { next: (data) => { if (isActive) { callbacks.next(data); } }, error: (error) => { if (isActive) { callbacks.error(error); } }, complete: () => { if (isActive) { callbacks.complete(); } }, }); } catch (error) { console.error("🔥 Failed to start proxied subscription:", error); if (isActive) { callbacks.error(error); } } }; startSubscription(); return { unsubscribe: () => { isActive = false; if (subscription) { subscription(); } }, }; } export async function makeProxiedRequest(url, proxyConfig, options = {}) { if (typeof window !== "undefined") { throw new Error("makeProxiedRequest is only supported in Node.js"); } const https = await import("https"); const http = await import("http"); const net = await import("net"); const tls = await import("tls"); const { URL } = await import("url"); const urlObj = new URL(url); const isHttps = urlObj.protocol === "https:"; const method = options.method || "GET"; const headers = options.headers || {}; const body = options.body || ""; return new Promise((resolve, reject) => { if (!isHttps) { const requestHeaders = { ...headers, Host: urlObj.hostname, }; if (body) { requestHeaders["Content-Length"] = Buffer.byteLength(body).toString(); } const requestOptions = { hostname: proxyConfig.host, port: proxyConfig.port, path: url, method, headers: requestHeaders, }; const req = http.request(requestOptions, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { resolve(data); }); }); req.on("error", reject); if (body) { req.write(body); } req.end(); return; } const proxySocket = net.createConnection(proxyConfig.port, proxyConfig.host); let connectResponseReceived = false; proxySocket.on("connect", () => { const connectReq = `CONNECT ${urlObj.hostname}:${urlObj.port || 443} HTTP/1.1\r\nHost: ${urlObj.hostname}:${urlObj.port || 443}\r\n\r\n`; proxySocket.write(connectReq); }); proxySocket.on("data", (data) => { if (!connectResponseReceived) { const response = data.toString(); if (response.includes("200")) { connectResponseReceived = true; proxySocket.removeAllListeners("data"); const tlsSocket = tls.connect({ socket: proxySocket, servername: urlObj.hostname, }, () => { const requestOptions = { method, headers, createConnection: () => tlsSocket, host: urlObj.hostname, path: urlObj.pathname + urlObj.search, }; const req = https.request(requestOptions, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { resolve(data); }); }); req.on("error", (err) => { console.error("❌ HTTPS tunneled request error:", err); reject(err); }); if (body) { req.write(body); } req.end(); }); } else { reject(new Error(`CONNECT failed: ${response}`)); proxySocket.end(); } } }); proxySocket.on("error", (error) => { console.error("❌ Proxy socket error:", error); reject(error); }); }); } export async function makeProxiedGraphQLRequest(endpoint, query, proxyUrl) { const proxyConfig = parseProxyUrl(proxyUrl); try { const requestBody = JSON.stringify({ query }); const response = await makeProxiedRequest(endpoint, proxyConfig, { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": "intu-sdk-proxy", Accept: "application/json", }, body: requestBody, }); if (typeof response === "string" && response.trim().length > 0) { const trimmed = response.trim(); if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) { console.error("[makeProxiedGraphQLRequest] Response is not valid JSON. Full response:", trimmed); throw new Error("Proxy returned non-JSON response: " + trimmed.slice(0, 200)); } } else { console.error("[makeProxiedGraphQLRequest] Empty or invalid response received"); throw new Error("Empty or invalid response from proxy"); } const parsedResponse = JSON.parse(response); return parsedResponse; } catch (error) { console.error("❌ GraphQL proxy request failed:", error); throw error; } } export async function makeProxiedRpcCall(url, method, params, proxyUrl) { const proxyConfig = parseProxyUrl(proxyUrl); const postData = JSON.stringify({ jsonrpc: "2.0", method, params, id: Math.floor(Math.random() * 1000), }); try { const response = await makeProxiedRequest(url, proxyConfig, { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": "intu-sdk-proxy", }, body: postData, }); const trimmed = typeof response === "string" ? response.trim() : ""; const looksLikeJson = trimmed.startsWith("{") || trimmed.startsWith("["); if (!looksLikeJson) { return { error: { message: trimmed || "Non-JSON response from RPC" } }; } try { const jsonResponse = JSON.parse(response); if (jsonResponse.error) throw new Error(jsonResponse.error.message); return jsonResponse.result; } catch (parseError) { throw new Error(`Invalid JSON response from proxy: ${parseError.message}`); } } catch (error) { console.error("[PROXY] RPC call failed:", error); throw error; } } export async function fetchSubgraphDataWithProxy(endpoint, query, proxyUrl) { const goldskyBackup = "https://api.goldsky.com/api/public/project_cm1jkqjytq51s01yr9q6h3idj/subgraphs/intu-arb-sepolia/1.0.0/gn"; try { const result = await makeProxiedGraphQLRequest(endpoint, query, proxyUrl); const hasPayload = result && (result.data || result.result); let looksEmpty = false; if (hasPayload) { const actual = result.data || result.result; if (actual && typeof actual === "object" && Object.values(actual).every((v) => Array.isArray(v) && v.length === 0)) { looksEmpty = true; } } if (hasPayload && !looksEmpty) { return result; } console.log("Primary Hyperindex subgraph returned empty/no data, trying Goldsky backup..."); return await makeProxiedGraphQLRequest(goldskyBackup, query, proxyUrl); } catch (primaryError) { console.error("Error in primary Hyperindex subgraph request:", primaryError); try { return await makeProxiedGraphQLRequest(goldskyBackup, query, proxyUrl); } catch (backupError) { console.error("Error in backup subgraph request:", backupError); throw backupError; } } } async function _getTransactionsWeb3(vaultAddress, web3, chainId, proxyUrl) { const VaultJson = await _getVaultJson(); const vaultContract = new web3.eth.Contract(VaultJson.abi, vaultAddress); try { const txInfo = await vaultContract.methods.transactionInfos(1).call(); const votesNeeded = txInfo.votesNeeded; const graphqlEndpoint = getGraphEndpoint(chainId); const query = getGraphQLQueryString("getVaultTransactions", chainId, { vaultAddress, }); const data = await fetchSubgraphDataWithProxy(graphqlEndpoint, query, proxyUrl); console.log("🔍 [DEBUG] Transaction GraphQL response:", JSON.stringify(data, null, 2)); const resultKey = chainId === 421614 ? "TransactionProposed" : "transactionProposeds"; const confirmedKey = chainId === 421614 ? "TransactionUserConfirmed" : "transactionUserConfirmeds"; const actualData = data?.data || data; if (!actualData || !actualData[resultKey]) { console.log("🔍 [DEBUG] Available keys:", actualData ? Object.keys(actualData) : "null"); return []; } const transactions = actualData[resultKey] || []; const confirmations = actualData[confirmedKey] || []; console.log(`📊 [DEBUG] Found ${transactions.length} transactions and ${confirmations.length} confirmations`); return transactions.map((tx) => { const signedTransactions = confirmations .filter((confirmedTx) => confirmedTx.txId === tx.txId) .map((confirmedTx) => ({ user: confirmedTx.user, signedTransaction: confirmedTx.signedTransaction, })); return { id: tx.txId, transactionData: tx.transactionInfo, transactionNotes: tx.notes, signedTransactionsNeeded: Number(votesNeeded), userSignedTransactions: signedTransactions, }; }); } catch (error) { return []; } } export async function getAllTransactionsWithProxy(vaultAddress, proxiedSigner, proxyUrl) { console.log("[GET_ALL_TX_W3] 🔄 Getting all transactions with Web3.js proxy implementation"); const { web3 } = proxiedSigner; const chainId = Number(await web3.eth.getChainId()); try { return await _getTransactionsWeb3(vaultAddress, web3, chainId, proxyUrl); } catch (error) { console.error("[GET_ALL_TX_W3] ❌ Failed to get transactions:", error.message); throw error; } } export async function getVaultsWithProxy(userAddress, provider, proxyUrl, proxiedSigner) { const proxyConfig = parseProxyUrl(proxyUrl); const isWeb3Provider = provider && provider.eth && typeof provider.eth.getChainId === "function"; let originalUrl; let chainId; if (isWeb3Provider) { originalUrl = provider.currentProvider?.connection?.url || provider.currentProvider?.host || "https://arbitrum-sepolia.infura.io/v3/f0b33e4b953e4306b6d5e8b9f9d51567"; chainId = Number(await provider.eth.getChainId()); } else { originalUrl = provider.connection?.url || "https://arbitrum-sepolia.infura.io/v3/f0b33e4b953e4306b6d5e8b9f9d51567"; chainId = (await provider.getNetwork()).chainId; } const userTransactionCount = parseInt(await makeProxiedRpcCall(originalUrl, "eth_getTransactionCount", [userAddress, "latest"], proxyUrl), 16); if (userTransactionCount === 0) { console.log("⚠️ [DEBUG] User has 0 transactions, returning empty vault list"); console.log("💡 [DEBUG] This might be expected if the user hasn't made any transactions"); console.log("💡 [DEBUG] However, vault creation should count as a transaction"); return []; } let vaultAddresses; if (isWeb3Provider) { vaultAddresses = await _getVaultAddressesWeb3(userAddress, chainId, proxyUrl); } else { const { getFilteredUserInitializedLogs } = await import("./web3/providerfunctions.js"); vaultAddresses = await getFilteredUserInitializedLogs(userAddress, provider); } if (!vaultAddresses || vaultAddresses.length === 0) { return []; } console.log("📋 [DEBUG] Retrieving vault data for", vaultAddresses.length, "vaults"); const vaultDataPromises = vaultAddresses.map(async (vaultAddress, idx) => { try { let vaultData; if (proxiedSigner) { vaultData = await _getVaultWeb3(vaultAddress, proxiedSigner.web3); } else if (isWeb3Provider) { vaultData = await _getVaultWeb3(vaultAddress, provider); } else { const { getVault } = await import("./web3/providerfunctions.js"); vaultData = await getVault(vaultAddress, provider); } return vaultData; } catch (error) { console.error(`❌ [DEBUG] Failed to get data for vault ${vaultAddress}:`, error.message); return null; } }); const vaultData = (await Promise.all(vaultDataPromises)).filter(Boolean); const userVaults = vaultData.filter((vault) => { const isUserInVault = vault.users.some((user) => user.address.toLowerCase() === userAddress.toLowerCase()); return isUserInVault; }); return Promise.all(userVaults.map(async (vault) => { const proposals = []; let transactions = []; try { if (proxiedSigner) { let transactionResult = await getAllTransactionsWithProxy(vault.vaultAddress, proxiedSigner, proxyUrl); transactions = transactionResult; } else { if (isWeb3Provider) { transactions = await _getTransactionsWeb3(vault.vaultAddress, provider, chainId, proxyUrl); } } } catch (error) { console.error(`Failed to get transactions for vault ${vault.vaultAddress}:`, error.message); } return { ...vault, proposals: proposals, transactions: transactions, }; })); } async function _getVaultAddressesWeb3(userAddress, chainId, proxyUrl) { const graphqlEndpoint = getGraphEndpoint(chainId); if (!graphqlEndpoint) { console.error("No GraphQL endpoint for chainId " + chainId); return []; } const queryString = getGraphQLQueryString("getVaultCreateds", chainId, { userAddress, }); console.log(queryString); try { const data = await fetchSubgraphDataWithProxy(graphqlEndpoint, queryString, proxyUrl); const resultKey = chainId === 421614 ? "VaultCreated" : "vaultCreateds"; const actualData = data?.data || data; if (actualData && actualData[resultKey]) { const vaultAddresses = actualData[resultKey].map((item) => item.vaultAddress); return vaultAddresses; } else { } } catch (error) { console.warn("Failed to fetch vault addresses via proxy:", error); } return []; } async function _getVaultWeb3(vaultAddress, web3) { const VaultJson = await _getVaultJson(); const vaultContract = new web3.eth.Contract(VaultJson.abi, vaultAddress); try { const infos = await vaultContract.methods.vaultInfos().call(); const users = await Promise.all(infos.users.map(async (userAddr) => { try { const userInfo = await vaultContract.methods .userInfos(userAddr) .call(); return { address: userAddr, isRegistered: userInfo.isRegistered || false, }; } catch (error) { return { address: userAddr, isRegistered: false, }; } })); const decodeHexString = (hexStr) => { try { if (!hexStr || hexStr === "0x" || hexStr.length <= 2) return ""; const cleanHex = hexStr.replace(/^0x/, "").replace(/00+$/, ""); if (cleanHex.length === 0) return ""; let result = ""; for (let i = 0; i < cleanHex.length; i += 2) { const hexPair = cleanHex.substr(i, 2); const charCode = parseInt(hexPair, 16); if (charCode !== 0) { result += String.fromCharCode(charCode); } } return result; } catch (error) { console.warn("Failed to decode hex string:", hexStr, error); return ""; } }; const decodedVaultName = decodeHexString(infos.name || ""); return { vaultAddress, users, masterPublicKey: infos.masterPublicKey || "", masterPublicAddress: infos.masterPublicAddress || "", vaultName: decodedVaultName, threshold: Number(infos.transactionThreshold) || 0, transactionThreshold: Number(infos.transactionThreshold) || 0, userCount: Number(infos.usersCount) || 0, status: infos.completed ? 1 : 0, createdDate: Number(infos.createdDate) || 0, proposalCount: Number(infos.proposalCount) || 0, transactionCount: Number(infos.transactionCount) || 0, rotateThreshold: Number(infos.rotateThreshold) || 0, adminThreshold: Number(infos.adminThreshold) || 0, createdBlock: Number(infos.createdBlock) || 0, resharingOccurred: Boolean(infos.resharingOccurred), }; } catch (error) { console.error("Error getting vault info for", vaultAddress, ":", error); throw error; } } const _getVaultJson = (() => { let vaultJson = null; return async () => { if (!vaultJson) { vaultJson = await loadJson(JSON_PATHS.VAULT); } return vaultJson; }; })(); async function _getVaultInfosWeb3(vaultAddress, web3) { const VaultJson = await _getVaultJson(); const vaultContract = new web3.eth.Contract(VaultJson.abi, vaultAddress); const infos = (await vaultContract.methods.vaultInfos().call()); const vaultDetails = (await vaultContract.methods.vault().call()); const decodeHexString = (hexStr) => { try { if (!hexStr || hexStr === "0x" || hexStr.length <= 2) return ""; const cleanHex = hexStr.replace(/^0x/, "").replace(/00+$/, ""); if (cleanHex.length === 0) return ""; let result = ""; for (let i = 0; i < cleanHex.length; i += 2) { const hexPair = cleanHex.substr(i, 2); const charCode = parseInt(hexPair, 16); if (charCode !== 0) { result += String.fromCharCode(charCode); } } return result; } catch (error) { return ""; } }; return { users: infos.users, masterPublicKey: infos.masterPublicKey || "", masterPublicAddress: infos.masterPublicAddress || "", vaultName: decodeHexString(infos.name || ""), threshold: Number(infos.transactionThreshold) || 0, transactionThreshold: Number(infos.transactionThreshold) || 0, userCount: Number(infos.usersCount) || 0, status: infos.completed ? 1 : 0, createdDate: Number(infos.createdDate), proposalCount: Number(infos.proposalCount), transactionCount: Number(infos.transactionCount), rotateThreshold: Number(infos.rotateThreshold), adminThreshold: Number(infos.adminThreshold), createdBlock: Number(infos.createdBlock) || 0, resharingOccurred: Boolean(infos.resharingOccurred), seed: vaultDetails.seed && vaultDetails.seed.length > 0 ? vaultDetails.seed : infos.seed || "", }; } async function _getUsersToAddWeb3(vaultAddress, web3) { const VaultJson = await _getVaultJson(); const vaultContract = new web3.eth.Contract(VaultJson.abi, vaultAddress); try { const users = await vaultContract.methods.getUserToAdd().call(); return users; } catch (e) { return []; } } async function _getUsersToRemoveWeb3(vaultAddress, web3) { const VaultJson = await _getVaultJson(); const vaultContract = new web3.eth.Contract(VaultJson.abi, vaultAddress); try { const users = await vaultContract.methods.getUserToRemove().call(); return users; } catch (e) { return []; } } export async function _getUserPreRegisterInfosWeb3(vaultAddress, userAddress, chainId, proxyUrl) { const graphqlEndpoint = getGraphEndpoint(chainId); if (!graphqlEndpoint) { console.error("No GraphQL endpoint for chainId " + chainId); return { user: userAddress, registered: false, parisEncKey: "", megaPublicKey: "", encMegaSecretKey: "", dbKey: "", }; } const queryString = getGraphQLQueryString("vaultUserPreRegister", chainId, { vaultAddress, userAddress, }); const data = await fetchSubgraphDataWithProxy(graphqlEndpoint, queryString, proxyUrl); if (!data) { return { user: userAddress, registered: false, parisEncKey: "", megaPublicKey: "", encMegaSecretKey: "", dbKey: "", }; } const resultKey = chainId === 421614 || chainId === 42161 ? "VaultUserPreRegister" : "vaultUserPreRegisters"; let userPreRegister = null; if (Array.isArray(data[resultKey])) { userPreRegister = data[resultKey].find((entry) => entry.user.toLowerCase() === userAddress.toLowerCase()); } else if (data[resultKey] && typeof data[resultKey] === "object" && data[resultKey] !== null) { if (data[resultKey].user && data[resultKey].user.toLowerCase() === userAddress.toLowerCase()) { userPreRegister = data[resultKey]; } } if (userPreRegister) { return { user: userPreRegister.user, registered: true, parisEncKey: userPreRegister.parisEncKey || "", megaPublicKey: userPreRegister.megaPublicKey || "", encMegaSecretKey: userPreRegister.encSharedKey || userPreRegister.encMegaSecretKey || "", dbKey: userPreRegister.dbKey || "", }; } return { user: userAddress, registered: false, parisEncKey: "", megaPublicKey: "", encMegaSecretKey: "", dbKey: "", }; } async function _getPreRegisterInfosWeb3(vaultAddress, web3, chainId, proxyUrl) { const vaultInfos = await _getVaultInfosWeb3(vaultAddress, web3); let usersFromVault = vaultInfos.users || []; const usersToAdd = await _getUsersToAddWeb3(vaultAddress, web3); let allUsers = [...usersFromVault]; if (usersToAdd && usersToAdd.length > 0) { allUsers = [ ...allUsers, ...usersToAdd.filter((u) => !allUsers.includes(u)), ]; } const parisEncKeyArray = new Array(allUsers.length).fill(""); const megaPublicKeyArray = new Array(allUsers.length).fill(""); const encMegaSecretKeyArray = new Array(allUsers.length).fill(""); const dbPublicKeyArray = new Array(allUsers.length).fill(""); const graphqlEndpoint = getGraphEndpoint(chainId); if (!graphqlEndpoint) { return { parisEncKeyArray, megaPublicKeyArray, encMegaSecretKeyArray, dbPublicKeyArray, users: allUsers, }; } const queryString = getGraphQLQueryString("vaultUserPreRegister", chainId, { vaultAddress, }); const data = await fetchSubgraphDataWithProxy(graphqlEndpoint, queryString, proxyUrl); const resultKey = chainId === 421614 || chainId === 42161 ? "VaultUserPreRegister" : "vaultUserPreRegisters"; const actualData = data?.data || data; if (actualData && actualData[resultKey] && Array.isArray(actualData[resultKey])) { actualData[resultKey].forEach((preRegister) => { const userIndex = allUsers.findIndex((user) => user.toLowerCase() === preRegister.user.toLowerCase()); if (userIndex !== -1) { parisEncKeyArray[userIndex] = preRegister.parisEncKey || ""; megaPublicKeyArray[userIndex] = preRegister.megaPublicKey || ""; encMegaSecretKeyArray[userIndex] = preRegister.encSharedKey || preRegister.encMegaSecretKey || ""; dbPublicKeyArray[userIndex] = preRegister.dbKey || ""; } }); } else { console.log("⚠️ No pre-registration data found - will retry"); } return { parisEncKeyArray, megaPublicKeyArray, encMegaSecretKeyArray, dbPublicKeyArray, users: allUsers, }; } async function _getUtilsParamsWeb3(vaultAddress, userAddress, web3, chainId, proxyUrl) { const vaultInfosFromWeb3 = await _getVaultInfosWeb3(vaultAddress, web3); const preRegisterData = await _getPreRegisterInfosWeb3(vaultAddress, web3, chainId, proxyUrl); const userIndex = preRegisterData.users.findIndex((addr) => addr.toLowerCase() === userAddress.toLowerCase()); if (userIndex === -1) { throw new Error("User " + userAddress + " not found in vault " + vaultAddress + " for _getUtilsParamsWeb3"); } const seed = vaultInfosFromWeb3.seed; const thresholdToUse = vaultInfosFromWeb3.threshold !== undefined ? Number(vaultInfosFromWeb3.threshold) : 0; const result = { seed, threshold: thresholdToUse, index: userIndex, megaPkArray: preRegisterData.megaPublicKeyArray, encMegaSecretKeyArrayMapped: preRegisterData.encMegaSecretKeyArray, dbKeyArrayMapped: preRegisterData.dbPublicKeyArray, }; console.log(" - MegaPk array content:", result.megaPkArray.map((k) => (k ? k.substring(0, 10) + "..." : "empty"))); return result; } export async function getUserSignatureWeb3(vaultAddress, web3, account) { const VaultJson = await _getVaultJson(); const vaultContract = new web3.eth.Contract(VaultJson.abi, vaultAddress); const vaultInfos = await vaultContract.methods.vaultInfos().call(); const encryptionMessage = vaultInfos.encryptionMessage; const deterministicMessage = vaultAddress + encryptionMessage + account.address; const messageHash = web3.utils.keccak256(web3.utils.utf8ToHex(deterministicMessage)); const signature = web3.eth.accounts.sign(messageHash, account.privateKey); return signature.signature; } export async function _getUserSignatureWeb3(vaultAddress, proxiedSigner, chainId, proxyUrl) { const { web3, account } = proxiedSigner; return await getUserSignatureWeb3(vaultAddress, web3, account); } async function _preRegisterStepWeb3(vaultAddress, parisEncKey, megaPublicKey, encSharedKey, dbKey, proxiedSigner, returnHash) { const { web3, account } = proxiedSigner; const VaultJson = await _getVaultJson(); const vaultContract = new web3.eth.Contract(VaultJson.abi, vaultAddress); const gasPrice = await web3.eth.getGasPrice(); const finalGasPrice = (BigInt(gasPrice) * BigInt(105)) / BigInt(100); const preRegisterMethod = vaultContract.methods.preRegister(parisEncKey, megaPublicKey, encSharedKey, dbKey); let estimatedGas; try { estimatedGas = await preRegisterMethod.estimateGas({ from: account.address, }); } catch (error) { if (error.message?.includes("_b.call is not a function")) { } estimatedGas = BigInt(500000); } const gasLimit = (BigInt(estimatedGas) * BigInt(120)) / BigInt(100); if (returnHash) { const txData = preRegisterMethod.encodeABI(); const nonce = await web3.eth.getTransactionCount(account.address, "latest"); const txObject = { from: account.address, to: vaultAddress, data: txData, gas: gasLimit.toString(), gasPrice: finalGasPrice.toString(), nonce: Number(nonce), value: "0", }; const signedTx = await account.signTransaction(txObject); if (!signedTx.rawTransaction) { throw new Error("Failed to sign preRegister transaction - rawTransaction is missing"); } return signedTx.rawTransaction; } const txData = preRegisterMethod.encodeABI(); const nonce = await web3.eth.getTransactionCount(account.address, "latest"); const txObject = { from: account.address, to: vaultAddress, data: txData, gas: gasLimit.toString(), gasPrice: finalGasPrice.toString(), nonce: Number(nonce), value: "0", }; console.log("🔧 [DEBUG] Signing and sending transaction..."); const signedTx = await account.signTransaction(txObject); if (!signedTx.rawTransaction) { throw new Error("Failed to sign preRegister transaction - rawTransaction is missing"); } const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction); console.log("✅ [DEBUG] Transaction sent successfully"); return receipt; } export async function preRegistrationWithProxy(vaultAddress, proxiedSigner, proxyUrl, intuSignature, returnHash) { const { web3, account } = proxiedSigner; const userAddress = account.address; const chainId = Number(await web3.eth.getChainId()); const retryWithDelay = async (fn, retries = 5, delay = 1000) => { try { return await fn(); } catch (error) { console.log(`Attempt failed, retries left: ${retries}. Error:`, error?.message || error); if (retries === 0) { throw error; } await new Promise((resolve) => setTimeout(resolve, delay)); return retryWithDelay(fn, retries - 1, delay); } }; let preRegCheck = await retryWithDelay(() => _getUserPreRegisterInfosWeb3(vaultAddress, userAddress, chainId, proxyUrl), 5, 2000); if (preRegCheck.registered) { console.log("user already preregistered : ", userAddress); return `User already preregistered : ${userAddress}`; } let signature; intuSignature ? (signature = intuSignature) : (signature = await _getUserSignatureWeb3(vaultAddress, proxiedSigner, chainId, proxyUrl)); const { encryptionKey, megaPublicKey, encMegaSecretKey } = await preRegister(signature); let dbKey = await getPolybaseKey(signature); let sk = dbKey.key; let pkfinal = getPublicKey(hexToBytes(String(sk))); let rh = returnHash || false; return await _preRegisterStepWeb3(vaultAddress, encryptionKey, megaPublicKey, encMegaSecretKey, pkfinal, proxiedSigner, rh); } async function _registerUserAllWeb3(vaultAddress, step1Dealings, pedersenOpeningKey, pedersenOpeningKappa, pedersenOpeningLambda, simpleDealingKey, simpleDealingLambda, pedersenTranscriptKey, pedersenTranscriptKappa, pedersenTranscriptLambda, step3Crypto, proxiedSigner, returnHash) { const { web3, account } = proxiedSigner; const VaultJson = await _getVaultJson(); const vaultContract = new web3.eth.Contract(VaultJson.abi, vaultAddress); const gasPrice = await web3.eth.getGasPrice(); const finalGasPrice = (BigInt(gasPrice) * BigInt(105)) / BigInt(100); const registerMethod = vaultContract.methods.registerAllSteps(step1Dealings, pedersenOpeningKey, pedersenOpeningKappa, pedersenOpeningLambda, simpleDealingKey, simpleDealingLambda, pedersenTranscriptKey, pedersenTranscriptKappa, pedersenTranscriptLambda, step3Crypto); let estimatedGas; try { estimatedGas = await registerMethod.estimateGas({ from: account.address }); } catch (error) { console.error("Error estimating gas for registerAllSteps, using fallback: ", error); estimatedGas = BigInt(2000000); } const gasLimit = (BigInt(estimatedGas) * BigInt(120)) / BigInt(100); if (returnHash) { const txData = registerMethod.encodeABI(); const nonce = await web3.eth.getTransactionCount(account.address, "latest"); const txObject = { from: account.address, to: vaultAddress, data: txData, gas: gasLimit.toString(), gasPrice: finalGasPrice.toString(), nonce: Number(nonce), value: "0", }; const signedTx = await account.signTransaction(txObject); const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction); return receipt; } const txData = registerMethod.encodeABI(); const nonce = await web3.eth.getTransactionCount(account.address, "latest"); const txObject = { from: account.address, to: vaultAddress, data: txData, gas: gasLimit.toString(), gasPrice: finalGasPrice.toString(), nonce: Number(nonce), value: "0", }; try { const signedTx = await account.signTransaction(txObject); if (!signedTx.rawTransaction) { throw new Error("Failed to sign registerAllSteps transaction - rawTransaction is missing"); } const txResult = await web3.eth.sendSignedTransaction(signedTx.rawTransaction); if (process.env.DEBUG) { console.log("[REG_W3] ✅ Transaction successful:", txResult.transactionHash); } return txResult; } catch (error) { console.error("[REG_W3] ❌ Transaction failed:", error); const errorMessage = error.message || ""; if (errorMessage.includes("User already registered in this vault")) { console.log("[REG_W3] ✅ Registration already completed for this user. No action needed."); return { success: true, message: "User already registered", alreadyRegistered: true, }; } console.log("[REG_W3] ⚠️ Retrying registration with alternative gas estimation..."); try { estimatedGas = BigInt(2000000); const gasLimit = (BigInt(estimatedGas) * BigInt(120)) / BigInt(100); const retryTxData = registerMethod.encodeABI(); const retryNonce = await web3.eth.getTransactionCount(account.address, "latest"); const retryTxObject = { from: account.address, to: vaultAddress, data: retryTxData, gas: gasLimit.toString(), gasPrice: finalGasPrice.toString(), nonce: Number(retryNonce), value: "0", }; const retrySignedTx = await account.signTransaction(retryTxObject); if (!retrySignedTx.rawTransaction) { throw new Error("Failed to sign registerAllSteps retry transaction - rawTransaction is missing"); } const txResult = await web3.eth.sendSignedTransaction(retrySignedTx.rawTransaction); return txResult; } catch (error) { console.error("[REG_W3] ❌ Transaction failed:", error); const errorMessage = error.message || ""; if (errorMessage.includes("User already registered")) { console.log("[REG_W3] ✅ Registration already completed for this user. No action needed."); return { success: true, message: "User already registered", alreadyRegistered: true, }; } if (error.message && error.message.includes("receipt")) { console.log("[REG_W3] 🔄 Trying alternative: send transaction without waiting for receipt"); try { const txData = registerMethod.encodeABI(); const nonce = await web3.eth.getTransactionCount(account.address, "latest"); const txObject = { from: account.address, to: vaultAddress, data: txData, gas: gasLimit.toString(), gasPrice: finalGasPrice.toString(), nonce: Number(nonce), value: "0", }; const signedTx = await account.signTransaction(txObject); const sentTx = await web3.eth.sendSignedTransaction(signedTx.rawTransaction); console.log("[REG_W3] ✅ Transaction sent successfully:", sentTx.transactionHash); return sentTx; } catch (altError) {