UNPKG

@atomiqlabs/chain-starknet

Version:
1,041 lines (947 loc) 41.5 kB
import { BigIntBufferUtils, ChainSwapType, IntermediaryReputationType, RelaySynchronizer, SignatureData, SwapCommitState, SwapCommitStateType, SwapContract, TransactionConfirmationOptions } from "@atomiqlabs/base"; import {Buffer} from "buffer"; import {EscrowManagerAbi, EscrowManagerAbiType} from "./EscrowManagerAbi"; import {StarknetContractBase} from "../contract/StarknetContractBase"; import {StarknetTraceCall, StarknetTx} from "../chain/modules/StarknetTransactions"; import {StarknetSigner} from "../wallet/StarknetSigner"; import {BigNumberish, constants, hash, logger} from "starknet"; import {StarknetChainInterface} from "../chain/StarknetChainInterface"; import {StarknetBtcRelay} from "../btcrelay/StarknetBtcRelay"; import {StarknetSwapData} from "./StarknetSwapData"; import {bigNumberishToBuffer, bytes31SpanToBuffer, toBigInt, toHex} from "../../utils/Utils"; import {TimelockRefundHandler} from "./handlers/refund/TimelockRefundHandler"; import {StarknetLpVault} from "./modules/StarknetLpVault"; import {StarknetPreFetchVerification, StarknetSwapInit} from "./modules/StarknetSwapInit"; import {StarknetSwapRefund} from "./modules/StarknetSwapRefund"; import {claimHandlersList, IClaimHandler} from "./handlers/claim/ClaimHandlers"; import {StarknetSwapClaim} from "./modules/StarknetSwapClaim"; import {IHandler} from "./handlers/IHandler"; import {StarknetBtcStoredHeader} from "../btcrelay/headers/StarknetBtcStoredHeader"; import {sha256} from "@noble/hashes/sha2"; import {StarknetAbiEvent} from "../contract/modules/StarknetContractEvents"; import {ExtractAbiFunctionNames} from "abi-wan-kanabi/dist/kanabi"; const ESCROW_STATE_COMMITTED = 1; const ESCROW_STATE_CLAIMED = 2; const ESCROW_STATE_REFUNDED = 3; const swapContractAddreses = { [constants.StarknetChainId.SN_SEPOLIA]: "0x017bf50dd28b6d823a231355bb25813d4396c8e19d2df03026038714a22f0413", [constants.StarknetChainId.SN_MAIN]: "0x04f278e1f19e495c3b1dd35ef307c4f7510768ed95481958fbae588bd173f79a" }; const swapContractDeploymentHeights = { [constants.StarknetChainId.SN_SEPOLIA]: 1118142, [constants.StarknetChainId.SN_MAIN]: 1617247 }; const defaultClaimAddresses = { [constants.StarknetChainId.SN_SEPOLIA]: { [ChainSwapType.HTLC]: "0x04a57ea54d4637c352aad1bbee046868926a11702216a0aaf7eeec1568be2d7b", [ChainSwapType.CHAIN_TXID]: "0x04c7cde88359e14b6f6f779f8b9d8310cee37e91a6f143f855ae29fab33c396e", [ChainSwapType.CHAIN]: "0x051bef6f5fd12e2832a7d38653bdfc8eb84ba7eb7a4aada5b87ef38a9999cf17", [ChainSwapType.CHAIN_NONCED]: "0x050e50eacd16da414f2c3a7c3570fd5e248974c6fe757d41acbf72d2836fa0a1" }, [constants.StarknetChainId.SN_MAIN]: { [ChainSwapType.HTLC]: "0x07b74b50a883ebee262b6db0e3c0c697670c6f30e3d610e75faf33a89c46aa2a", [ChainSwapType.CHAIN_TXID]: "0x016c2db2b03f39cf4fd7f871035000f66b62307d9983056e33a38315da8a44dc", [ChainSwapType.CHAIN]: "0x02c45a81c4a48d0645a0a199e620061e8a55dcc9c2b5946d050eaeeddba64e9a", [ChainSwapType.CHAIN_NONCED]: "0x0019b5480dd7ed8ded10a09437b0a7a30b8997b4ef139deb24ff8c86f995d84f" } } const defaultRefundAddresses = { [constants.StarknetChainId.SN_SEPOLIA]: { timelock: "0x034b8f28b3ca979036cb2849cfa3af7f67207459224b6ca5ce2474aa398ec3e7" }, [constants.StarknetChainId.SN_MAIN]: { timelock: "0x06a59659990c2aefbf7239f6d911617b3ae60b79cb3364f3bd242a6ca8f4f4f7" } } /** * Starknet swap contract (escrow manager) contract representation handling PrTLC (on-chain) and HTLC (lightning) * based swaps * * @category Swaps */ export class StarknetSwapContract extends StarknetContractBase<typeof EscrowManagerAbi> implements SwapContract< StarknetSwapData, StarknetTx, never, StarknetPreFetchVerification, StarknetSigner, "STARKNET" > { /** * @inheritDoc */ readonly supportsInitWithoutClaimer = true; //////////////////////// //// Constants readonly chainId: "STARKNET" = "STARKNET"; //////////////////////// //// Timeouts /** * @inheritDoc */ readonly claimWithSecretTimeout: number = 180; /** * @inheritDoc */ readonly claimWithTxDataTimeout: number = 180; /** * @inheritDoc */ readonly refundTimeout: number = 180; private readonly claimGracePeriod: number = 10*60; private readonly refundGracePeriod: number = 10*60; /** * @private */ readonly authGracePeriod: number = 30; //////////////////////// //// Services readonly Init: StarknetSwapInit; readonly Refund: StarknetSwapRefund; readonly Claim: StarknetSwapClaim; readonly LpVault: StarknetLpVault; //////////////////////// //// Handlers readonly claimHandlersByAddress: {[address: string]: IClaimHandler<any, any>} = {}; readonly claimHandlersBySwapType: {[type in ChainSwapType]?: IClaimHandler<any, any>} = {}; readonly refundHandlersByAddress: {[address: string]: IHandler<any, any>} = {}; readonly timelockRefundHandler: IHandler<any, any>; readonly btcRelay: StarknetBtcRelay<any>; private readonly initFunctionName: ExtractAbiFunctionNames<EscrowManagerAbiType> = "initialize"; private readonly initEntryPointSelector = BigInt(hash.starknetKeccak(this.initFunctionName)); /** * Constructs the swap contract (escrow manager) * * @param chainInterface Underlying chain interface to use * @param btcRelay Btc relay light client contract * @param contractAddress Optional underlying contract address (default is used otherwise) * @param _handlerAddresses Optional handler addresses (defaults are used otherwise) * @param contractDeploymentHeight The height at which this contract was deployed (default is used otherwise) */ constructor( chainInterface: StarknetChainInterface, btcRelay: StarknetBtcRelay<any>, contractAddress: string = swapContractAddreses[chainInterface.starknetChainId], _handlerAddresses?: { refund?: { timelock?: string }, claim?: { [type in ChainSwapType]?: string } }, contractDeploymentHeight?: number ) { super( chainInterface, contractAddress, EscrowManagerAbi, contractDeploymentHeight ?? (swapContractAddreses[chainInterface.starknetChainId]===contractAddress ? swapContractDeploymentHeights[chainInterface.starknetChainId] : undefined) ); this.Init = new StarknetSwapInit(chainInterface, this); this.Refund = new StarknetSwapRefund(chainInterface, this); this.Claim = new StarknetSwapClaim(chainInterface, this); this.LpVault = new StarknetLpVault(chainInterface, this); this.btcRelay = btcRelay; const handlerAddresses = { refund: {...defaultRefundAddresses[chainInterface.starknetChainId], ..._handlerAddresses?.refund}, claim: {...defaultClaimAddresses[chainInterface.starknetChainId], ..._handlerAddresses?.claim} }; claimHandlersList.forEach(handlerCtor => { const handler = new handlerCtor(handlerAddresses.claim[handlerCtor.type]); this.claimHandlersByAddress[toHex(handler.address)] = handler; this.claimHandlersBySwapType[handlerCtor.type] = handler; }); this.timelockRefundHandler = new TimelockRefundHandler(handlerAddresses.refund.timelock); this.refundHandlersByAddress[this.timelockRefundHandler.address] = this.timelockRefundHandler; } /** * @inheritDoc */ async start(): Promise<void> { } //////////////////////////////////////////// //// Signatures /** * @inheritDoc */ preFetchForInitSignatureVerification(): Promise<StarknetPreFetchVerification> { return this.Init.preFetchForInitSignatureVerification(); } /** * @inheritDoc */ getInitSignature(signer: StarknetSigner, swapData: StarknetSwapData, authorizationTimeout: number, preFetchedBlockData?: never, feeRate?: string): Promise<SignatureData> { return this.Init.signSwapInitialization(signer, swapData, authorizationTimeout); } /** * @inheritDoc */ isValidInitAuthorization(sender: string, swapData: StarknetSwapData, sig: SignatureData, feeRate?: string, preFetchedData?: StarknetPreFetchVerification): Promise<null> { return this.Init.isSignatureValid(sender, swapData, sig.timeout, sig.prefix, sig.signature, preFetchedData); } /** * @inheritDoc */ getInitAuthorizationExpiry(swapData: StarknetSwapData, sig: SignatureData, preFetchedData?: StarknetPreFetchVerification): Promise<number> { return this.Init.getSignatureExpiry(sig.timeout); } /** * @inheritDoc */ isInitAuthorizationExpired(swapData: StarknetSwapData, sig: SignatureData): Promise<boolean> { return this.Init.isSignatureExpired(sig.timeout); } /** * @inheritDoc */ getRefundSignature(signer: StarknetSigner, swapData: StarknetSwapData, authorizationTimeout: number): Promise<SignatureData> { return this.Refund.signSwapRefund(signer, swapData, authorizationTimeout); } /** * @inheritDoc */ isValidRefundAuthorization(swapData: StarknetSwapData, sig: SignatureData): Promise<null> { return this.Refund.isSignatureValid(swapData, sig.timeout, sig.prefix, sig.signature); } /** * @inheritDoc */ getDataSignature(signer: StarknetSigner, data: Buffer): Promise<string> { return this.Chain.Signatures.getDataSignature(signer, data); } /** * @inheritDoc */ isValidDataSignature(data: Buffer, signature: string, publicKey: string): Promise<boolean> { return this.Chain.Signatures.isValidDataSignature(data, signature, publicKey); } //////////////////////////////////////////// //// Swap data utils /** * @inheritDoc */ async isClaimable(signer: string, data: StarknetSwapData): Promise<boolean> { if(!data.isClaimer(signer)) return false; if(await this.isExpired(signer, data)) return false; return await this.isCommited(data); } /** * @inheritDoc */ async isCommited(swapData: StarknetSwapData): Promise<boolean> { const data = await this.contract.get_hash_state("0x"+swapData.getEscrowHash()); return Number(data.state)===ESCROW_STATE_COMMITTED; } /** * @inheritDoc */ isExpired(signer: string, data: StarknetSwapData): Promise<boolean> { let currentTimestamp: bigint = BigInt(Math.floor(Date.now()/1000)); if(data.isClaimer(signer)) currentTimestamp = currentTimestamp + BigInt(this.claimGracePeriod); if(data.isOfferer(signer)) currentTimestamp = currentTimestamp - BigInt(this.refundGracePeriod); return Promise.resolve(data.getExpiry() < currentTimestamp); } /** * @inheritDoc */ async isRequestRefundable(signer: string, data: StarknetSwapData): Promise<boolean> { //Swap can only be refunded by the offerer if(!data.isOfferer(signer)) return false; if(!(await this.isExpired(signer, data))) return false; return await this.isCommited(data); } /** * @inheritDoc */ getHashForTxId(txId: string, confirmations: number) { const chainTxIdHandler = this.claimHandlersBySwapType[ChainSwapType.CHAIN_TXID]; if(chainTxIdHandler==null) throw new Error("Claim handler for CHAIN_TXID not found!"); return bigNumberishToBuffer(chainTxIdHandler.getCommitment({ txId, confirmations, btcRelay: this.btcRelay }), 32); } /** * @inheritDoc */ getHashForOnchain(outputScript: Buffer, amount: bigint, confirmations: number, nonce?: bigint): Buffer { let result: BigNumberish; if(nonce==null || nonce === 0n) { const chainHandler = this.claimHandlersBySwapType[ChainSwapType.CHAIN]; if(chainHandler==null) throw new Error("Claim handler for CHAIN not found!"); result = chainHandler.getCommitment({ output: outputScript, amount, confirmations, btcRelay: this.btcRelay }); } else { const chainNoncedHandler = this.claimHandlersBySwapType[ChainSwapType.CHAIN_NONCED]; if(chainNoncedHandler==null) throw new Error("Claim handler for CHAIN_NONCED not found!"); result = chainNoncedHandler.getCommitment({ output: outputScript, amount, nonce, confirmations, btcRelay: this.btcRelay }); } return bigNumberishToBuffer(result, 32); } /** * @inheritDoc */ getHashForHtlc(paymentHash: Buffer): Buffer { const htlcHandler = this.claimHandlersBySwapType[ChainSwapType.HTLC]; if(htlcHandler==null) throw new Error("Claim handler for HTLC not found!"); return bigNumberishToBuffer(htlcHandler.getCommitment(paymentHash), 32); } /** * @inheritDoc */ getExtraData(outputScript: Buffer, amount: bigint, confirmations: number, nonce?: bigint): Buffer { if(nonce==null) nonce = 0n; const txoHash = Buffer.from(sha256(Buffer.concat([ BigIntBufferUtils.toBuffer(amount, "le", 8), outputScript ]))); return Buffer.concat([ txoHash, BigIntBufferUtils.toBuffer(nonce, "be", 8), BigIntBufferUtils.toBuffer(BigInt(confirmations), "be", 2) ]); } //////////////////////////////////////////// //// Swap data getters /** * @inheritDoc */ async getCommitStatus(signer: string, data: StarknetSwapData): Promise<SwapCommitState> { const escrowHash = data.getEscrowHash(); const stateData = await this.contract.get_hash_state("0x"+escrowHash); const state = Number(stateData.state); const initBlockHeight = Number(stateData.init_blockheight); const blockHeight = Number(stateData.finish_blockheight); const getInitTxId = async () => { const events = await this._Events.getContractBlockEvents( ["escrow_manager::events::Initialize"], [null, null, null, "0x"+escrowHash], initBlockHeight, initBlockHeight ); if(events.length===0) throw new Error("Initialize event not found!"); return events[0].txHash; } switch(state) { case ESCROW_STATE_COMMITTED: if(data.isOfferer(signer) && await this.isExpired(signer,data)) { return { type: SwapCommitStateType.REFUNDABLE, getInitTxId }; } return { type: SwapCommitStateType.COMMITED, getInitTxId }; case ESCROW_STATE_CLAIMED: return { type: SwapCommitStateType.PAID, getInitTxId, getTxBlock: async () => { return { blockTime: await this.Chain.Blocks.getBlockTime(blockHeight), blockHeight: blockHeight }; }, getClaimTxId: async () => { const events = await this._Events.getContractBlockEvents( ["escrow_manager::events::Claim"], [null, null, null, "0x"+escrowHash], blockHeight, blockHeight ); if(events.length===0) throw new Error("Claim event not found!"); return events[0].txHash; }, getClaimResult: async () => { const events = await this._Events.getContractBlockEvents( ["escrow_manager::events::Claim"], [null, null, null, "0x"+escrowHash], blockHeight, blockHeight ); if(events.length===0) throw new Error("Claim event not found!"); const event = events[0]; const claimHandlerHex = toHex(event.params.claim_handler); const claimHandler = this.claimHandlersByAddress[claimHandlerHex]; if(claimHandler==null) { throw new Error("getClaimResult("+escrowHash+"): Unknown claim handler with claim: "+claimHandlerHex); } return claimHandler.parseWitnessResult(event.params.witness_result); } }; case ESCROW_STATE_REFUNDED: return { type: await this.isExpired(signer, data) ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED, getInitTxId, getTxBlock: async () => { return { blockTime: await this.Chain.Blocks.getBlockTime(blockHeight), blockHeight: blockHeight }; }, getRefundTxId: async () => { const events = await this._Events.getContractBlockEvents( ["escrow_manager::events::Refund"], [null, null, null, "0x"+escrowHash], blockHeight, blockHeight ); if(events.length===0) throw new Error("Refund event not found!"); return events[0].txHash; } }; default: return { type: await this.isExpired(signer, data) ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED }; } } /** * @inheritDoc */ async getCommitStatuses(request: { signer: string; swapData: StarknetSwapData }[]): Promise<{ [p: string]: SwapCommitState }> { const result: { [p: string]: SwapCommitState } = {}; let promises: Promise<void>[] = []; //TODO: We can upgrade this to use multicall for(let {signer, swapData} of request) { promises.push(this.getCommitStatus(signer, swapData).then(val => { result[swapData.getEscrowHash()] = val; })); if(promises.length>=this.Chain.config.maxParallelCalls!) { await Promise.all(promises); promises = []; } } await Promise.all(promises); return result; } /** * @inheritDoc */ async getHistoricalSwaps(signer: string, startBlockheight?: number): Promise<{ swaps: { [escrowHash: string]: { init?: { data: StarknetSwapData; getInitTxId: () => Promise<string>; getTxBlock: () => Promise<{ blockTime: number; blockHeight: number }> }; state: SwapCommitState } }; latestBlockheight?: number }> { const {height: latestBlockheight} = await this.Chain.getFinalizedBlock(); const swapsOpened: { [escrowHash: string]: { data: Promise<StarknetSwapData | null>, getInitTxId: () => Promise<string>, getTxBlock: () => Promise<{ blockTime: number, blockHeight: number }> } } = {}; const resultingSwaps: { [escrowHash: string]: { init?: { data: StarknetSwapData; getInitTxId: () => Promise<string>; getTxBlock: () => Promise<{ blockTime: number; blockHeight: number }> }; state: SwapCommitState } } = {}; const promises: Promise<void>[] = []; const processor = async (_event: StarknetAbiEvent< EscrowManagerAbiType, "escrow_manager::events::Initialize" | "escrow_manager::events::Claim" | "escrow_manager::events::Refund" >) => { const escrowHash = toHex(_event.params.escrow_hash).substring(2); if(_event.name==="escrow_manager::events::Initialize") { const event = _event as StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Initialize">; const claimHandlerHex = toHex(event.params.claim_handler); const claimHandler = this.claimHandlersByAddress[claimHandlerHex]; if(claimHandler==null) { logger.warn(`getHistoricalSwaps(Initialize): Unknown claim handler in tx ${event.txHash} with claim handler: `+claimHandlerHex); return null; } swapsOpened[escrowHash] = { data: (async () => { const txTrace = await this.Chain.Transactions.traceTransaction(event.txHash, event.blockHash); if(txTrace==null) { logger.warn(`getHistoricalSwaps(Initialize): Cannot get transaction trace for tx ${event.txHash}`); return null; } const data = this.findInitSwapData(txTrace, event.params.escrow_hash, claimHandler); if(data==null) { logger.warn(`getHistoricalSwaps(Initialize): Cannot parse swap data from tx ${event.txHash} with escrow hash: `+escrowHash); return null; } return data; })(), getInitTxId: () => Promise.resolve(event.txHash), getTxBlock: async () => { return { blockHeight: event.blockNumber!, blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber!) } } } } if(_event.name==="escrow_manager::events::Claim") { const event = _event as StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Claim">; const claimHandlerHex = toHex(event.params.claim_handler); const claimHandler = this.claimHandlersByAddress[claimHandlerHex]; if(claimHandler==null) { logger.warn(`getHistoricalSwaps(Claim): Unknown claim handler in tx ${event.txHash} with claim handler: `+claimHandlerHex); return null; } const foundSwapData = swapsOpened[escrowHash]; delete swapsOpened[escrowHash]; promises.push((async() => { const data = await foundSwapData?.data; resultingSwaps[escrowHash] = { init: data==null ? undefined : { data, getInitTxId: foundSwapData.getInitTxId, getTxBlock: foundSwapData.getTxBlock }, state: { type: SwapCommitStateType.PAID, getInitTxId: foundSwapData?.getInitTxId, getClaimTxId: () => Promise.resolve(event.txHash), getClaimResult: () => Promise.resolve(claimHandler.parseWitnessResult(event.params.witness_result)), getTxBlock: async () => { return { blockHeight: event.blockNumber!, blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber!) } } } } })()); } if(_event.name==="escrow_manager::events::Refund") { const event = _event as StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Refund">; const foundSwapData = swapsOpened[escrowHash]; delete swapsOpened[escrowHash]; promises.push((async() => { const data = await foundSwapData?.data; const isExpired = data!=null && await this.isExpired(signer, data); resultingSwaps[escrowHash] = { init: data==null ? undefined : { data, getInitTxId: foundSwapData.getInitTxId, getTxBlock: foundSwapData.getTxBlock }, state: { type: isExpired ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED, getInitTxId: foundSwapData?.getInitTxId, getRefundTxId: () => Promise.resolve(event.txHash), getTxBlock: async () => { return { blockHeight: event.blockNumber!, blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber!) } } } } })()); } }; //We have to fetch separately the different directions await this._Events.findInContractEventsForward( ["escrow_manager::events::Initialize", "escrow_manager::events::Claim", "escrow_manager::events::Refund"], [signer, null], processor, startBlockheight ); await this._Events.findInContractEventsForward( ["escrow_manager::events::Initialize", "escrow_manager::events::Claim", "escrow_manager::events::Refund"], [null, signer], processor, startBlockheight ) for(let escrowHash in swapsOpened) { const foundSwapData = swapsOpened[escrowHash]; const data = await foundSwapData.data; if(data==null) continue; resultingSwaps[escrowHash] = { init: { data, getInitTxId: foundSwapData.getInitTxId, getTxBlock: foundSwapData.getTxBlock }, state: data.isOfferer(signer) && await this.isExpired(signer, data) ? {type: SwapCommitStateType.REFUNDABLE, getInitTxId: foundSwapData.getInitTxId} : {type: SwapCommitStateType.COMMITED, getInitTxId: foundSwapData.getInitTxId} } } await Promise.all(promises); logger.debug(`getHistoricalSwaps(): Found ${Object.keys(resultingSwaps).length} settled swaps!`); logger.debug(`getHistoricalSwaps(): Found ${Object.keys(swapsOpened).length} unsettled swaps!`); return { swaps: resultingSwaps, latestBlockheight: latestBlockheight ?? startBlockheight }; } //////////////////////////////////////////// //// Swap data initializer /** * @inheritDoc */ createSwapData( type: ChainSwapType, offerer: string, claimer: string, token: string, amount: bigint, claimData: string, sequence: bigint, expiry: bigint, payIn: boolean, payOut: boolean, securityDeposit: bigint, claimerBounty: bigint, depositToken: string = this.Chain.Tokens.getNativeCurrencyAddress() ): Promise<StarknetSwapData> { const claimHandler = this.claimHandlersBySwapType[type]; if(claimHandler==null) throw new Error("Invalid claim handler for type: "+ChainSwapType[type]); return Promise.resolve(new StarknetSwapData({ offerer, claimer, token, refundHandler: this.timelockRefundHandler.address, claimHandler: claimHandler.address, payOut, payIn, reputation: payIn, //For now track reputation for all payIn swaps sequence, claimData: "0x"+claimData, refundData: toHex(expiry), amount, feeToken: depositToken, securityDeposit, claimerBounty, kind: type })); } /** * * @param call * @param escrowHash * @param claimHandler * @private */ findInitSwapData(call: StarknetTraceCall, escrowHash: BigNumberish, claimHandler: IClaimHandler<any, any>): StarknetSwapData | null { if( BigInt(call.contract_address)===BigInt(this.contract.address) && BigInt(call.entry_point_selector)===this.initEntryPointSelector ) { //Found, check correct escrow hash const escrow = StarknetSwapData.fromSerializedFeltArray(call.calldata, claimHandler); if(call.calldata.length < 1) throw new Error("Calldata invalid length"); const signatureLen = Number(toBigInt(call.calldata.shift()!)); if(call.calldata.length < signatureLen + 2) throw new Error("Calldata invalid length"); const _signature = call.calldata.splice(0, signatureLen); const _timeout = toBigInt(call.calldata.shift()!); const extraDataLen = Number(toBigInt(call.calldata.shift()!)); if(call.calldata.length < extraDataLen) throw new Error("Calldata invalid length"); const extraData = call.calldata.splice(0, extraDataLen); if(call.calldata.length!==0) throw new Error("Calldata not read fully!"); if("0x"+escrow.getEscrowHash()===toHex(escrowHash)) { if(extraData.length!==0) { escrow.setExtraData(bytes31SpanToBuffer(extraData, 42).toString("hex")); } return escrow; } } for(let _call of call.calls) { const found = this.findInitSwapData(_call, escrowHash, claimHandler); if(found!=null) return found; } return null; } //////////////////////////////////////////// //// Utils /** * * @param address * @param token * @private */ private getIntermediaryBalance(address: string, token: string): Promise<bigint> { return this.LpVault.getIntermediaryBalance(address, token); } /** * @inheritDoc */ async getBalance(signer: string, tokenAddress: string, inContract?: boolean): Promise<bigint> { if(inContract) return await this.getIntermediaryBalance(signer, tokenAddress); //TODO: For native token we should discount the cost of deploying an account if it is not deployed yet return await this.Chain.getBalance(signer, tokenAddress); } /** * @inheritDoc */ getIntermediaryReputation(address: string, token: string): Promise<IntermediaryReputationType> { return this.LpVault.getIntermediaryReputation(address, token); } //////////////////////////////////////////// //// Transaction initializers /** * @inheritDoc */ async txsClaimWithSecret( signer: string | StarknetSigner, swapData: StarknetSwapData, secret: string, checkExpiry?: boolean, initAta?: boolean, feeRate?: string, skipAtaCheck?: boolean ): Promise<StarknetTx[]> { return this.Claim.txsClaimWithSecret(typeof(signer)==="string" ? signer : signer.getAddress(), swapData, secret, checkExpiry, feeRate) } /** * @inheritDoc */ async txsClaimWithTxData( signer: string | StarknetSigner, swapData: StarknetSwapData, tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number }, requiredConfirmations: number, vout: number, commitedHeader?: StarknetBtcStoredHeader, synchronizer?: RelaySynchronizer<StarknetBtcStoredHeader, StarknetTx, any>, initAta?: boolean, feeRate?: string ): Promise<StarknetTx[]> { return this.Claim.txsClaimWithTxData( typeof(signer)==="string" ? signer : signer.getAddress(), swapData, tx, requiredConfirmations, vout, commitedHeader, synchronizer, feeRate ); } /** * @inheritDoc */ txsRefund(signer: string, swapData: StarknetSwapData, check?: boolean, initAta?: boolean, feeRate?: string): Promise<StarknetTx[]> { return this.Refund.txsRefund(signer, swapData, check, feeRate); } /** * @inheritDoc */ txsRefundWithAuthorization(signer: string, swapData: StarknetSwapData, sig: SignatureData, check?: boolean, initAta?: boolean, feeRate?: string): Promise<StarknetTx[]> { return this.Refund.txsRefundWithAuthorization(signer, swapData, sig.timeout, sig.prefix, sig.signature, check, feeRate); } /** * @inheritDoc */ txsInit(sender: string, swapData: StarknetSwapData, sig: SignatureData, skipChecks?: boolean, feeRate?: string): Promise<StarknetTx[]> { return this.Init.txsInit(sender, swapData, sig.timeout, sig.prefix, sig.signature, skipChecks, feeRate); } /** * @inheritDoc */ txsWithdraw(signer: string, token: string, amount: bigint, feeRate?: string): Promise<StarknetTx[]> { return this.LpVault.txsWithdraw(signer, token, amount, feeRate); } /** * @inheritDoc */ txsDeposit(signer: string, token: string, amount: bigint, feeRate?: string): Promise<StarknetTx[]> { return this.LpVault.txsDeposit(signer, token, amount, feeRate); } //////////////////////////////////////////// //// Executors /** * @inheritDoc */ async claimWithSecret( signer: StarknetSigner, swapData: StarknetSwapData, secret: string, checkExpiry?: boolean, initAta?: boolean, txOptions?: TransactionConfirmationOptions ): Promise<string> { const result = await this.Claim.txsClaimWithSecret(signer.getAddress(), swapData, secret, checkExpiry, txOptions?.feeRate); const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal); return signature; } /** * @inheritDoc */ async claimWithTxData( signer: StarknetSigner, swapData: StarknetSwapData, tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number }, requiredConfirmations: number, vout: number, commitedHeader?: StarknetBtcStoredHeader, synchronizer?: RelaySynchronizer<StarknetBtcStoredHeader, StarknetTx, any>, initAta?: boolean, txOptions?: TransactionConfirmationOptions ): Promise<string> { const txs = await this.Claim.txsClaimWithTxData( signer.getAddress(), swapData, tx, requiredConfirmations, vout, commitedHeader, synchronizer, txOptions?.feeRate ); if(txs===null) throw new Error("Btc relay not synchronized to required blockheight!"); //TODO: This doesn't return proper tx signature const [signature] = await this.Chain.sendAndConfirm(signer, txs, txOptions?.waitForConfirmation, txOptions?.abortSignal); return signature; } /** * @inheritDoc */ async refund( signer: StarknetSigner, swapData: StarknetSwapData, check?: boolean, initAta?: boolean, txOptions?: TransactionConfirmationOptions ): Promise<string> { let result = await this.txsRefund(signer.getAddress(), swapData, check, initAta, txOptions?.feeRate); const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal); return signature; } /** * @inheritDoc */ async refundWithAuthorization( signer: StarknetSigner, swapData: StarknetSwapData, signature: SignatureData, check?: boolean, initAta?: boolean, txOptions?: TransactionConfirmationOptions ): Promise<string> { let result = await this.txsRefundWithAuthorization(signer.getAddress(), swapData, signature, check, initAta, txOptions?.feeRate); const [txSignature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal); return txSignature; } /** * @inheritDoc */ async init( signer: StarknetSigner, swapData: StarknetSwapData, signature: SignatureData, skipChecks?: boolean, txOptions?: TransactionConfirmationOptions ): Promise<string> { if(swapData.isPayIn()) { if(!swapData.isOfferer(signer.getAddress())) throw new Error("Invalid signer provided!"); } else { if(!swapData.isClaimer(signer.getAddress())) throw new Error("Invalid signer provided!"); } let result = await this.txsInit(signer.getAddress(), swapData, signature, skipChecks, txOptions?.feeRate); const [txSignature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal); return txSignature; } /** * @inheritDoc */ async withdraw( signer: StarknetSigner, token: string, amount: bigint, txOptions?: TransactionConfirmationOptions ): Promise<string> { const txs = await this.LpVault.txsWithdraw(signer.getAddress(), token, amount, txOptions?.feeRate); const [txId] = await this.Chain.sendAndConfirm(signer, txs, txOptions?.waitForConfirmation, txOptions?.abortSignal, false); return txId; } /** * @inheritDoc */ async deposit( signer: StarknetSigner, token: string, amount: bigint, txOptions?: TransactionConfirmationOptions ): Promise<string> { const txs = await this.LpVault.txsDeposit(signer.getAddress(), token, amount, txOptions?.feeRate); const [txId] = await this.Chain.sendAndConfirm(signer, txs, txOptions?.waitForConfirmation, txOptions?.abortSignal, false); return txId; } //////////////////////////////////////////// //// Fees /** * @inheritDoc */ getInitPayInFeeRate(offerer?: string, claimer?: string, token?: string, paymentHash?: string): Promise<string> { return this.Chain.Fees.getFeeRate(); } /** * @inheritDoc */ getInitFeeRate(offerer?: string, claimer?: string, token?: string, paymentHash?: string): Promise<string> { return this.Chain.Fees.getFeeRate(); } /** * @inheritDoc */ getRefundFeeRate(swapData: StarknetSwapData): Promise<string> { return this.Chain.Fees.getFeeRate(); } /** * @inheritDoc */ getClaimFeeRate(signer: string, swapData: StarknetSwapData): Promise<string> { return this.Chain.Fees.getFeeRate(); } /** * @inheritDoc */ getClaimFee(signer: string, swapData: StarknetSwapData, feeRate?: string): Promise<bigint> { return this.Claim.getClaimFee(swapData, feeRate); } /** * @inheritDoc */ getCommitFee(signer: string, swapData: StarknetSwapData, feeRate?: string): Promise<bigint> { return this.Init.getInitFee(swapData, feeRate); } /** * @inheritDoc */ getRefundFee(signer: string, swapData: StarknetSwapData, feeRate?: string): Promise<bigint> { return this.Refund.getRefundFee(swapData, feeRate); } }