Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m3s
CI/CD Pipeline / Security Scanning (push) Successful in 2m18s
CI/CD Pipeline / Lint and Format (push) Failing after 34s
CI/CD Pipeline / Terraform Validation (push) Failing after 20s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 22s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 40s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 49s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 21s
Validation / validate-genesis (push) Successful in 25s
Validation / validate-terraform (push) Failing after 21s
Validation / validate-kubernetes (push) Failing after 8s
Validation / validate-smart-contracts (push) Failing after 8s
Validation / validate-security (push) Failing after 1m11s
Validation / validate-documentation (push) Failing after 14s
Verify Deployment / Verify Deployment (push) Failing after 45s
Ship AddressActivityRegistry V1/V2, ISO20022IntakeGateway, Chain138ParticipantSurface, checkpoint hub contracts, checkpoint-core package, aggregator/indexer/sdk services, relay profile guards, M00 diamond bridge facet, and OMNL compliance contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
343 lines
15 KiB
JavaScript
343 lines
15 KiB
JavaScript
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const express_1 = __importDefault(require("express"));
|
|
const fs = __importStar(require("fs"));
|
|
const path = __importStar(require("path"));
|
|
const ethers_1 = require("ethers");
|
|
const config_1 = require("./config");
|
|
const tokenEnrich_1 = require("./tokenEnrich");
|
|
const checkpoint_core_1 = require("@dbis/checkpoint-core");
|
|
const merkle_1 = require("./merkle");
|
|
const serialize_1 = require("./serialize");
|
|
const addressIndex_1 = require("./addressIndex");
|
|
const etherscanV2Shim_1 = require("./etherscanV2Shim");
|
|
const chain138Explorer_1 = require("./chain138Explorer");
|
|
const CHECKPOINT_ABI = [
|
|
'function getLatestBatchId() view returns (uint64)',
|
|
'function latestCheckpointBlock() view returns (uint256)',
|
|
'function getLatestCheckpoint() view returns (tuple(uint64,uint64,uint64,uint256,uint256,uint256,bytes32,bytes32,bytes32,bytes32,uint16,uint32,uint64,address,bytes32))',
|
|
'function getCheckpoint(uint64) view returns (tuple(uint64,uint64,uint64,uint256,uint256,uint256,bytes32,bytes32,bytes32,bytes32,uint16,uint32,uint64,address,bytes32))',
|
|
'function isTxIncluded(bytes32) view returns (bool,uint64)',
|
|
];
|
|
const MIRROR_ABI = ['function getMirroredTransactionCount() view returns (uint256)'];
|
|
const app = (0, express_1.default)();
|
|
const mainnet = new ethers_1.ethers.JsonRpcProvider(config_1.checkpointIndexerConfig.mainnetRpc);
|
|
const chain138 = new ethers_1.ethers.JsonRpcProvider(config_1.checkpointIndexerConfig.chain138Rpc);
|
|
const hub = config_1.checkpointIndexerConfig.checkpointProxy ? new ethers_1.ethers.Contract(config_1.checkpointIndexerConfig.checkpointProxy, CHECKPOINT_ABI, mainnet) : null;
|
|
const mirror = new ethers_1.ethers.Contract(config_1.checkpointIndexerConfig.legacyMirror, MIRROR_ABI, mainnet);
|
|
let addressIndexCache = null;
|
|
let addressIndexBuiltAt = 0;
|
|
const ADDRESS_INDEX_TTL_MS = 60_000;
|
|
function getAddressIndex() {
|
|
const now = Date.now();
|
|
if (!addressIndexCache || now - addressIndexBuiltAt > ADDRESS_INDEX_TTL_MS) {
|
|
addressIndexCache = (0, addressIndex_1.buildAddressIndex)(config_1.checkpointIndexerConfig.batchPayloadDir);
|
|
addressIndexBuiltAt = now;
|
|
}
|
|
return addressIndexCache;
|
|
}
|
|
function loadBatchPayload(batchId) {
|
|
const p = path.join(config_1.checkpointIndexerConfig.batchPayloadDir, `batch-${batchId}.json`);
|
|
if (!fs.existsSync(p))
|
|
return null;
|
|
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
}
|
|
async function payloadWithEnrichedLeaves(batchId, raw) {
|
|
if (!raw?.leaves?.length)
|
|
return raw;
|
|
const chainId = raw.chainId ?? 138;
|
|
let leaves = raw.leaves;
|
|
if (config_1.checkpointIndexerConfig.enrichTokenTransfers || config_1.checkpointIndexerConfig.usdEnrichEnabled) {
|
|
leaves = await (0, tokenEnrich_1.enrichLeaves)(leaves, config_1.checkpointIndexerConfig.blockscoutApi);
|
|
}
|
|
const hashes = leaves.map((l) => (0, merkle_1.paymentLeafHash)(chainId, {
|
|
txHash: String(l.txHash),
|
|
from: String(l.from),
|
|
to: String(l.to),
|
|
value: String(l.value ?? '0'),
|
|
nativeValueWei: l.nativeValueWei != null ? String(l.nativeValueWei) : undefined,
|
|
onChainValueWei: l.onChainValueWei != null ? String(l.onChainValueWei) : undefined,
|
|
tokenValue: l.tokenValue != null ? String(l.tokenValue) : undefined,
|
|
blockNumber: Number(l.blockNumber),
|
|
blockTimestamp: Number(l.blockTimestamp),
|
|
gasUsed: String(l.gasUsed ?? '0'),
|
|
success: Boolean(l.success),
|
|
}));
|
|
const { root, proofs } = (0, merkle_1.merkleProofs)(hashes);
|
|
const leavesWithProofs = leaves.map((l, i) => ({
|
|
...l,
|
|
leafHash: hashes[i],
|
|
merkleProof: proofs[i],
|
|
}));
|
|
return {
|
|
...raw,
|
|
batchId: raw.batchId ?? batchId,
|
|
leaves: leavesWithProofs,
|
|
merkleRootComputed: root,
|
|
merkleRootMatches: raw.paymentsRoot ? root.toLowerCase() === String(raw.paymentsRoot).toLowerCase() : null,
|
|
walletAttestationCopy: 'Balances and payments as of Chain 138 checkpoint block in this batch, attested on Ethereum mainnet.',
|
|
};
|
|
}
|
|
app.get('/health', (_req, res) => {
|
|
res.json({ ok: true, proxy: config_1.checkpointIndexerConfig.checkpointProxy || null, batchDir: config_1.checkpointIndexerConfig.batchPayloadDir });
|
|
});
|
|
/** Etherscan V2 supplement for Chain 138 (unsupported on official chainlist). */
|
|
app.get('/v2/chainlist-supplement', etherscanV2Shim_1.chainlistSupplement);
|
|
app.get('/v2/api', (req, res) => {
|
|
void (0, etherscanV2Shim_1.etherscanV2Api)(req, res);
|
|
});
|
|
/** Public batch JSON for contentURI resolution (CHECKPOINT_PAYLOAD_PUBLIC_URL). */
|
|
app.get('/v1/payload/batch-:batchId.json', (req, res) => {
|
|
const raw = loadBatchPayload(req.params.batchId);
|
|
if (!raw)
|
|
return res.status(404).json({ error: 'batch not found' });
|
|
res.type('json').send(JSON.stringify(raw, null, 2));
|
|
});
|
|
app.get('/v1/checkpoint/latest', async (_req, res) => {
|
|
if (!hub)
|
|
return res.status(503).json({ error: 'CHAIN138_MAINNET_CHECKPOINT_PROXY not set' });
|
|
try {
|
|
const batchId = await hub.getLatestBatchId();
|
|
const header = await hub.getLatestCheckpoint();
|
|
let v1Count = 0n;
|
|
try {
|
|
v1Count = await mirror.getMirroredTransactionCount();
|
|
}
|
|
catch {
|
|
v1Count = 0n;
|
|
}
|
|
const raw = batchId > 0n ? loadBatchPayload(batchId.toString()) : null;
|
|
const payload = raw ? await payloadWithEnrichedLeaves(batchId.toString(), raw) : null;
|
|
res.json({
|
|
source: 'v2',
|
|
batchId: batchId.toString(),
|
|
header: (0, serialize_1.jsonSafe)(header),
|
|
legacy: { mirrorCount: v1Count.toString(), tether: config_1.checkpointIndexerConfig.legacyTether },
|
|
payload,
|
|
});
|
|
}
|
|
catch (e) {
|
|
res.status(500).json({ error: String(e) });
|
|
}
|
|
});
|
|
app.get('/v1/checkpoint/:batchId', async (req, res) => {
|
|
if (!hub)
|
|
return res.status(503).json({ error: 'proxy not set' });
|
|
try {
|
|
const batchId = req.params.batchId;
|
|
const header = await hub.getCheckpoint(BigInt(batchId));
|
|
const raw = loadBatchPayload(batchId);
|
|
const payload = raw ? await payloadWithEnrichedLeaves(batchId, raw) : null;
|
|
res.json({ batchId, header: (0, serialize_1.jsonSafe)(header), payload });
|
|
}
|
|
catch (e) {
|
|
res.status(500).json({ error: String(e) });
|
|
}
|
|
});
|
|
app.get('/v1/tx/:txHash/attestation', async (req, res) => {
|
|
if (!hub)
|
|
return res.status(503).json({ error: 'proxy not set' });
|
|
try {
|
|
const txHash = req.params.txHash;
|
|
const [included, batchId] = await hub.isTxIncluded(txHash);
|
|
const raw = included && batchId > 0n ? loadBatchPayload(batchId.toString()) : null;
|
|
const payload = raw ? await payloadWithEnrichedLeaves(batchId.toString(), raw) : null;
|
|
res.json({ txHash, included, batchId: batchId.toString(), payload });
|
|
}
|
|
catch (e) {
|
|
res.status(500).json({ error: String(e) });
|
|
}
|
|
});
|
|
/** Wallet helper: verify leaf against on-chain hub (client may also use @dbis/checkpoint-sdk). */
|
|
app.get('/v1/checkpoint/:batchId/verify/:leafIndex', async (req, res) => {
|
|
if (!hub)
|
|
return res.status(503).json({ error: 'proxy not set' });
|
|
try {
|
|
const batchId = req.params.batchId;
|
|
const leafIndex = parseInt(req.params.leafIndex, 10);
|
|
const raw = loadBatchPayload(batchId);
|
|
if (!raw?.leaves?.length || leafIndex < 0 || leafIndex >= raw.leaves.length) {
|
|
return res.status(404).json({ error: 'leaf not found' });
|
|
}
|
|
const payload = await payloadWithEnrichedLeaves(batchId, raw);
|
|
const leaves = payload.leaves;
|
|
const leaf = leaves?.[leafIndex];
|
|
if (!leaf || !leaf.merkleProof) {
|
|
return res.status(404).json({ error: 'proof missing' });
|
|
}
|
|
const VERIFY_ABI = [
|
|
'function verifyPaymentInBatch(uint64 batchId, tuple(bytes32,address,address,uint256,uint256,uint64,uint256,bool) leaf, bytes32[] proof) view returns (bool)',
|
|
];
|
|
const verifyHub = new ethers_1.ethers.Contract(config_1.checkpointIndexerConfig.checkpointProxy, VERIFY_ABI, mainnet);
|
|
const l = leaf;
|
|
const ok = await verifyHub.verifyPaymentInBatch(BigInt(batchId), [
|
|
l.txHash,
|
|
l.from,
|
|
l.to,
|
|
(0, checkpoint_core_1.merkleVerifyValueWei)(l),
|
|
BigInt(String(l.blockNumber)),
|
|
BigInt(String(l.blockTimestamp)),
|
|
BigInt(String(l.gasUsed ?? 0)),
|
|
Boolean(l.success),
|
|
], l.merkleProof);
|
|
res.json({ batchId, leafIndex, verified: ok, leaf });
|
|
}
|
|
catch (e) {
|
|
res.status(500).json({ error: String(e) });
|
|
}
|
|
});
|
|
app.get('/v1/account/:address', async (req, res) => {
|
|
try {
|
|
const addr = req.params.address;
|
|
let blockTag = 'latest';
|
|
if (req.query.atBatch) {
|
|
if (!hub)
|
|
return res.status(503).json({ error: 'proxy not set' });
|
|
const h = await hub.getCheckpoint(BigInt(String(req.query.atBatch)));
|
|
blockTag = Number(h.checkpointBlock);
|
|
}
|
|
else if (hub) {
|
|
blockTag = Number(await hub.latestCheckpointBlock());
|
|
}
|
|
const balance = await chain138.getBalance(addr, blockTag);
|
|
const index = getAddressIndex();
|
|
const rows = index.byAddress[addr.toLowerCase()] ?? [];
|
|
const activity = (0, addressIndex_1.summarizeAddressActivity)(rows);
|
|
res.json({
|
|
address: addr,
|
|
chainId: 138,
|
|
blockTag,
|
|
balanceWei: balance.toString(),
|
|
attestedActivity: activity,
|
|
mainnetSurfaces: {
|
|
transactionMirror: config_1.checkpointIndexerConfig.legacyMirror,
|
|
addressActivityRegistry: config_1.checkpointIndexerConfig.addressActivityRegistry || null,
|
|
checkpointHub: config_1.checkpointIndexerConfig.checkpointProxy || null,
|
|
},
|
|
walletAttestationCopy: 'Native balance as of Chain 138 checkpoint block. Attested payments (ETH + USD) via checkpoint batches on Ethereum mainnet.',
|
|
});
|
|
}
|
|
catch (e) {
|
|
res.status(500).json({ error: String(e) });
|
|
}
|
|
});
|
|
app.get('/v1/account/:address/transactions', async (req, res) => {
|
|
try {
|
|
const addr = req.params.address;
|
|
const limit = Math.min(parseInt(String(req.query.limit ?? '50'), 10) || 50, 200);
|
|
const offset = parseInt(String(req.query.offset ?? '0'), 10) || 0;
|
|
const index = getAddressIndex();
|
|
const { total, items } = (0, addressIndex_1.getAddressTransactions)(index, addr, limit, offset);
|
|
res.json({
|
|
address: addr,
|
|
chainId: 138,
|
|
total,
|
|
limit,
|
|
offset,
|
|
items,
|
|
indexMeta: {
|
|
builtAt: index.builtAt,
|
|
batchCount: index.batchCount,
|
|
txCount: index.txCount,
|
|
},
|
|
});
|
|
}
|
|
catch (e) {
|
|
res.status(500).json({ error: String(e) });
|
|
}
|
|
});
|
|
app.get('/v1/account/:address/activity', async (req, res) => {
|
|
try {
|
|
const addr = req.params.address;
|
|
const index = getAddressIndex();
|
|
const rows = index.byAddress[addr.toLowerCase()] ?? [];
|
|
const summary = (0, addressIndex_1.summarizeAddressActivity)(rows);
|
|
res.json({
|
|
address: addr,
|
|
chainId: 138,
|
|
summary,
|
|
recent: rows.slice(0, 25).map((r) => ({
|
|
...r,
|
|
chain138ExplorerUrl: (0, chain138Explorer_1.chain138ExplorerTxUrl)(r.txHash),
|
|
})),
|
|
chain138Explorer: {
|
|
base: (0, chain138Explorer_1.chain138ExplorerBase)(),
|
|
txUrlTemplate: `${(0, chain138Explorer_1.chain138ExplorerBase)()}/tx/{txHash}`,
|
|
},
|
|
etherscan: {
|
|
transactionMirrorEvents: `https://etherscan.io/address/${config_1.checkpointIndexerConfig.legacyMirror}#events`,
|
|
addressActivityEvents: config_1.checkpointIndexerConfig.addressActivityRegistry
|
|
? `https://etherscan.io/address/${config_1.checkpointIndexerConfig.addressActivityRegistry}#events`
|
|
: null,
|
|
},
|
|
});
|
|
}
|
|
catch (e) {
|
|
res.status(500).json({ error: String(e) });
|
|
}
|
|
});
|
|
app.get('/v1/tx/:txHash/logs', async (req, res) => {
|
|
try {
|
|
const txHash = req.params.txHash;
|
|
const receipt = await chain138.getTransactionReceipt(txHash);
|
|
if (!receipt)
|
|
return res.status(404).json({ error: 'receipt not found on chain 138' });
|
|
res.json({
|
|
txHash,
|
|
chainId: 138,
|
|
blockNumber: receipt.blockNumber,
|
|
status: receipt.status,
|
|
gasUsed: receipt.gasUsed.toString(),
|
|
logCount: receipt.logs.length,
|
|
logs: receipt.logs.map((log, i) => ({
|
|
index: i,
|
|
address: log.address,
|
|
topics: log.topics,
|
|
data: log.data,
|
|
})),
|
|
});
|
|
}
|
|
catch (e) {
|
|
res.status(500).json({ error: String(e) });
|
|
}
|
|
});
|
|
app.listen(config_1.checkpointIndexerConfig.port, () => {
|
|
console.log(`checkpoint-indexer listening on :${config_1.checkpointIndexerConfig.port}`);
|
|
});
|