UNPKG

hyperspace-sdk

Version:

An unofficial SDK for Hyperspace NFT Marketplace on Avalanche

189 lines (171 loc) 5.36 kB
import { hexDataLength, hexDataSlice } from "@ethersproject/bytes"; import { ECSignature, NftOrderV4 } from "./types"; import { ERC721ORDER_STRUCT_ABI, ERC721ORDER_STRUCT_NAME, FEE_ABI, ONE_TWENTY_EIGHT_BIT_LENGTH, PROPERTY_ABI, RESERVED_APP_ID_PREFIX, RESERVED_APP_ID_PREFIX_DIGITS, TWO_FIFTY_SIX_BIT_LENGTH, } from "./trader_constants"; import invariant from "tiny-invariant"; import padStart from "lodash/padStart"; import padEnd from "lodash/padEnd"; import { v4 } from "uuid"; import { ethers, Wallet } from "ethers"; export const parseRawSignature = (rawSignature: string): ECSignature => { const hexSize = hexDataLength(rawSignature); // if (hexUtils.size(rpcSig) !== 65) { // throw new Error(`Invalid RPC signature length: "${rpcSig}"`); // } if (hexSize !== 65) { throw new Error( `Invalid signature length, expected 65, got ${hexSize}.\n"Raw signature: ${rawSignature}"` ); } // Some providers encode V as 0,1 instead of 27,28. const VALID_V_VALUES = [0, 1, 27, 28]; // Some providers return the signature packed as V,R,S and others R,S,V. // Try to guess which encoding it is (with a slight preference for R,S,V). // let v = parseInt(rpcSig.slice(-2), 16); let v = parseInt(rawSignature.slice(-2), 16); if (VALID_V_VALUES.includes(v)) { // Format is R,S,V v = v >= 27 ? v : v + 27; return { // r: hexDataSlice.slice(rpcSig, 0, 32), // s: hexUtils.slice(rpcSig, 32, 64), r: hexDataSlice(rawSignature, 0, 32), s: hexDataSlice(rawSignature, 32, 64), v, }; } // Format should be V,R,S // v = parseInt(rpcSig.slice(2, 4), 16); v = parseInt(rawSignature.slice(2, 4), 16); if (!VALID_V_VALUES.includes(v)) { throw new Error( `Cannot determine RPC signature layout from V value: "${rawSignature}"` ); } v = v >= 27 ? v : v + 27; return { v, r: hexDataSlice(rawSignature, 1, 33), s: hexDataSlice(rawSignature, 33, 65), }; }; const checkIfStringContainsOnlyNumbers = (val: string) => { const onlyNumbers = /^\d+$/.test(val); return onlyNumbers; }; export const verifyAppIdOrThrow = (appId: string) => { const isCorrectLength = appId.length <= ONE_TWENTY_EIGHT_BIT_LENGTH - RESERVED_APP_ID_PREFIX_DIGITS; const hasOnlyNumbers = checkIfStringContainsOnlyNumbers(appId); invariant(isCorrectLength, "appId must be 39 digits or less"); invariant( hasOnlyNumbers, "appId must be numeric only (no alpha or special characters, only numbers)" ); }; // uuids are 128bits export const generateRandom128BitNumber = (base = 10): string => { const hex = "0x" + v4().replace(/-/g, ""); const value = BigInt(hex); const valueBase10String = value.toString(base); // don't convert this to a number, will lose precision return valueBase10String; }; export const generateRandomV4OrderNonce = ( appId: string = "314159" ): string => { if (appId) { verifyAppIdOrThrow(appId); } const order128 = padStart( generateRandom128BitNumber(), ONE_TWENTY_EIGHT_BIT_LENGTH, "0" ); const appId128 = padEnd( `${RESERVED_APP_ID_PREFIX}${appId}`, ONE_TWENTY_EIGHT_BIT_LENGTH, "0" ); const final256BitNonce = `${appId128}${order128}`; invariant( final256BitNonce.length <= TWO_FIFTY_SIX_BIT_LENGTH, "Invalid nonce size" ); return final256BitNonce; }; export const signOrderData = async ( order: NftOrderV4, signer: ethers.Wallet ) => { const domain = { chainId: 43114, verifyingContract: "0xdef1c0ded9bec7f1a1670819833240f027b25eff", name: "ZeroEx", version: "1.0.0", }; const types = { [ERC721ORDER_STRUCT_NAME]: ERC721ORDER_STRUCT_ABI, Fee: FEE_ABI, Property: PROPERTY_ABI, }; const value = order; const rawSignatureFromEoaWallet = await signer._signTypedData( domain, types, value ); return rawSignatureFromEoaWallet; }; export const prepareEncodedTransaction = async ( signer: Wallet, gasPriceMultiplier: number, gasLimitMultiplier: number, transactionInput: any ) => { const signerAddress = await signer.getAddress(); const signerNonce = await signer.provider.getTransactionCount(signerAddress); let constructedTransaction = { chainId: 43114, nonce: signerNonce, to: "0x398BAa6FFc99126671Ab6be565856105a6118A40", data: "0x", value: "0x0", gasPrice: "0", gasLimit: "0", }; if (transactionInput.data) { constructedTransaction.data = transactionInput.data; } if (transactionInput.value) { constructedTransaction.value = transactionInput.value; } const gasPrice = await signer.provider.getGasPrice(); const gasEstimate = await signer.estimateGas(constructedTransaction); // Double the gas price and limit to be safe. constructedTransaction.gasPrice = gasPrice.mul(gasPriceMultiplier)._hex; constructedTransaction.gasLimit = gasEstimate.mul(gasLimitMultiplier)._hex; return constructedTransaction; }; export const signOrder = async (order: NftOrderV4, signer: ethers.Wallet) => { const rawSignature = await signOrderData(order, signer); const ecSignature = parseRawSignature(rawSignature); const signedOrder = { ...order, signature: { signatureType: 2, r: ecSignature.r, s: ecSignature.s, v: ecSignature.v, }, rawSignature, }; return signedOrder; };