Poseidon Signature Guide
This guide focuses on Poseidon Hashing and ECDSA Signature, integral components in the secure execution of Place Order operations on our DEX (Decentralized Exchange). Poseidon Hashing, a cryptographic hash function, minimizes complexity in generating and verifying zero-knowledge proofs, making it a fundamental element in our security architecture.
Poseidon Hashing
Construct Order Data
Prepare the order data, including relevant details such as trading pair, order type, quantity, price and vesselPublicKey.
Below is an example of how you can structure the order data, including all relevant details:
- vesselPublicKey: Public key associated with Vessel. Please visit our website to get your Key.
- takerFeeRate: Taker fee rate for the order. Retrieve this from our User Profile. If the fetched fee rates do not match the provided fee rates, an error is thrown when placing order.
- makerFeeRate: Maker fee rate for the order.
- timestamp: Unix timestamp in milliseconds when the order is placed.
- clientOrderId: Unique identifier for the order, often generated based on timestamp.
- buyAssetId: The asset you are acquiring in the trade.
- sellAssetId: The asset you are using to make the purchase.
- buyAssetAmount: Amount of the buy asset.
- sellAssetAmount: Amount of the sell asset.
These data structure can be put into the following example of order data:
{
"vesselPublicKey": "0xYourVesselPublicKey",
"takerFeeRate": 2000,
"makerFeeRate": 2000,
"timestamp": 1644568800000,
"clientOrderId": "uniqueOrderIdentifier",
"buyAssetId": 4,
"sellAssetId": 1,
"buyAssetAmount": 18000000000,
"sellAssetAmount": 64800000000000000000
}
Construct AMM Data
AMM Data including relevant details such as poolId, tickindex, timestamp, asset amount, nounce and vesselPublicKey.
- vesselPublicKey: Public key associated with Vessel. Please visit our website to get your Key.
- poolId: The unique identifier of the AMM pool.
- tickIndexL: The minimum price tick index.
- tickIndexR: The maximum price tick index.
- timestamp: Unix timestamp in milliseconds when the order is placed.
- baseAmount: Amount of the base asset.
- quoteAmount: Amount of the quote asset.
- nonce: An arbitrary number that can be used just once in a cryptographic communication.
These data structure can be put into the following example of order data:
{
"vesselPublicKey": "0xYourVesselPublicKey",
"takerFeeRate": 2000,
"makerFeeRate": 2000,
"timestamp": 1644568800000,
"clientOrderId": "uniqueOrderIdentifier",
"buyAssetId": 4,
"sellAssetId": 1,
"buyAssetAmount": 18000000000,
"sellAssetAmount": 64800000000000000000
}
Poseidon Calculation
For Placing Order
The Poseidon hash is calculated based on the provided order details and the Vessel Public Key using the following formula:
$MsgHash = Poseidon(Poseidon(Poseidon(X, Y), Z), U))$
Where:
$ X = \text{buyAssetId} + \text{sellAssetId} \cdot 2^{32} + \text{takerFeeRate} \cdot 2^{64} + \text{makerFeeRate} \cdot 2^{96} + \text{timestamp} \cdot 2^{128} $
$Y = \text{buyAssetAmount} + \text{sellAssetAmount} \cdot 2^{126}$
$Z = \text{clientOrderId}$
$U = \text{Poseidon}(\text{pointX} , % , |\mathbb{F}_q|, \text{pointY} , % , |\mathbb{F}_q|)$
Notes:
- $\text{pointX}$ is the result of combining 0x with the first 64 bytes of vesselPublicKey(without 0x). $\text{pointY}$ is the result of combining 0x with the last 64 bytes of vesselPublicKey(without 0x).
- $U$, $Z$, and $\text{timestamp}$ uniquely identify each order, while other variables provide detailed order information.
- Specify fee rates ($\text{makerFeeRate}$ and $\text{takerFeeRate}$) as integer values, with the actual percentage obtained by dividing the input by $10^6$.
- Asset amount should be multiplied by $10^{onChainDecimal}$.
For Adding Liquidity
$MsgHash = Poseidon(X, Y, Z)$
Where:
$ X = \text{poolId} + \text{tickIndexL} \cdot 2^{32} + \text{tickIndexR} \cdot 2^{96} + \text{timestamp} \cdot 2^{160} + 2^{224} $
$Y = \text{baseAmount} + \text{quoteAmount} \cdot 2^{126}$
$Z = \text{nonce}$
For Removing Liquidity
$MsgHash = Poseidon(X, Y, Z)$
Where:
$ X = \text{poolId} + \text{tickIndexL} \cdot 2^{32} + \text{tickIndexR} \cdot 2^{96} + \text{timestamp} \cdot 2^{160} $
$Y = \text{baseAmount} + \text{quoteAmount} \cdot 2^{126}$
$Z = \text{nonce}$
For Collecting Fee
$MsgHash = Poseidon(X, Y, Z)$
Where:
$ X = \text{poolId} + \text{tickIndexL} \cdot 2^{32} + \text{tickIndexR} \cdot 2^{96} + \text{timestamp} \cdot 2^{160} $
$Y = 0 $
$Z = \text{nonce}$
ECDSA Signature
Generate an ECDSA signature using the secp256k1 elliptic curve based on the $MsgHash$ and VesselPrivateKey.
JavaScript Example
For your convenience, a JavaScript script is provided below to streamline the process of generating Poseidon signatures for order placement on our platform.
If you prefer to calculate Poseidon signatures in other platforms, we offer an example on Vessel GitHub. This example demonstrates how to access Poseidon implementation in different platforms through C shared libraries, following cgo examples.
Generate Order Signature Script Code
const Decimal = require('decimal.js');
const { buildPoseidon } = require('circomlibjs');
const { ethers } = require('ethers');
const secp256k1 = require("secp256k1");
const fetch = require('node-fetch');
const readline = require('readline');
class PoseidonHashCalculator {
constructor() {
this.poseidon = null;
}
async init() {
this.poseidon = await buildPoseidon();
}
parseJSONInput(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
throw new Error('Invalid JSON input');
}
}
validateAndSetDefaults(inputObj) {
const defaults = {
baseUrl: 'http://trade.vessel.finance',
vesselPublicKey: '0x97fe66c8bbb312f5142a94b95f667a558cee0cee5aa8ccbcacda33f78c1df05ee38883b9a4b540796b455af78a2645e121ed6efa4ee9be202c1c0f4a3ee155c0',
vesselPrivateKey: '0xa044497ce084c9e7ff7ccf2633a7a8d698b156ab14d900530b6aeff4e0c4a79d',
takerFeeRate: 0.002,
makerFeeRate: 0.002,
timestamp: Date.now().toString(),
clientOrderId: null // Set later based on timestamp
};
const requiredFields = ['symbol', 'price', 'quantity', 'side'];
requiredFields.forEach(field => {
if (inputObj[field] === undefined) {
console.error(`Error: "${field}" is required. Please re-enter the input.`);
process.exit(1);
}
});
Object.keys(defaults).forEach(key => {
if (inputObj[key] === undefined) {
console.log(`Note: "${key}" is missing. Using default value: ${defaults[key]}`);
inputObj[key] = defaults[key];
}
});
// Set clientOrderId default to timestamp if not provided
if (!inputObj.clientOrderId) {
inputObj.clientOrderId = inputObj.timestamp;
}
return inputObj;
}
async fetchSymbolInfo(baseUrl, symbol) {
const url = `${baseUrl}/api/v1/public/tickerInfo?symbol=${symbol}`;
const requestOptions = {
method: 'GET',
headers: { "User-Agent": "Apifox/1.0.0 (https://apifox.com)" },
redirect: 'follow'
};
try {
const response = await fetch(url, requestOptions);
const data = await response.json();
if (!data.error && data.symbols && data.symbols.length > 0) {
const symbolInfo = data.symbols.find(s => s.symbol === symbol);
if (symbolInfo) {
return {
baseAssetId: symbolInfo.baseAssetId,
baseAssetDecimal: symbolInfo.baseAssetOnChainDecimal,
quoteAssetId: symbolInfo.quoteAssetId,
quoteAssetDecimal: symbolInfo.quoteAssetOnChainDecimal
};
} else {
throw new Error(`Symbol ${symbol} not found.`);
}
} else {
throw new Error('Error fetching symbol information.');
}
} catch (error) {
console.error('Error in fetchSymbolInfo:', error);
throw error;
}
}
async poseidonHash(
// Add your parameters here. For example:
vesselPublicKey, takerFeeRate, makerFeeRate, baseAssetId,
baseAssetDecimal, quoteAssetId, quoteAssetDecimal, price,
size, side, timestamp, clientOrderId
) {
let buyAssetId, sellAssetId;
let buyAssetAmount, sellAssetAmount;
let buyAssetOnChainDecimal, sellAssetOnChainDecimal;
console.log('takerFeeRate', takerFeeRate);
console.log('makerFeeRate', makerFeeRate);
console.log('timestamp', timestamp);
console.log('clientOrderId', clientOrderId);
const takerFeeRateInEngine = BigInt(takerFeeRate.mul(1000000).toFixed());
const makerFeeRateInEngine = BigInt(makerFeeRate.mul(1000000).toFixed());
console.log('takerFeeRateInEngine', takerFeeRateInEngine);
console.log('makerFeeRateInEngine', makerFeeRateInEngine);
console.log('timestamp', timestamp);
if (side.toUpperCase() === "BUY") {
buyAssetId = baseAssetId;
sellAssetId = quoteAssetId;
buyAssetAmount = new Decimal(size);
sellAssetAmount = new Decimal(size).mul(new Decimal(price));
buyAssetOnChainDecimal = baseAssetDecimal;
sellAssetOnChainDecimal = quoteAssetDecimal;
} else {
buyAssetId = quoteAssetId;
sellAssetId = baseAssetId;
buyAssetAmount = new Decimal(size).mul(new Decimal(price));
sellAssetAmount = new Decimal(size);
buyAssetOnChainDecimal = quoteAssetDecimal;
sellAssetOnChainDecimal = baseAssetDecimal;
}
console.log('buyAssetId', buyAssetId);
console.log('sellAssetId', sellAssetId);
console.log('buyAssetAmount', buyAssetAmount);
console.log('sellAssetAmount', sellAssetAmount);
console.log('buyAssetOnChainDecimal', buyAssetOnChainDecimal);
console.log('sellAssetOnChainDecimal', sellAssetOnChainDecimal);
// Extract pointX and pointY from vesselKey
const ecPoint = vesselPublicKey.slice(2); // Remove prefix
const pointX = BigInt(`0x${ecPoint.slice(0, 64)}`);
const pointY = BigInt(`0x${ecPoint.slice(64, 128)}`);
const binaryBuyAssetId = BigInt(buyAssetId);
const binarySellAssetId = BigInt(sellAssetId) << 32n;
const binaryTakerFeeRate = BigInt(takerFeeRateInEngine) << 64n;
const binaryMakerFeeRate = BigInt(makerFeeRateInEngine) << 96n;
const binaryTimestampMillis = BigInt(timestamp) << 128n;
console.log('binaryBuyAssetId', binaryBuyAssetId);
console.log('binarySellAssetId', binarySellAssetId);
console.log('binaryTakerFeeRate', binaryTakerFeeRate);
console.log('binaryMakerFeeRate', binaryMakerFeeRate);
console.log('binaryTimestampMillis', binaryTimestampMillis);
let x = binaryBuyAssetId + binarySellAssetId + binaryTakerFeeRate + binaryMakerFeeRate + binaryTimestampMillis;
console.log('x', x);
// Decimal calculations and converting to BigIntegers
const binaryBuyAssetAmount = BigInt(new Decimal(buyAssetAmount).mul(new Decimal(buyAssetOnChainDecimal)).toFixed());
const binarySellAssetAmount = BigInt(new Decimal(sellAssetAmount).mul(new Decimal(sellAssetOnChainDecimal)).toFixed()) << 126n;
const y = binaryBuyAssetAmount + binarySellAssetAmount;
console.log('binaryBuyAssetAmount', binaryBuyAssetAmount);
console.log('binarySellAssetAmount', binarySellAssetAmount);
console.log('y', y);
const xyHash = this.poseidon([x, y]);
const z = BigInt(clientOrderId);
console.log('big int client order id', z);
const xyzHash = this.poseidon([xyHash, z]);
console.log('xyHash', this.poseidon.F.toString(xyHash));
console.log('xyzHash', this.poseidon.F.toString(xyzHash));
console.log('pointX', pointX);
console.log('pointY', pointY);
const Fq = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617');
const u = this.poseidon([pointX % Fq, pointY % Fq]);
console.log('u', this.poseidon.F.toString(u));
const poseidonHash = this.poseidon([xyzHash, u]);
console.log('poseidonHash', this.poseidon.F.toString(poseidonHash));
return poseidonHash.toString();
}
async run() {
await this.init();
// Check for command-line argument
if (process.argv.length > 2) {
const jsonInput = process.argv[2];
await this.processInput(jsonInput);
} else {
console.log(`Please input your parameters as a JSON object. Here is an example structure:
{
"symbol": "WBTCUSDT",
"side": "BUY",
"timeInForce": "GTC",
"quantity": 0.01,
"price": 36000,
"vesselPublicKey": "0x97fe66c8bbb312f5142a94b95f667a558cee0cee5aa8ccbcacda33f78c1df05ee38883b9a4b540796b455af78a2645e121ed6efa4ee9be202c1c0f4a3ee155c0",
"vesselPrivateKey": "0xa044497ce084c9e7ff7ccf2633a7a8d698b156ab14d900530b6aeff4e0c4a79d"
}`);
// Read from stdin
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
let inputData = '';
rl.on('line', (line) => {
inputData += line;
try {
JSON.parse(inputData);
rl.close(); // Close the readline interface if JSON is valid
} catch (error) {
// console.log("Awaiting complete JSON input...");
}
});
rl.on('close', async () => {
try {
await this.processInput(inputData);
} catch (error) {
console.log(error);
console.error('Invalid JSON input. Please check the format and try again.');
}
});
}
}
async processInput(jsonInput) {
await this.init();
let inputObj = this.parseJSONInput(jsonInput);
inputObj = this.validateAndSetDefaults(inputObj);
// Fetch symbol info from the API
const symbolInfo = await this.fetchSymbolInfo(inputObj.baseUrl, inputObj.symbol);
console.log(`\n===================Fetching ${inputObj.symbol} info===================`)
console.log(symbolInfo);
console.log('\n===================Start Poseidon Hashing===================');
// Now you have all the necessary information to call poseidonHash
const hashResult = await this.poseidonHash(inputObj.vesselPublicKey, new Decimal(inputObj.takerFeeRate), new Decimal(inputObj.makerFeeRate), symbolInfo.baseAssetId, symbolInfo.baseAssetDecimal, symbolInfo.quoteAssetId, symbolInfo.quoteAssetDecimal, new Decimal(inputObj.price), new Decimal(inputObj.quantity), inputObj.side, BigInt(inputObj.timestamp), inputObj.clientOrderId);
const hexHash = ethers.utils.arrayify(ethers.utils.hexlify(BigInt(this.poseidon.F.toString(hashResult))));
console.log(inputObj.vesselPrivateKey);
const signObj = secp256k1.ecdsaSign(hexHash, ethers.utils.arrayify(inputObj.vesselPrivateKey));
console.log('hexHash', ethers.utils.hexlify(hexHash));
console.log('signature', ethers.utils.hexlify(signObj.signature));
console.log("\n===================The request body===================");
console.log(
`{
"symbol": "${inputObj.symbol}",
"side": "${inputObj.side}",
"type": "LIMIT",
"timeInForce": "GTC",
"quantity": ${inputObj.quantity},
"price": ${inputObj.price},
"clientOrderId": "${inputObj.clientOrderId}",
"signature": "${ethers.utils.hexlify(signObj.signature)}",
"newOrderRespType": "FULL"
}`);
console.log("\n===================VESSEL-TIMESTAMP in header===================")
console.log(inputObj.timestamp);
}
}
async function main() {
const calculator = new PoseidonHashCalculator();
await calculator.run();
}
main();
Generate AMM Signature Script Code
const Decimal = require('decimal.js');
const { buildPoseidon } = require('circomlibjs');
const { ethers } = require('ethers');
const secp256k1 = require("secp256k1");
const fetch = require('node-fetch');
const readline = require('readline');
class PoseidonHashCalculator {
constructor() {
this.poseidon = null;
}
async init() {
this.poseidon = await buildPoseidon();
}
parseJSONInput(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
throw new Error('Invalid JSON input');
}
}
validateAndSetDefaults(inputObj) {
const defaults = {
baseUrl: 'http://trade.vessel.finance',
vesselPublicKey: '0x97fe66c8bbb312f5142a94b95f667a558cee0cee5aa8ccbcacda33f78c1df05ee38883b9a4b540796b455af78a2645e121ed6efa4ee9be202c1c0f4a3ee155c0',
vesselPrivateKey: '0xa044497ce084c9e7ff7ccf2633a7a8d698b156ab14d900530b6aeff4e0c4a79d',
timestamp: Date.now().toString(),
nonce: null // Set later based on timestamp
};
const requiredFields = ['poolId', 'tickIndexL', 'tickIndexR', 'baseAmount', 'quoteAmount', 'type'];
const types = ['AddLiquidity', 'RemoveLiquidity', 'CollectFee'];
requiredFields.forEach(field => {
if (inputObj[field] === undefined) {
console.error(`Error: "${field}" is required. Please re-enter the input.`);
process.exit(1);
}
if (field === "type" && ! types.includes(inputObj[field])) {
console.error(`Error: "type ${field}" is not one of ['AddLiquidity', 'RemoveLiquidity', 'CollectFee']. Please re-enter the input.`);
process.exit(1);
}
});
Object.keys(defaults).forEach(key => {
if (inputObj[key] === undefined) {
console.log(`Note: "${key}" is missing. Using default value: ${defaults[key]}`);
inputObj[key] = defaults[key];
}
});
// Set clientOrderId default to timestamp if not provided
if (!inputObj.nonce) {
inputObj.nonce = inputObj.timestamp;
}
return inputObj;
}
async fetchSymbolInfo(baseUrl, symbol) {
const url = `${baseUrl}/api/v1/public/tickerInfo?symbol=${symbol}`;
const requestOptions = {
method: 'GET',
headers: {"User-Agent": "Apifox/1.0.0 (https://apifox.com)"},
redirect: 'follow'
};
try {
const response = await fetch(url, requestOptions);
const data = await response.json();
if (!data.error && data.symbols && data.symbols.length > 0) {
const symbolInfo = data.symbols.find(s => s.symbol === symbol);
if (symbolInfo) {
return {
baseAssetId: symbolInfo.baseAssetId,
baseAssetDecimal: symbolInfo.baseAssetOnChainDecimal,
quoteAssetId: symbolInfo.quoteAssetId,
quoteAssetDecimal: symbolInfo.quoteAssetOnChainDecimal
};
} else {
throw new Error(`Symbol ${symbol} not found.`);
}
} else {
throw new Error('Error fetching symbol information.');
}
} catch (error) {
console.error('Error in fetchSymbolInfo:', error);
throw error;
}
}
async fetchPoolInfo(baseUrl, poolId) {
const url = `${baseUrl}/api/v1/amm/pools?poolId=${poolId}`;
const requestOptions = {
method: 'GET',
headers: {"User-Agent": "Apifox/1.0.0 (https://apifox.com)"},
redirect: 'follow'
};
try {
const response = await fetch(url, requestOptions);
const data = await response.json();
if (!data.error && data.pools && data.pools.length > 0) {
const poolInfo = data.pools.find(s => s.poolId === poolId);
if (poolInfo) {
return {
symbol: poolInfo.symbol,
};
} else {
throw new Error(`Pool ${poolId} not found.`);
}
} else {
throw new Error('Error fetching Pool information.');
}
} catch (error) {
console.error('Error in fetchPoolInfo:', error);
throw error;
}
}
async poseidonAmmHash(
poolId, leftTickIndex, rightTickIndex, timestamp, isAdd,
approvedBaseSize, approvedQuoteSize, baseAssetDecimal, quoteAssetDecimal, nonce
) {
console.log('poolId', poolId);
console.log('leftTickIndex', leftTickIndex);
console.log('rightTickIndex', rightTickIndex);
console.log('timestamp', timestamp);
console.log('isAdd', isAdd);
const binaryPoolId = BigInt(poolId);
const binaryLeftTickIndex = BigInt(leftTickIndex) << 32n;
const binaryRightTickIndex = BigInt(rightTickIndex) << 96n;
const binaryTimestampMillis = BigInt(timestamp) << 160n;
let binaryIsAdd = BigInt(0);
if (isAdd) {
binaryIsAdd = BigInt(1) << 224n;
}
console.log('binaryPoolId', binaryPoolId);
console.log('binaryLeftTickIndex', binaryLeftTickIndex);
console.log('binaryRightTickIndex', binaryRightTickIndex);
console.log('binaryTimestampMillis', binaryTimestampMillis);
console.log('binaryIsAdd', binaryIsAdd);
let x = binaryPoolId + binaryLeftTickIndex + binaryRightTickIndex + binaryTimestampMillis + binaryIsAdd;
console.log('x', x);
console.log('approvedBaseSize', approvedBaseSize);
console.log('baseAssetDecimal', baseAssetDecimal);
console.log('approvedQuoteSize', approvedQuoteSize);
console.log('quoteAssetDecimal', quoteAssetDecimal);
// Decimal calculations and converting to BigIntegers
const binaryBaseSize = BigInt(new Decimal(approvedBaseSize).mul(new Decimal(baseAssetDecimal)).toFixed());
const binaryQuoteSize = BigInt(new Decimal(approvedQuoteSize).mul(new Decimal(quoteAssetDecimal)).toFixed()) << 126n;
// const binaryBaseSize = BigInt(new Decimal(approvedBaseSize).mul(new Decimal(Decimal.pow(10, baseAssetDecimal))).toFixed());
// const binaryQuoteSize = BigInt(new Decimal(approvedQuoteSize).mul(new Decimal(Decimal.pow(10, quoteAssetDecimal))).toFixed()) << 126n;
const y = binaryBaseSize + binaryQuoteSize;
console.log('binaryBaseSize', binaryBaseSize);
console.log('binaryQuoteSize', binaryQuoteSize);
console.log('y', y);
const xyHash = this.poseidon([x, y]);
const z = BigInt(nonce);
console.log('nonce', z);
const xyzHash = this.poseidon([xyHash, z]);
console.log('xyHash', this.poseidon.F.toString(xyHash));
console.log('xyzHash', this.poseidon.F.toString(xyzHash));
const poseidonHash = xyzHash;
console.log('poseidonHash', this.poseidon.F.toString(poseidonHash));
return poseidonHash.toString();
}
async run() {
await this.init();
// Check for command-line argument
if (process.argv.length > 2) {
const jsonInput = process.argv[2];
await this.processInput(jsonInput);
} else {
console.log(`Please input your parameters as a JSON object. Here is an example structure:
{
"poolId": 2,
"tickIndexL": 260,
"tickIndexR": 400,
"baseAmount": 1,
"quoteAmount": 80000,
"type": "AddLiquidity",
"vesselPublicKey": "0x97fe66c8bbb312f5142a94b95f667a558cee0cee5aa8ccbcacda33f78c1df05ee38883b9a4b540796b455af78a2645e121ed6efa4ee9be202c1c0f4a3ee155c0",
"vesselPrivateKey": "0xa044497ce084c9e7ff7ccf2633a7a8d698b156ab14d900530b6aeff4e0c4a79d"
}`);
// Read from stdin
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
let inputData = '';
rl.on('line', (line) => {
inputData += line;
try {
JSON.parse(inputData);
rl.close(); // Close the readline interface if JSON is valid
} catch (error) {
// console.log("Awaiting complete JSON input...");
}
});
rl.on('close', async () => {
try {
await this.processInput(inputData);
} catch (error) {
console.log(error);
console.error('Invalid JSON input. Please check the format and try again.');
}
});
}
}
async processInput(jsonInput) {
await this.init();
let inputObj = this.parseJSONInput(jsonInput);
inputObj = this.validateAndSetDefaults(inputObj);
// Fetch pool info from the API
const poolInfo = await this.fetchPoolInfo(inputObj.baseUrl, inputObj.poolId);
console.log(`\n===================Fetching Pool ${inputObj.poolId} info===================`)
console.log(poolInfo);
// Fetch symbol info from the API
const symbolInfo = await this.fetchSymbolInfo(inputObj.baseUrl, poolInfo.symbol);
console.log(`\n===================Fetching ${inputObj.symbol} info===================`)
console.log(symbolInfo);
console.log('\n===================Start Poseidon Hashing===================');
let baseAmount = inputObj.baseAmount;
let quoteAmount = inputObj.quoteAmount;
let isAdd = false;
if (inputObj.type === "AddLiquidity"){
isAdd = true;
}else if (inputObj.type === "CollectFee"){
baseAmount = 0;
quoteAmount = 0;
}
// Now you have all the necessary information to call poseidonHash
const hashResult = await this.poseidonAmmHash(inputObj.poolId, inputObj.tickIndexL, inputObj.tickIndexR,BigInt(inputObj.timestamp),isAdd,
new Decimal(baseAmount), new Decimal(quoteAmount), symbolInfo.baseAssetDecimal, symbolInfo.quoteAssetDecimal, inputObj.nonce);
const hexHash = ethers.utils.arrayify(ethers.utils.hexlify(BigInt(this.poseidon.F.toString(hashResult))));
console.log(inputObj.vesselPrivateKey);
const signObj = secp256k1.ecdsaSign(hexHash, ethers.utils.arrayify(inputObj.vesselPrivateKey));
console.log('hexHash', ethers.utils.hexlify(hexHash));
console.log('signature', ethers.utils.hexlify(signObj.signature));
console.log("\n===================The request body===================");
if (inputObj.type === "CollectFee"){
console.log(
`{
"poolId": ${inputObj.poolId},
"tickIndexL": ${inputObj.tickIndexL},
"tickIndexR": ${inputObj.tickIndexR},
"nonce": "${inputObj.nonce}",
"signature": "${ethers.utils.hexlify(signObj.signature)}"
}`);
}else{
console.log(
`{
"poolId": ${inputObj.poolId},
"tickIndexL": ${inputObj.tickIndexL},
"tickIndexR": ${inputObj.tickIndexR},
"baseAmount": ${inputObj.baseAmount},
"quoteAmount": ${inputObj.quoteAmount},
"nonce": "${inputObj.nonce}",
"signature": "${ethers.utils.hexlify(signObj.signature)}"
}`);
}
console.log("\n===================VESSEL-TIMESTAMP in header===================")
console.log(inputObj.timestamp);
}
}
async function main() {
const calculator = new PoseidonHashCalculator();
await calculator.run();
}
main();
Script Guide
Run the Script Locally
- Copy the JavaScript script into your local environment.
- Run the script using your preferred JavaScript runtime environment.
- Install the required Node.js modules.
- The dependency on ethers version ^5.7.2 is recommended. Other versions may cause errors.
Input Default or Custom Parameters
- The script comes with default parameters for testing purposes on Testnet.
- Modify parameters such as symbol, side, quantity, vesselPublicKey, vesselPrivateKey to match your desired order details.
- When generating an AMM signature, users can input "AddLiquidity", "RemoveLiquidity" and "CollectFee" as the value for the type parameter.
Copy Results for Apifox Testing
- Once the script completes, it will generate the body parameters and the timestamp for the header for order placement.
- Copy the json, then paste it into the body of your order placement request in Apifox.
- Copy the Unix timestamp in the script output and set it in the header of your order placement request in APIfox.
Important Notes
Keep Your vesselPrivateKey Secure
Never share your vesselPrivateKey with anyone. It's crucial for generating signature for order placement.
Timestamp Usage
The timestamp in the signature cannot be earlier than the order placement request timestamp by more than 60 seconds. Ensure synchronization for accurate results.
Feel free to customize parameters based on your testing needs. Experience the simplicity of generating Poseidon signatures for secure and authenticated order placement.