@firefly-exchange/library-sui
Version:
Sui library housing helper methods, classes to interact with Bluefin protocol(s) deployed on Sui
272 lines (271 loc) • 12.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addPrefix = exports.verifySECP = exports.parseAndShapeSignedData = exports.createZkSignature = exports.getSalt = exports.getRandomInt = exports.requestGas = exports.storedOrderToSui = exports.printOrder = exports.createOrder = exports.decodeZkSignature = exports.verifyZkPublicKey = exports.verifyZkAddress = exports.getSignerFromSeed = exports.getKeyPairFromPvtKey = exports.keypairFromSecretKey = exports.getKeyPairFromSeed = exports.getProvider = exports.readFile = exports.writeFile = void 0;
const enums_1 = require("../src/enums");
const library_1 = require("./library");
const defaults_1 = require("./defaults");
const dotenv_1 = require("dotenv");
const DeploymentConfig_1 = require("./DeploymentConfig");
const verify_1 = require("@mysten/sui/verify");
const bcs_1 = require("@mysten/bcs");
const secp256k1_1 = require("@noble/curves/secp256k1");
const cryptography_1 = require("@mysten/sui/cryptography");
const zklogin_1 = require("@mysten/sui/zklogin");
const fs_1 = __importDefault(require("fs"));
const types_1 = require("./types");
const zklogin_2 = require("@mysten/zklogin");
(0, dotenv_1.config)({ path: ".env" });
function writeFile(filePath, jsonData) {
fs_1.default.writeFileSync(filePath, JSON.stringify(jsonData));
}
exports.writeFile = writeFile;
function readFile(filePath) {
return fs_1.default.existsSync(filePath)
? JSON.parse(fs_1.default.readFileSync(filePath).toString())
: {};
}
exports.readFile = readFile;
function getProvider(rpcURL) {
return new types_1.SuiClient({ url: rpcURL });
}
exports.getProvider = getProvider;
function getKeyPairFromSeed(seed, scheme = "Secp256k1") {
switch (scheme) {
case "ED25519":
return types_1.Ed25519Keypair.deriveKeypair(seed);
case "Secp256k1":
return types_1.Secp256k1Keypair.deriveKeypair(seed);
default:
throw new Error("Provided scheme is invalid");
}
}
exports.getKeyPairFromSeed = getKeyPairFromSeed;
function keypairFromSecretKey(privateKeyBase64) {
const keyPair = (0, cryptography_1.decodeSuiPrivateKey)(privateKeyBase64);
return types_1.Ed25519Keypair.fromSecretKey(keyPair.secretKey);
}
exports.keypairFromSecretKey = keypairFromSecretKey;
function getKeyPairFromPvtKey(key, scheme = "Secp256k1") {
if (key.startsWith("0x")) {
key = key.substring(2); // Remove the first two characters (0x)
}
switch (scheme) {
case "ED25519":
return types_1.Ed25519Keypair.fromSecretKey(Buffer.from(key, "hex"));
case "Secp256k1":
return types_1.Secp256k1Keypair.fromSecretKey(Buffer.from(key, "hex"));
case "ZkLogin":
return keypairFromSecretKey(key);
default:
throw new Error("Provided key is invalid");
}
}
exports.getKeyPairFromPvtKey = getKeyPairFromPvtKey;
function getSignerFromSeed(seed, scheme = "Secp256k1") {
return getKeyPairFromSeed(seed, scheme);
}
exports.getSignerFromSeed = getSignerFromSeed;
//need to check zkLogin flow
const verifyZkAddress = (signature, userAddress) => {
const parsedSignature = (0, cryptography_1.parseSerializedSignature)(signature);
if (parsedSignature.signatureScheme == "ZkLogin") {
const legacyAddress = (0, zklogin_1.computeZkLoginAddressFromSeed)(parsedSignature.zkLogin.addressSeed, parsedSignature.zkLogin.iss, true);
const nonLegacyAddress = (0, zklogin_1.computeZkLoginAddressFromSeed)(parsedSignature.zkLogin.addressSeed, parsedSignature.zkLogin.iss, false);
return legacyAddress === userAddress || nonLegacyAddress === userAddress;
}
return false;
};
exports.verifyZkAddress = verifyZkAddress;
const verifyZkPublicKey = ({ signature, publicKey }) => {
const parsedSignature = (0, cryptography_1.parseSerializedSignature)(signature);
if (parsedSignature.signatureScheme == "ZkLogin") {
const userSignature = (0, bcs_1.toB64)(parsedSignature.zkLogin.userSignature);
const parsedUserSignature = (0, cryptography_1.parseSerializedSignature)(userSignature);
if (parsedUserSignature.signatureScheme == "MultiSig") {
throw "multisig not supported";
}
const { publicKey: signaturePublicKey, signatureScheme } = parsedUserSignature;
return ((0, verify_1.publicKeyFromRawBytes)(signatureScheme, signaturePublicKey).toBase64() ===
publicKey);
}
return false;
};
exports.verifyZkPublicKey = verifyZkPublicKey;
const decodeZkSignature = (decodedSignature) => {
const PUBLIC_KEY_LENGTH = 44;
const SCHEME_LENGTH = 1;
const SIGNATURE_LENGTH = decodedSignature.length - PUBLIC_KEY_LENGTH - SCHEME_LENGTH;
const signature = decodedSignature.slice(0, SIGNATURE_LENGTH);
const scheme = decodedSignature.slice(SIGNATURE_LENGTH, SIGNATURE_LENGTH + SCHEME_LENGTH);
const publicKey = decodedSignature.slice(-PUBLIC_KEY_LENGTH);
return {
zkSignature: Buffer.from(signature, "hex").toString("utf-8"),
publicKey,
scheme
};
};
exports.decodeZkSignature = decodeZkSignature;
function createOrder(params) {
return {
market: params?.market || defaults_1.DEFAULT.ORDER.market,
maker: params?.maker || defaults_1.DEFAULT.ORDER.maker,
isBuy: params?.isBuy == true,
reduceOnly: params?.reduceOnly == true,
postOnly: params?.postOnly == true,
orderbookOnly: params?.orderbookOnly == undefined ? true : params.orderbookOnly,
ioc: params?.ioc == true,
price: params?.price ? (0, library_1.toBigNumber)(params.price) : defaults_1.DEFAULT.ORDER.price,
quantity: params?.quantity
? (0, library_1.toBigNumber)(params.quantity)
: defaults_1.DEFAULT.ORDER.quantity,
leverage: params?.leverage
? (0, library_1.toBigNumber)(params?.leverage)
: defaults_1.DEFAULT.ORDER.leverage,
expiration: params?.expiration
? (0, library_1.bigNumber)(params?.expiration)
: defaults_1.DEFAULT.ORDER.expiration,
salt: params?.salt ? (0, library_1.bigNumber)(params?.salt) : (0, library_1.bigNumber)(Date.now())
};
}
exports.createOrder = createOrder;
function printOrder(order) {
console.log("\norder.maker:", order.maker, "\norder.market:", order.market, "\norder.isBuy:", order.isBuy, "\norder.reduceOnly:", order.reduceOnly, "\norder.postOnly:", order.postOnly, "\norder.orderbookOnly:", order.orderbookOnly, "\norder.ioc:", order.ioc, "\norder.price:", order.price.toFixed(0), "\norder.quantity:", order.quantity.toFixed(0), "\norder.leverage:", order.leverage.toFixed(0), "\norder.expiration:", order.expiration.toFixed(0), "\norder.salt:", order.salt.toFixed(0));
}
exports.printOrder = printOrder;
/**
* Consumes an order of type StoredOrder and returns a SuiOrder that can be sent to on-chain for settlement
* @param order StoredOrder to be transformed
* @param perpetualID market/perpetual address
* @param orderbookOnly (optional) true by default as all orders going through our exchange have orderbook only true
*/
function storedOrderToSui(order, perpetualID, orderbookOnly = true) {
return {
market: perpetualID,
maker: order.maker,
isBuy: order.isBuy,
reduceOnly: order.reduceOnly,
postOnly: order.postOnly,
orderbookOnly,
ioc: order.timeInForce == enums_1.TIME_IN_FORCE.IMMEDIATE_OR_CANCEL,
quantity: order.amount,
price: order.price,
leverage: order.leverage,
expiration: (0, library_1.bigNumber)(order.expiration),
salt: (0, library_1.bigNumber)(order.salt)
};
}
exports.storedOrderToSui = storedOrderToSui;
async function requestGas(address) {
const url = DeploymentConfig_1.network.faucet;
try {
const data = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
FixedAmountRequest: {
recipient: address
}
})
});
return data;
}
catch (e) {
console.log("Error while requesting gas", e.message);
}
return false;
}
exports.requestGas = requestGas;
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (min - max) + min); // The maximum is exclusive and the minimum is inclusive
}
exports.getRandomInt = getRandomInt;
function getSalt() {
return (Date.now() +
getRandomInt(0, 1000000) +
getRandomInt(0, 1000000) +
getRandomInt(0, 1000000));
}
exports.getSalt = getSalt;
const createZkSignature = ({ userSignature, zkPayload }) => {
const { salt, decodedJWT, proof, maxEpoch } = zkPayload;
const addressSeed = (0, zklogin_2.genAddressSeed)(BigInt(salt), "sub", decodedJWT.sub, decodedJWT.aud).toString();
const zkLoginSignature = (0, zklogin_2.getZkLoginSignature)({
inputs: {
...proof,
addressSeed
},
maxEpoch: maxEpoch,
userSignature
});
return zkLoginSignature;
};
exports.createZkSignature = createZkSignature;
const parseAndShapeSignedData = ({ signature, isParsingRequired = true }) => {
let data;
const parsedSignature = (0, cryptography_1.parseSerializedSignature)(signature);
if (isParsingRequired && parsedSignature.signatureScheme === "ZkLogin") {
//zk login signature
const { userSignature } = parsedSignature.zkLogin;
//convert user sig to b64
const convertedUserSignature = (0, bcs_1.toB64)(userSignature);
//reparse b64 converted user sig
const parsedUserSignature = (0, cryptography_1.parseSerializedSignature)(convertedUserSignature);
if (parsedUserSignature.signatureScheme == "MultiSig") {
throw "multisig not supported";
}
data = {
signature: Buffer.from(parsedSignature.serializedSignature).toString("hex") + "3",
publicKey: (0, verify_1.publicKeyFromRawBytes)(parsedUserSignature.signatureScheme, parsedUserSignature.publicKey).toBase64()
};
}
else if (parsedSignature.signatureScheme != "MultiSig") {
data = {
signature: Buffer.from(parsedSignature.signature).toString("hex") +
enums_1.SIGNER_TYPES.UI_ED25519,
publicKey: (0, verify_1.publicKeyFromRawBytes)(parsedSignature.signatureScheme, parsedSignature.publicKey).toBase64()
};
}
else {
throw "signature scheme not supported";
}
return data;
};
exports.parseAndShapeSignedData = parseAndShapeSignedData;
/**
* Validates if the provided signature is created using the data and public key provided
* @param signature hex encoded signature
* @param data data bytes
* @param publicKey public key bytes
* @returns True if signature is valid
*/
function verifySECP(signature, data, publicKey) {
const sig_r_s = secp256k1_1.secp256k1.Signature.fromCompact(signature);
const sig_r_s_b1 = sig_r_s.addRecoveryBit(0x1);
const recovered_pk_1 = sig_r_s_b1.recoverPublicKey(data).toRawBytes(true).toString();
const sig_r_s_b0 = sig_r_s.addRecoveryBit(0x0);
const recovered_pk_0 = sig_r_s_b0.recoverPublicKey(data).toRawBytes(true).toString();
return (publicKey.toString() === recovered_pk_1 || publicKey.toString() === recovered_pk_0);
}
exports.verifySECP = verifySECP;
/**
* Ensures that an address string starts with "0x".
* If it doesn't, the function prepends "0x" to the address.
*
* @param address - The token string to check.
* @returns The address string with a "0x" prefix.
*/
function addPrefix(address) {
// Convert token to lowercase to check case-insensitively
if (address.toLowerCase().startsWith("0x")) {
return address;
}
return "0x" + address;
}
exports.addPrefix = addPrefix;