chore: sync submodule state (parent ref update)
Made-with: Cursor
This commit is contained in:
94
services/tezos-relay/src/TezosRelayService.js
Normal file
94
services/tezos-relay/src/TezosRelayService.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Tezos relay: listen TezosBridgeInitiated -> (mock) Tezos mint/transfer -> confirmTransaction.
|
||||
*/
|
||||
import { ethers } from 'ethers';
|
||||
import { config } from './config.js';
|
||||
import { TezosAdapterABI } from './abis.js';
|
||||
import * as metrics from './metrics.js';
|
||||
|
||||
export class TezosRelayService {
|
||||
constructor(logger) {
|
||||
this.logger = logger;
|
||||
this.provider = null;
|
||||
this.adapter = null;
|
||||
this.signer = null;
|
||||
this.queue = [];
|
||||
this.inFlight = 0;
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (!config.tezosAdapterAddress) {
|
||||
throw new Error('TEZOS_ADAPTER_ADDRESS required');
|
||||
}
|
||||
if (!config.oraclePrivateKey) {
|
||||
throw new Error('TEZOS_RELAY_ORACLE_KEY or PRIVATE_KEY required');
|
||||
}
|
||||
this.provider = new ethers.JsonRpcProvider(config.sourceChain.rpcUrl);
|
||||
this.signer = new ethers.Wallet(config.oraclePrivateKey, this.provider);
|
||||
this.adapter = new ethers.Contract(config.tezosAdapterAddress, TezosAdapterABI, this.signer);
|
||||
this.logger.info('Tezos relay started', { adapter: config.tezosAdapterAddress });
|
||||
this.adapter.on('TezosBridgeInitiated', (requestId, sender, token, amount, destination) => {
|
||||
this.queue.push({ requestId, sender, token, amount, destination });
|
||||
metrics.incrementEventsDetected();
|
||||
metrics.setPendingRequests(this.queue.length + this.inFlight);
|
||||
this.processQueue();
|
||||
});
|
||||
}
|
||||
|
||||
async processQueue() {
|
||||
while (this.queue.length > 0 && this.inFlight < config.maxConcurrent) {
|
||||
const item = this.queue.shift();
|
||||
this.inFlight++;
|
||||
metrics.setPendingRequests(this.queue.length + this.inFlight);
|
||||
this.handleRequest(item).finally(() => {
|
||||
this.inFlight--;
|
||||
metrics.setPendingRequests(this.queue.length + this.inFlight);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async handleRequest({ requestId, sender, token, amount, destination }) {
|
||||
try {
|
||||
let tezosTxHash;
|
||||
if (config.mockTezosRelay) {
|
||||
tezosTxHash = `mock_${requestId.slice(0, 10)}_${Date.now()}`;
|
||||
this.logger.info('Mock Tezos tx', { requestId, destination, tezosTxHash });
|
||||
} else {
|
||||
tezosTxHash = await this.performTezosMintOrTransfer(requestId, sender, token, amount, destination);
|
||||
}
|
||||
await this.adapter.confirmTransaction(requestId, tezosTxHash);
|
||||
metrics.incrementConfirmationsSubmitted();
|
||||
} catch (err) {
|
||||
this.logger.error('confirmTransaction failed', { requestId, error: err.message });
|
||||
metrics.incrementConfirmationsFailed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform actual Tezos mint/transfer via Taquito or Tezos RPC.
|
||||
* Real mint: set MOCK_TEZOS_RELAY=false, TEZOS_MINTER_ADDRESS, TEZOS_RPC_URL, TEZOS_ORACLE_SECRET_KEY (edsk...).
|
||||
* When @taquito/taquito is installed and config is set, calls minter contract mintOrTransfer(destination, amount); otherwise returns mock hash.
|
||||
*/
|
||||
async performTezosMintOrTransfer(requestId, sender, token, amount, destination) {
|
||||
const { config } = await import('./config.js');
|
||||
if (!config.tezosRpcUrl) {
|
||||
return `mock_${requestId.slice(0, 10)}_${Date.now()}`;
|
||||
}
|
||||
try {
|
||||
const { Tezos } = await import('@taquito/taquito');
|
||||
const { InMemorySigner } = await import('@taquito/signer');
|
||||
const tezosSecret = process.env.TEZOS_ORACLE_SECRET_KEY || config.oraclePrivateKey;
|
||||
if (config.tezosMinterAddress && tezosSecret) {
|
||||
Tezos.setProvider({ rpc: config.tezosRpcUrl });
|
||||
Tezos.setSignerProvider(await InMemorySigner.fromSecretKey(tezosSecret));
|
||||
const contract = await Tezos.wallet.at(config.tezosMinterAddress);
|
||||
const op = await contract.methods.mintOrTransfer(destination, amount.toString()).send();
|
||||
await op.confirmation(1);
|
||||
return op.hash;
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.warn('Taquito mint/transfer failed, using mock hash', { error: e.message });
|
||||
}
|
||||
return `mock_${requestId.slice(0, 10)}_${Date.now()}`;
|
||||
}
|
||||
}
|
||||
5
services/tezos-relay/src/abis.js
Normal file
5
services/tezos-relay/src/abis.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const TezosAdapterABI = [
|
||||
'event TezosBridgeInitiated(bytes32 indexed requestId, address indexed sender, address indexed token, uint256 amount, string destination)',
|
||||
'function confirmTransaction(bytes32 requestId, string calldata txHash) external',
|
||||
'function getBridgeStatus(bytes32 requestId) view returns (tuple(bytes32 requestId, address sender, address token, uint256 amount, bytes destinationData, uint8 status, uint256 createdAt, uint256 completedAt))',
|
||||
];
|
||||
22
services/tezos-relay/src/config.js
Normal file
22
services/tezos-relay/src/config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
// Load PRIVATE_KEY from dotenv: smom-dbis-138/.env then service .env
|
||||
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
||||
const cwd = process.cwd();
|
||||
dotenv.config({ path: path.resolve(cwd, 'smom-dbis-138/.env') });
|
||||
dotenv.config({ path: path.resolve(cwd, '.env') });
|
||||
|
||||
export const config = {
|
||||
sourceChain: { chainId: 138, rpcUrl: process.env.RPC_URL_138 || process.env.RPC_URL || 'http://127.0.0.1:8545' },
|
||||
tezosAdapterAddress: process.env.TEZOS_ADAPTER_ADDRESS || '',
|
||||
tezosRpcUrl: process.env.TEZOS_RPC_URL || 'https://mainnet.smartpy.io',
|
||||
tezosMinterAddress: process.env.TEZOS_MINTER_ADDRESS || '',
|
||||
oraclePrivateKey: process.env.TEZOS_RELAY_ORACLE_KEY || process.env.PRIVATE_KEY,
|
||||
pollIntervalMs: parseInt(process.env.POLL_INTERVAL_MS || '5000', 10),
|
||||
maxConcurrent: parseInt(process.env.TEZOS_RELAY_MAX_CONCURRENT || '5', 10),
|
||||
mockTezosRelay: process.env.MOCK_TEZOS_RELAY === 'true',
|
||||
};
|
||||
11
services/tezos-relay/src/metrics.js
Normal file
11
services/tezos-relay/src/metrics.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const metrics = {
|
||||
tezos_relay_events_detected_total: 0,
|
||||
tezos_relay_confirmations_submitted_total: 0,
|
||||
tezos_relay_confirmations_failed_total: 0,
|
||||
tezos_relay_pending_requests: 0,
|
||||
};
|
||||
export function incrementEventsDetected() { metrics.tezos_relay_events_detected_total++; }
|
||||
export function incrementConfirmationsSubmitted() { metrics.tezos_relay_confirmations_submitted_total++; }
|
||||
export function incrementConfirmationsFailed() { metrics.tezos_relay_confirmations_failed_total++; }
|
||||
export function setPendingRequests(n) { metrics.tezos_relay_pending_requests = n; }
|
||||
export function getMetrics() { return { ...metrics }; }
|
||||
Reference in New Issue
Block a user