UNPKG

@atomiqlabs/chain-starknet

Version:

Starknet specific base implementation

198 lines (170 loc) 7.63 kB
import {SignatureVerificationError, SwapDataVerificationError} from "@atomiqlabs/base"; import {toHex, tryWithRetries} from "../../../utils/Utils"; import {StarknetSwapModule} from "../StarknetSwapModule"; import {StarknetSwapData} from "../StarknetSwapData"; import {StarknetAction, StarknetGas, sumStarknetGas} from "../../chain/StarknetAction"; import {StarknetSwapContract} from "../StarknetSwapContract"; import {IHandler} from "../handlers/IHandler"; import {BigNumberish} from "starknet"; import {StarknetTx} from "../../chain/modules/StarknetTransactions"; import {StarknetSigner} from "../../wallet/StarknetSigner"; import {StarknetFees} from "../../chain/modules/StarknetFees"; const Refund = [ { name: 'Swap hash', type: 'felt' }, { name: 'Timeout', type: 'timestamp' } ]; export class StarknetSwapRefund extends StarknetSwapModule { private static readonly GasCosts = { REFUND: {l1: 750, l2: 0}, REFUND_PAY_OUT: {l1: 1250, l2: 0} }; /** * Action for generic Refund instruction * * @param signer * @param swapData * @param witness * @param handlerGas * @constructor * @private */ private Refund( signer: string, swapData: StarknetSwapData, witness: BigNumberish[], handlerGas?: StarknetGas ): StarknetAction { return new StarknetAction(signer, this.root, this.swapContract.populateTransaction.refund(swapData.toEscrowStruct(), witness), sumStarknetGas(swapData.payIn ? StarknetSwapRefund.GasCosts.REFUND_PAY_OUT : StarknetSwapRefund.GasCosts.REFUND, handlerGas) ); } /** * Action for cooperative refunding with signature * * @param sender * @param swapData * @param timeout * @param signature * @constructor * @private */ private RefundWithSignature( sender: string, swapData: StarknetSwapData, timeout: string, signature: BigNumberish[] ): StarknetAction { return new StarknetAction(sender, this.root, this.swapContract.populateTransaction.cooperative_refund(swapData.toEscrowStruct(), signature, BigInt(timeout)), swapData.payIn ? StarknetSwapRefund.GasCosts.REFUND_PAY_OUT : StarknetSwapRefund.GasCosts.REFUND ); } public async signSwapRefund( signer: StarknetSigner, swapData: StarknetSwapData, authorizationTimeout: number ): Promise<{ prefix: string; timeout: string; signature: string }> { const authPrefix = "refund"; const authTimeout = Math.floor(Date.now()/1000)+authorizationTimeout; const signature = await this.root.Signatures.signTypedMessage(signer, Refund, "Refund", { "Swap hash": "0x"+swapData.getEscrowHash(), "Timeout": toHex(authTimeout) }); return { prefix: authPrefix, timeout: authTimeout.toString(10), signature: signature }; } public async isSignatureValid( swapData: StarknetSwapData, timeout: string, prefix: string, signature: string ): Promise<null> { if(prefix!=="refund") throw new SignatureVerificationError("Invalid prefix"); const expiryTimestamp = BigInt(timeout); const currentTimestamp = BigInt(Math.floor(Date.now() / 1000)); const isExpired = (expiryTimestamp - currentTimestamp) < BigInt(this.contract.authGracePeriod); if(isExpired) throw new SignatureVerificationError("Authorization expired!"); const valid = await this.root.Signatures.isValidSignature(signature, swapData.claimer, Refund, "Refund", { "Swap hash": "0x"+swapData.getEscrowHash(), "Timeout": toHex(expiryTimestamp) }); if(!valid) { throw new SignatureVerificationError("Invalid signature!"); } return null; } /** * Creates transactions required for refunding timed out swap * * @param signer * @param swapData swap data to refund * @param check whether to check if swap is already expired and refundable * @param feeRate fee rate to be used for the transactions * @param witnessData */ public async txsRefund<T>( signer: string, swapData: StarknetSwapData, check?: boolean, feeRate?: string, witnessData?: T ): Promise<StarknetTx[]> { const refundHandler: IHandler<any, T> = this.contract.refundHandlersByAddress[swapData.refundHandler.toLowerCase()]; if(refundHandler==null) throw new Error("Invalid refund handler"); if(check && !await tryWithRetries(() => this.contract.isRequestRefundable(swapData.offerer.toString(), swapData), this.retryPolicy)) { throw new SwapDataVerificationError("Not refundable yet!"); } feeRate ??= await this.root.Fees.getFeeRate(); const {initialTxns, witness} = await refundHandler.getWitness(signer, swapData, witnessData, feeRate); const action = this.Refund(signer, swapData, witness, refundHandler.getGas(swapData)); await action.addToTxs(initialTxns, feeRate); this.logger.debug("txsRefund(): creating refund transaction, swap: "+swapData.getClaimHash()); return initialTxns; } /** * Creates transactions required for refunding the swap with authorization signature, also unwraps WSOL to SOL * * @param signer * @param swapData swap data to refund * @param timeout signature timeout * @param prefix signature prefix of the counterparty * @param signature signature of the counterparty * @param check whether to check if swap is committed before attempting refund * @param feeRate fee rate to be used for the transactions */ public async txsRefundWithAuthorization( signer: string, swapData: StarknetSwapData, timeout: string, prefix: string, signature: string, check?: boolean, feeRate?: string ): Promise<StarknetTx[]> { if(check && !await tryWithRetries(() => this.contract.isCommited(swapData), this.retryPolicy)) { throw new SwapDataVerificationError("Not correctly committed"); } await tryWithRetries( () => this.isSignatureValid(swapData, timeout, prefix, signature), this.retryPolicy, (e) => e instanceof SignatureVerificationError ); const action = this.RefundWithSignature(signer, swapData, timeout, JSON.parse(signature)); feeRate ??= await this.root.Fees.getFeeRate(); this.logger.debug("txsRefundWithAuthorization(): creating refund transaction, swap: "+swapData.getClaimHash()+ " auth expiry: "+timeout+" signature: "+signature); return [await action.tx(feeRate)]; } /** * Get the estimated solana transaction fee of the refund transaction, in the worst case scenario in case where the * ATA needs to be initialized again (i.e. adding the ATA rent exempt lamports to the fee) */ async getRefundFee(swapData: StarknetSwapData, feeRate?: string): Promise<bigint> { feeRate ??= await this.root.Fees.getFeeRate(); return StarknetFees.getGasFee(swapData.payIn ? StarknetSwapRefund.GasCosts.REFUND_PAY_OUT.l1 : StarknetSwapRefund.GasCosts.REFUND.l1, feeRate); } }