This commit is contained in:
ZOMBIIIIIII
2026-05-14 14:41:03 +03:00
parent 53635806d6
commit e88ee3a55f
8 changed files with 415 additions and 48 deletions

View File

@@ -24,9 +24,13 @@ const BSC_RPC = 'https://bsc-dataseed.binance.org';
const RPC: Record<'ETH' | 'BSC', string> = { ETH: ETH_RPC, BSC: BSC_RPC };
// Realistic mainnet floors (gwei).
// Realistic mainnet floors (gwei). 0 = without floor (use raw eth_feeHistory value).
// ETH: убран floor — eth_feeHistory сам по себе репрезентативный, искусственный floor
// перерасходовал gas в spam/low-traffic блоках.
// BSC: оставлен низкий floor — chain не полностью EIP-1559, без минимума получается
// 0.001 gwei который reject'ится min-relay.
const FLOOR_GWEI: Record<'ETH' | 'BSC', string> = {
ETH: '0.5',
ETH: '0',
BSC: '0.05',
};

View File

@@ -746,7 +746,9 @@ export async function swapTrx(
// ─── SOL Jupiter ─────────────────────────────────────────────────────
const SOL_RPC = 'https://api.mainnet-beta.solana.com';
const JUPITER_API = 'https://quote-api.jup.ag/v6';
// Jupiter migrated в 2025: старый `quote-api.jup.ag/v6` deprecated и DNS удалён.
// `lite-api.jup.ag/swap/v1` — public anonymous endpoint (~600 req/min), JSON-schema идентична.
const JUPITER_API = 'https://lite-api.jup.ag/swap/v1';
let _solConnection: Connection | null = null;
function getSolConnection(): Connection {

View File

@@ -14,7 +14,10 @@ import * as bip39 from 'bip39';
import { BIP32Factory } from 'bip32';
import * as ecc from 'tiny-secp256k1';
import * as bitcoin from 'bitcoinjs-lib';
import { Keypair, Connection, PublicKey, SystemProgram, Transaction, ComputeBudgetProgram, VersionedTransaction } from '@solana/web3.js';
import {
Keypair, Connection, PublicKey, SystemProgram, Transaction, ComputeBudgetProgram,
VersionedTransaction, TransactionMessage, AddressLookupTableAccount, TransactionInstruction,
} from '@solana/web3.js';
import {
getAssociatedTokenAddressSync,
createAssociatedTokenAccountIdempotentInstruction,
@@ -504,6 +507,117 @@ export async function signAndBroadcastSolanaTx(p: SignSolanaTxParams): Promise<{
return { signature: sig };
}
/** Relay-style SOL step body: instructions[] + addressLookupTableAddresses[].
*
* Когда Relay execute returns SOL bridge tx, format не serialized — а instructions array
* с program IDs, keys, и calldata. Серверу нужно:
* 1. Validate каждый isSigner=true key === user's SOL pubkey (мы можем подписать только за себя)
* 2. Resolve address lookup tables через SOL RPC (Relay не передаёт сами таблицы, только адреса)
* 3. Fetch latest blockhash
* 4. Build VersionedTransaction с feePayer=user
* 5. Sign + broadcast
*/
export interface SignSolanaInstructionsParams {
mnemonic: string;
expectedFromAddress: string;
instructions: Array<{
programId: string;
keys: Array<{ pubkey: string; isSigner: boolean; isWritable: boolean }>;
data: string; // hex (no 0x prefix) или base64 — autodetect
}>;
addressLookupTableAddresses?: string[];
}
export async function signAndBroadcastSolanaInstructions(
p: SignSolanaInstructionsParams,
): Promise<{ signature: string }> {
const seed = await bip39.mnemonicToSeed(p.mnemonic);
const { key } = derivePath(DERIVATION_PATHS.SOL, seed.toString('hex'));
if (!key || key.length !== 32) {
throw new Error('SOL derivation produced invalid seed length');
}
const keypair = Keypair.fromSeed(key);
assertAddressMatch(keypair.publicKey.toBase58(), p.expectedFromAddress, 'SOL');
const userPubkey = keypair.publicKey;
if (!Array.isArray(p.instructions) || p.instructions.length === 0) {
throw new Error('SOL instructions: empty array');
}
if (p.instructions.length > 32) {
throw new Error('SOL instructions: too many (max 32)');
}
// ─── Build TransactionInstruction[] из Relay-style objects ───
const ixs: TransactionInstruction[] = [];
for (const raw of p.instructions) {
if (!raw || typeof raw !== 'object') throw new Error('SOL instruction: not an object');
let programId: PublicKey;
try { programId = new PublicKey(raw.programId); } catch { throw new Error(`SOL invalid programId: ${raw.programId}`); }
if (!Array.isArray(raw.keys)) throw new Error('SOL instruction.keys must be array');
const keys = raw.keys.map((k, idx) => {
let pubkey: PublicKey;
try { pubkey = new PublicKey(k.pubkey); } catch { throw new Error(`SOL invalid pubkey at keys[${idx}]: ${k.pubkey}`); }
// SECURITY: any signer-key must be userPubkey (мы можем подписать только за себя)
if (k.isSigner && !pubkey.equals(userPubkey)) {
throw new Error(`SOL instruction has signer key ${k.pubkey} ≠ user ${userPubkey.toBase58()}`);
}
return { pubkey, isSigner: Boolean(k.isSigner), isWritable: Boolean(k.isWritable) };
});
// data: hex или base64? Relay обычно отдаёт hex без префикса.
let data: Buffer;
const dStr = String(raw.data || '');
if (/^[0-9a-fA-F]*$/.test(dStr) && dStr.length % 2 === 0) {
data = Buffer.from(dStr, 'hex');
} else {
try { data = Buffer.from(dStr, 'base64'); } catch { throw new Error('SOL instruction.data: not hex or base64'); }
}
ixs.push(new TransactionInstruction({ programId, keys, data }));
}
const conn = getSolConnection();
// ─── Resolve address lookup tables через RPC ───
const luts: AddressLookupTableAccount[] = [];
for (const lutAddr of (p.addressLookupTableAddresses ?? [])) {
let lutPk: PublicKey;
try { lutPk = new PublicKey(lutAddr); } catch { throw new Error(`SOL invalid LUT address: ${lutAddr}`); }
const acc = await conn.getAddressLookupTable(lutPk);
if (!acc.value) throw new Error(`SOL LUT not found on-chain: ${lutAddr}`);
luts.push(acc.value);
}
// ─── Compile VersionedTransaction ───
const latestBlock = await conn.getLatestBlockhash();
const msg = new TransactionMessage({
payerKey: userPubkey,
recentBlockhash: latestBlock.blockhash,
instructions: ixs,
}).compileToV0Message(luts);
const tx = new VersionedTransaction(msg);
tx.sign([keypair]);
// ─── Broadcast + confirm ───
const sig = await conn.sendRawTransaction(tx.serialize());
try {
await conn.confirmTransaction({
signature: sig,
blockhash: latestBlock.blockhash,
lastValidBlockHeight: latestBlock.lastValidBlockHeight,
}, 'confirmed');
} catch (err: any) {
const name = err?.name || '';
if (name === 'TransactionExpiredBlockheightExceededError') {
throw new Error(`SOL Relay-bridge tx EXPIRED. sig=${sig}`);
}
throw new Error(`SOL Relay-bridge confirm error (${name}): ${err.message}. sig=${sig}`);
}
return { signature: sig };
}
// ─── BITCOIN ───
async function sendBtc(p: SendParams): Promise<{ txid: string }> {