@intuweb3/sdk
Version:
INTU SDK - Modern blockchain interaction toolkit
1,230 lines (1,229 loc) • 140 kB
JavaScript
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) {