feat(omnl): HYBX-BATCH-001 package, rail scripts, regulatory docs, CI
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- Add OMNL/CBK Indonesia submission and audit binder docs, manifests, attestations - Add scripts/omnl transaction-package pipeline, LEI/PvP helpers, jq/lib fixtures - Update entity master data, MASTER_INDEX, TODOS, dbis-rail docs and rulebook - Add proof_package/regulatory skeleton and transaction package zip + snapshot JSON - validate-omnl-rail workflow, forge-verification-proxy tweak, .gitignore hygiene - Bump smom-dbis-138 (cronos verify docs/scripts) and explorer-monorepo (SPA + env report) Made-with: Cursor
This commit is contained in:
@@ -63,7 +63,14 @@ function send(res, status, data) {
|
||||
async function forwardEtherscanFormat(payload) {
|
||||
const query = new URLSearchParams({ module: 'contract', action: 'verifysourcecode' });
|
||||
const path = `/api/?${query}`;
|
||||
const body = JSON.stringify(payload);
|
||||
// Blockscout's Etherscan-compatible endpoint expects classic form fields, not JSON.
|
||||
// Keep the Forge payload keys, but serialize them as application/x-www-form-urlencoded.
|
||||
const form = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(payload)) {
|
||||
if (value === undefined || value === null || value === '') continue;
|
||||
form.set(key, String(value));
|
||||
}
|
||||
const body = form.toString();
|
||||
const url = new URL(path, BLOCKSCOUT_URL);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -74,7 +81,7 @@ async function forwardEtherscanFormat(payload) {
|
||||
path: url.pathname + url.search,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': Buffer.byteLength(body),
|
||||
Host: url.hostname + (url.port ? ':' + url.port : ''),
|
||||
},
|
||||
@@ -98,7 +105,7 @@ async function forwardEtherscanFormat(payload) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward to Blockscout v2 flattened-code API (for Standard JSON, we pass as source_code).
|
||||
* Forward to Blockscout v2 flattened-code verification API.
|
||||
*/
|
||||
async function forwardV2Flattened(payload) {
|
||||
const addr = payload.contractaddress || payload.contractAddress;
|
||||
@@ -157,6 +164,90 @@ async function forwardV2Flattened(payload) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward to Blockscout v2 verification API for Standard JSON input.
|
||||
*/
|
||||
async function forwardV2StandardInput(payload) {
|
||||
const addr = payload.contractaddress || payload.contractAddress;
|
||||
const sourceCode = payload.sourceCode ?? payload.source_code;
|
||||
const standardJson =
|
||||
typeof sourceCode === 'string'
|
||||
? sourceCode
|
||||
: JSON.stringify(sourceCode);
|
||||
const path = `/api/v2/smart-contracts/${addr}/verification/via/standard-input`;
|
||||
const boundary = `----forge-verification-proxy-${Math.random().toString(16).slice(2)}`;
|
||||
const parts = [];
|
||||
const appendField = (name, value) => {
|
||||
if (value === undefined || value === null || value === '') return;
|
||||
parts.push(Buffer.from(`--${boundary}\r\n`));
|
||||
parts.push(Buffer.from(`Content-Disposition: form-data; name="${name}"\r\n\r\n`));
|
||||
parts.push(Buffer.from(`${value}\r\n`));
|
||||
};
|
||||
const appendFile = (name, filename, content, contentType = 'application/json') => {
|
||||
parts.push(Buffer.from(`--${boundary}\r\n`));
|
||||
parts.push(Buffer.from(`Content-Disposition: form-data; name="${name}"; filename="${filename}"\r\n`));
|
||||
parts.push(Buffer.from(`Content-Type: ${contentType}\r\n\r\n`));
|
||||
parts.push(Buffer.isBuffer(content) ? content : Buffer.from(String(content)));
|
||||
parts.push(Buffer.from('\r\n'));
|
||||
};
|
||||
|
||||
const compilerVersion = payload.compilerversion || payload.compilerVersion || 'v0.8.20+commit.a1b79de6';
|
||||
const contractName = payload.contractname || payload.contractName || 'Contract';
|
||||
const licenseType = payload.licensetype || payload.licenseType || 'mit';
|
||||
const constructorArgs =
|
||||
payload.constructor_args ??
|
||||
payload.constructorArguments ??
|
||||
payload.constructorArgumentsHex ??
|
||||
payload.constructorArgs ??
|
||||
'';
|
||||
|
||||
appendField('compiler_version', compilerVersion);
|
||||
appendField('contract_name', contractName);
|
||||
appendField('autodetect_constructor_args', String(payload.autodetectConstructorArguments !== false));
|
||||
appendField('license_type', licenseType);
|
||||
appendField('constructor_args', constructorArgs);
|
||||
if (payload.evmversion || payload.evm_version) appendField('evm_version', payload.evmversion || payload.evm_version);
|
||||
if (payload.optimizationUsed !== undefined || payload.optimization_used !== undefined) {
|
||||
appendField('is_optimization_enabled', String([true, '1', 1, 'true'].includes(payload.optimizationUsed ?? payload.optimization_used)));
|
||||
}
|
||||
if (payload.runs !== undefined || payload.optimization_runs !== undefined) {
|
||||
appendField('optimization_runs', String(parseInt(payload.runs ?? payload.optimization_runs ?? '200', 10) || 200));
|
||||
}
|
||||
appendFile('files[0]', 'standard-input.json', standardJson, 'application/json');
|
||||
parts.push(Buffer.from(`--${boundary}--\r\n`));
|
||||
const body = Buffer.concat(parts);
|
||||
const url = new URL(path, BLOCKSCOUT_URL);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request(
|
||||
{
|
||||
hostname: url.hostname,
|
||||
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
||||
path: url.pathname,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
||||
'Content-Length': body.length,
|
||||
},
|
||||
},
|
||||
(res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => { data += chunk; });
|
||||
res.on('end', () => {
|
||||
try {
|
||||
resolve({ status: res.statusCode, data: data ? JSON.parse(data) : {}, raw: data });
|
||||
} catch {
|
||||
resolve({ status: res.statusCode, data: null, raw: data });
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
req.on('error', reject);
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function toEtherscanResponse(result) {
|
||||
const { status, data, raw } = result;
|
||||
if (status >= 200 && status < 300 && data?.status === '1') {
|
||||
@@ -261,7 +352,7 @@ const server = http.createServer(async (req, res) => {
|
||||
codeformat === 'solidity-standard-json-input' ||
|
||||
(typeof sourceCode === 'string' && sourceCode.trimStart().startsWith('{') && sourceCode.includes('"sources"'));
|
||||
// Etherscan API expects Standard JSON in sourceCode; flattened Solidity causes "Invalid JSON".
|
||||
// Try v2 API first for flattened code; use Etherscan only for Standard JSON.
|
||||
// Try v2 API first for flattened code; use multipart standard-input when the payload is Standard JSON.
|
||||
const tryV2First = !isStandardJson;
|
||||
|
||||
try {
|
||||
@@ -278,13 +369,13 @@ const server = http.createServer(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
result = await forwardEtherscanFormat(payload);
|
||||
result = await forwardV2StandardInput(payload);
|
||||
out = toEtherscanResponse(result);
|
||||
if (out.status !== '1') {
|
||||
console.error('[forge-verification-proxy] Etherscan API failed:', out.message, '- trying v2...');
|
||||
result = await forwardV2Flattened(payload);
|
||||
const v2Out = toEtherscanResponse(result);
|
||||
send(res, 200, v2Out);
|
||||
console.error('[forge-verification-proxy] v2 standard-input failed:', out.message, '- trying Etherscan format...');
|
||||
result = await forwardEtherscanFormat(payload);
|
||||
const etherOut = toEtherscanResponse(result);
|
||||
send(res, 200, etherOut.status === '1' ? etherOut : out);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user