Files
smom-dbis-138/services/tezos-relay/src/TezosRelayService.js
2026-03-02 12:14:09 -08:00

95 lines
3.8 KiB
JavaScript

/**
* 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()}`;
}
}