@appliedblockchain/silentdatarollup-ethers-provider
Version:
Ethers.js provider for Silent Data [Rollup]
336 lines (331 loc) • 13.3 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
DEBUG_NAMESPACE: () => DEBUG_NAMESPACE,
SDInterface: () => SDInterface,
SilentDataRollupProvider: () => SilentDataRollupProvider
});
module.exports = __toCommonJS(index_exports);
// src/constants.ts
var DEBUG_NAMESPACE = "silentdata:ethers-provider";
// src/provider.ts
var import_silentdatarollup_core = require("@appliedblockchain/silentdatarollup-core");
var import_ethers = require("ethers");
function isPromise(value) {
return value && typeof value.then === "function";
}
function getNetwork(networkName, chainId) {
if (chainId) {
return new import_ethers.Network(networkName ?? "custom", chainId);
}
if (networkName === import_silentdatarollup_core.NetworkName.MAINNET) {
return new import_ethers.Network(networkName, import_silentdatarollup_core.ChainId.MAINNET);
} else if (networkName === import_silentdatarollup_core.NetworkName.TESTNET) {
return new import_ethers.Network(networkName, import_silentdatarollup_core.ChainId.TESTNET);
}
return void 0;
}
var providerDefaultOptions = {
batchMaxCount: 1
};
var SilentDataRollupProvider = class _SilentDataRollupProvider extends import_ethers.JsonRpcProvider {
constructor(config) {
(0, import_ethers.assertArgument)(config.rpcUrl, "rpcUrl is mandatory", "config", config);
const network = getNetwork(config.network, config.chainId);
const request = _SilentDataRollupProvider.getRequest({
rpcUrl: config.rpcUrl
});
const combinedOptions = {
...providerDefaultOptions,
...config.options
};
super(request, network, combinedOptions);
(0, import_ethers.assertArgument)(
config.signer || config.privateKey,
"signer or privateKey is mandatory",
"config",
config
);
this.baseProvider = new import_silentdatarollup_core.SilentDataRollupBase(config);
this.config = config;
this.config.authSignatureType = config.authSignatureType || import_silentdatarollup_core.SignatureType.Raw;
if (config.signer) {
try {
this.signer = config.signer.connect(this);
} catch {
this.signer = config.signer;
}
} else {
const wallet = new import_ethers.Wallet(config.privateKey);
this.signer = wallet.connect(this);
}
}
async _send(payload) {
if (Array.isArray(payload)) {
throw new Error("Batch requests are not currently supported");
}
const isEthCallOrEstimateGas = payload.method === "eth_call" || payload.method === "eth_estimateGas";
if (isEthCallOrEstimateGas && Array.isArray(payload.params)) {
const txParams = payload.params[0];
if (typeof txParams === "object" && txParams !== null) {
txParams.from = await this.signer.getAddress();
}
}
let isPrivateLogsRequest = false;
if (payload.method === "eth_getLogs" && Array.isArray(payload.params) && payload.params.length > 0) {
const filter = payload.params[0];
if (filter && typeof filter === "object" && "_isPrivateEvent" in filter) {
isPrivateLogsRequest = !!filter._isPrivateEvent;
if (isPrivateLogsRequest) {
const { _isPrivateEvent, ...filterWithoutPrivateEvent } = filter;
payload.params[0] = filterWithoutPrivateEvent;
}
}
}
const request = this._getConnection();
request.body = JSON.stringify(payload);
request.setHeader("content-type", "application/json");
const requiresAuthHeaders = isPrivateLogsRequest || import_silentdatarollup_core.SIGN_RPC_METHODS.includes(payload.method) || (0, import_silentdatarollup_core.isSignableContractCall)(payload, this.baseProvider.contracts);
if (requiresAuthHeaders) {
if (this.config.delegate) {
const {
[import_silentdatarollup_core.HEADER_DELEGATE]: xDelegate,
[import_silentdatarollup_core.HEADER_DELEGATE_SIGNATURE]: xDelegateSignature,
[import_silentdatarollup_core.HEADER_EIP712_DELEGATE_SIGNATURE]: xEip712DelegateSignature
} = await this.baseProvider.getDelegateHeaders(this);
request.setHeader(import_silentdatarollup_core.HEADER_DELEGATE, xDelegate);
if (xDelegateSignature) {
request.setHeader(import_silentdatarollup_core.HEADER_DELEGATE_SIGNATURE, xDelegateSignature);
}
if (xEip712DelegateSignature) {
request.setHeader(
import_silentdatarollup_core.HEADER_EIP712_DELEGATE_SIGNATURE,
xEip712DelegateSignature
);
}
}
const {
[import_silentdatarollup_core.HEADER_TIMESTAMP]: xTimestamp,
[import_silentdatarollup_core.HEADER_SIGNATURE]: xSignature,
[import_silentdatarollup_core.HEADER_EIP712_SIGNATURE]: xEip712Signature
} = await this.baseProvider.getAuthHeaders(this, payload);
request.setHeader(import_silentdatarollup_core.HEADER_TIMESTAMP, xTimestamp);
if (xSignature) {
request.setHeader(import_silentdatarollup_core.HEADER_SIGNATURE, xSignature);
}
if (xEip712Signature) {
request.setHeader(import_silentdatarollup_core.HEADER_EIP712_SIGNATURE, xEip712Signature);
}
}
const response = await request.send();
response.assertOk();
let resp = response.bodyJson;
if (!Array.isArray(resp)) {
resp = [resp];
}
return resp;
}
static getRequest({ rpcUrl }) {
const request = new import_ethers.FetchRequest(rpcUrl);
request.allowGzip = true;
return request;
}
clone() {
const clonedProvider = new _SilentDataRollupProvider(this.config);
return clonedProvider;
}
/**
* Helper method to configure a filter for private events
* @param filter - The original filter
* @param forcePrivateOnly - Whether to force filtering for only PrivateEvents
* @returns The configured filter with proper topics
*/
configurePrivateEventsFilter(filter, forcePrivateOnly = false) {
const privateFilter = {
...filter,
_isPrivateEvent: true
};
privateFilter.topics = privateFilter.topics || [];
if (forcePrivateOnly || privateFilter.eventSignature) {
if (forcePrivateOnly) {
privateFilter.topics = [
import_silentdatarollup_core.PRIVATE_EVENT_SIGNATURE_HASH,
// Only match PrivateEvent
...privateFilter.topics.slice(1)
// Preserve any other topic filters
];
}
if (privateFilter.eventSignature) {
const eventTypeHash = (0, import_silentdatarollup_core.calculateEventTypeHash)(
privateFilter.eventSignature
);
if (forcePrivateOnly) {
privateFilter.topics[1] = eventTypeHash;
} else {
privateFilter.topics = [
import_silentdatarollup_core.PRIVATE_EVENT_SIGNATURE_HASH,
// Only match PrivateEvent
eventTypeHash,
// Only match the specific event type
...(privateFilter.topics || []).slice(2)
// Preserve any other topics
];
}
delete privateFilter.eventSignature;
}
}
return privateFilter;
}
/**
* Gets logs for private events, including authentication headers
* @param filter - The filter parameters for logs
* @returns Array of logs matching the filter
*/
async getAllLogs(filter = {}) {
const privateFilter = this.configurePrivateEventsFilter(filter, false);
return await this.getLogs(privateFilter);
}
/**
* Gets only private events (PrivateEvent logs), including authentication headers
* @param filter - The filter parameters for logs
* @returns Array of logs matching the filter, containing only PrivateEvent logs
*/
async getPrivateLogs(filter = {}) {
const privateFilter = this.configurePrivateEventsFilter(filter, true);
return await this.getLogs(privateFilter);
}
/**
* Override of ethers' getLogs method that preserves our custom _isPrivateEvent property
*
* IMPORTANT: This method mimics the behavior of ethers' original getLogs implementation
* but adds a crucial step to preserve the _isPrivateEvent flag. We need this because:
*
* 1. Ethers' _getFilter method sanitizes filter objects, removing any non-standard properties
* 2. Our _isPrivateEvent flag would be stripped by this sanitization
* 3. We need the flag to reach the _send method to trigger the addition of auth headers
*
* The approach here is to run the normal filter processing, then re-attach our flag as a
* non-enumerable property to avoid JSON serialization issues. This allows the flag to
* survive until _send where we check for it to determine if auth headers are needed.
*
* @param _filter - The filter with our potential _isPrivateEvent property
* @returns Array of logs matching the filter
*/
async getLogs(_filter) {
let filter = this._getFilter(_filter);
if (isPromise(filter)) {
filter = await filter;
}
if (typeof _filter === "object" && "_isPrivateEvent" in _filter && _filter._isPrivateEvent) {
Object.defineProperty(filter, "_isPrivateEvent", {
value: true,
enumerable: false
});
}
const { network, params } = await (0, import_ethers.resolveProperties)({
network: this.getNetwork(),
params: this._perform({ method: "getLogs", filter })
});
return params.map((p) => this._wrapLog(p, network));
}
};
// src/sdInterface.ts
var import_debug = __toESM(require("debug"));
var import_ethers2 = require("ethers");
var debugLog = (0, import_debug.default)(DEBUG_NAMESPACE);
var SDInterface = class extends import_ethers2.Interface {
/**
* Extends the parseLog method to handle PrivateEvent logs
* @param log - The log to parse
* @returns The parsed log description with additional private event details if applicable
*/
parseLog(log) {
const parsedLog = super.parseLog(log);
if (!parsedLog) {
debugLog(
"Failed to parse log - no matching event found or event is anonymous"
);
return null;
}
if (parsedLog.name === "PrivateEvent") {
debugLog("Processing PrivateEvent log");
const eventType = parsedLog.args.eventType;
const payload = parsedLog.args.payload;
debugLog(
`PrivateEvent - eventType: ${eventType}, payload length: ${payload?.length || 0}`
);
parsedLog.innerLog = null;
try {
if (!payload || payload === "0x") {
debugLog("Empty payload for PrivateEvent, cannot decode inner log");
return parsedLog;
}
const syntheticLog = {
topics: [eventType],
data: payload
};
debugLog(`Created synthetic log with topic: ${eventType}`);
const eventFragment = this.getEvent(eventType);
if (!eventFragment) {
debugLog(`No matching event found for topic ${eventType}`);
return parsedLog;
}
debugLog(`Found matching event fragment: ${eventFragment.name}`);
try {
const innerLogDescription = super.parseLog(syntheticLog);
if (innerLogDescription) {
debugLog(
`Successfully decoded inner log: ${innerLogDescription.name}`
);
parsedLog.innerLog = innerLogDescription;
} else {
debugLog(
"Failed to parse inner log - no matching inner event found"
);
}
} catch (innerError) {
debugLog(`Failed to parse synthetic log for inner log:`, innerError);
}
} catch (error) {
debugLog(`Failed to decode private event payload:`, error);
}
} else {
debugLog(`Processing regular event: ${parsedLog.name}`);
}
return parsedLog;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
DEBUG_NAMESPACE,
SDInterface,
SilentDataRollupProvider
});