@atomiqlabs/chain-starknet
Version:
Starknet specific base implementation
152 lines (124 loc) • 6.33 kB
text/typescript
import {IClaimHandler} from "../ClaimHandlers";
import {StarknetSwapData} from "../../../StarknetSwapData";
import {StarknetGas} from "../../../../base/StarknetAction";
import {ChainSwapType, RelaySynchronizer} from "@atomiqlabs/base";
import {BigNumberish, hash} from "starknet";
import {StarknetBtcStoredHeader} from "../../../../btcrelay/headers/StarknetBtcStoredHeader";
import {StarknetTx} from "../../../../base/modules/StarknetTransactions";
import {StarknetBtcRelay} from "../../../../btcrelay/StarknetBtcRelay";
import {bufferToU32Array, getLogger, toHex, tryWithRetries, u32ArrayToBuffer} from "../../../../../utils/Utils";
export type BitcoinCommitmentData = {
btcRelay: StarknetBtcRelay<any>,
confirmations: number
}
export type BitcoinWitnessData = {
tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number },
requiredConfirmations: number,
commitedHeader?: StarknetBtcStoredHeader,
btcRelay?: StarknetBtcRelay<any>,
synchronizer?: RelaySynchronizer<StarknetBtcStoredHeader, StarknetTx, any>
};
const logger = getLogger("IBitcoinClaimHandler: ");
export abstract class IBitcoinClaimHandler<C, W extends BitcoinWitnessData> implements IClaimHandler<C & BitcoinCommitmentData, W> {
public readonly address: string;
constructor(address: string) {
this.address = address;
}
/**
* Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
* requiredConfirmations
* If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
* current chain tip & adds them to the txs array
*
* @param signer
* @param btcRelay
* @param txBlockheight transaction blockheight
* @param requiredConfirmations required confirmation for the swap to be claimable with that TX
* @param blockhash blockhash of the block which includes the transaction
* @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
* txns are added here
* @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
* to the required blockheight
* @param feeRate Fee rate to use for synchronization transactions
* @private
*/
protected async getCommitedHeaderAndSynchronize(
signer: string,
btcRelay: StarknetBtcRelay<any>,
txBlockheight: number,
requiredConfirmations: number,
blockhash: string,
txs: StarknetTx[],
synchronizer?: RelaySynchronizer<StarknetBtcStoredHeader, StarknetTx, any>,
feeRate?: string
): Promise<StarknetBtcStoredHeader> {
const requiredBlockheight = txBlockheight+requiredConfirmations-1;
const result = await tryWithRetries(
() => btcRelay.retrieveLogAndBlockheight({
blockhash: blockhash
}, requiredBlockheight)
);
if(result!=null) return result.header;
//Need to synchronize
if(synchronizer==null) return null;
//TODO: We don't have to synchronize to tip, only to our required blockheight
const resp = await synchronizer.syncToLatestTxs(signer.toString(), feeRate);
logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
"synchronizing ourselves in "+resp.txs.length+" txs");
logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
resp.txs.forEach(tx => txs.push(tx));
//Retrieve computed header
return resp.computedHeaderMap[txBlockheight];
}
public static readonly address = "";
public static readonly type: ChainSwapType = ChainSwapType.CHAIN_TXID;
public static readonly gas: StarknetGas = {l1: 20000};
protected serializeCommitment(data: BitcoinCommitmentData): BigNumberish[] {
return [
data.confirmations,
data.btcRelay.contract.address
]
}
getCommitment(data: C & BitcoinCommitmentData): BigNumberish {
return hash.computePoseidonHashOnElements(this.serializeCommitment(data));
}
protected async _getWitness(
signer: string,
swapData: StarknetSwapData,
{tx, btcRelay, commitedHeader, synchronizer, requiredConfirmations}: BitcoinWitnessData,
commitment: C,
feeRate?: string
): Promise<{
initialTxns: StarknetTx[];
witness: BigNumberish[]
}> {
const serializedData: BigNumberish[] = this.serializeCommitment({
...commitment,
btcRelay,
confirmations: requiredConfirmations
});
const commitmentHash = hash.computePoseidonHashOnElements(serializedData);
if(!swapData.isClaimData(commitmentHash)) throw new Error("Invalid commit data");
const merkleProof = await btcRelay.bitcoinRpc.getMerkleProof(tx.txid, tx.blockhash);
logger.debug("getWitness(): merkle proof computed: ", merkleProof);
const txs: StarknetTx[] = [];
if(commitedHeader==null) commitedHeader = await this.getCommitedHeaderAndSynchronize(
signer, btcRelay, tx.height, requiredConfirmations,
tx.blockhash, txs, synchronizer, feeRate
);
if(commitedHeader==null) throw new Error("Cannot fetch committed header!");
serializedData.push(...commitedHeader.serialize());
serializedData.push(merkleProof.merkle.length, ...merkleProof.merkle.map(bufferToU32Array).flat());
serializedData.push(merkleProof.pos);
return {initialTxns: txs, witness: serializedData};
}
abstract getWitness(signer: string, data: StarknetSwapData, witnessData: W, feeRate?: string): Promise<{
initialTxns: StarknetTx[];
witness: BigNumberish[]
}>;
abstract getGas(data: StarknetSwapData): StarknetGas;
abstract getType(): ChainSwapType;
parseWitnessResult(result: BigNumberish[]): string {
return u32ArrayToBuffer(result).toString("hex");
}
}