Files
defiQUG fbda1b4beb
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands
- CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround
- CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check
- NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere
- MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates
- LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 15:46:57 -08:00

141 lines
4.2 KiB
TypeScript

/**
* Chain adapter for Tezos mainnet (chainId 1729).
* Uses TzKT API for read operations. sendTransaction requires Taquito/injection for production.
*/
import type {
IChainAdapter,
ChainAdapterConfig,
NormalizedReceipt,
NormalizedLog,
SendTransactionResult,
} from './types.js';
import { getChainConfig, TEZOS_CHAIN_ID } from './config.js';
const TZKt_BASE = 'https://api.tzkt.io';
export class TezosChainAdapter implements IChainAdapter {
private config: ChainAdapterConfig;
private baseUrl: string;
constructor(rpcUrls?: string[]) {
const cfg = getChainConfig(TEZOS_CHAIN_ID);
if (!cfg) throw new Error('Tezos chain config not found');
this.config = rpcUrls?.length ? { ...cfg, rpcUrls } : cfg;
this.baseUrl = this.config.rpcUrls[0].replace(/\/$/, '');
}
getChainId(): number {
return this.config.chainId;
}
getConfig(): ChainAdapterConfig {
return this.config;
}
async getBlockNumber(): Promise<number> {
const res = await fetch(`${this.baseUrl}/v1/blocks/count`);
if (!res.ok) throw new Error(`TzKT blocks/count failed: ${res.status}`);
const count = await res.json();
return Number(count);
}
async getBlock(blockNumber: number): Promise<{ number: number; hash: string; parentHash: string; timestamp: number } | null> {
const res = await fetch(`${this.baseUrl}/v1/blocks/${blockNumber}`);
if (!res.ok) return null;
const b = await res.json();
return {
number: b.level,
hash: b.hash ?? '',
parentHash: b.previousHash ?? '',
timestamp: new Date(b.timestamp).getTime() / 1000,
};
}
async sendTransaction(signedTxHex: string): Promise<SendTransactionResult> {
const hex = signedTxHex.startsWith('0x') ? signedTxHex.slice(2) : signedTxHex;
const bytes = Buffer.from(hex, 'hex');
const rpcUrl = process.env.TEZOS_RPC_INJECT_URL ?? 'https://mainnet.api.tez.ie';
const res = await fetch(`${rpcUrl}/injection/operation`, {
method: 'POST',
headers: { 'Content-Type': 'application/octet-stream' },
body: bytes,
signal: AbortSignal.timeout(15000),
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Tezos injection failed: ${res.status} ${err}`);
}
const opHash = await res.text();
return { hash: opHash.trim(), from: '', nonce: 0 };
}
async getTransactionReceipt(txHash: string): Promise<NormalizedReceipt | null> {
const res = await fetch(`${this.baseUrl}/v1/operations/transactions/${txHash}`);
if (!res.ok) return null;
const op = await res.json();
if (Array.isArray(op)) {
const t = op[0];
if (!t) return null;
return {
chainId: this.config.chainId,
transactionHash: t.hash,
blockNumber: BigInt(t.level ?? 0),
blockHash: t.block ?? '',
transactionIndex: 0,
from: t.sender?.address ?? '',
to: t.target?.address ?? null,
gasUsed: BigInt(t.gasUsed ?? 0),
cumulativeGasUsed: BigInt(t.gasUsed ?? 0),
contractAddress: t.target?.address ?? null,
logsBloom: '',
status: t.status === 'applied' ? 1 : 0,
root: null,
};
}
return {
chainId: this.config.chainId,
transactionHash: op.hash,
blockNumber: BigInt(op.level ?? 0),
blockHash: op.block ?? '',
transactionIndex: 0,
from: op.sender?.address ?? '',
to: op.target?.address ?? null,
gasUsed: BigInt(op.gasUsed ?? 0),
cumulativeGasUsed: BigInt(op.gasUsed ?? 0),
contractAddress: op.target?.address ?? null,
logsBloom: '',
status: op.status === 'applied' ? 1 : 0,
root: null,
};
}
async getLogs(
_fromBlock: number,
_toBlock: number,
_address?: string,
_topics?: string[]
): Promise<NormalizedLog[]> {
return [];
}
async detectReorg(blockNumber: number, expectedBlockHash: string): Promise<boolean> {
const block = await this.getBlock(blockNumber);
if (!block) return true;
return block.hash !== expectedBlockHash;
}
async healthCheck(): Promise<boolean> {
try {
await fetch(`${this.baseUrl}/v1/blocks/head`);
return true;
} catch {
return false;
}
}
}
export function createAdapterTezos(rpcUrls?: string[]): TezosChainAdapter {
return new TezosChainAdapter(rpcUrls);
}