-
Notifications
You must be signed in to change notification settings - Fork 18
Description
The Issue: While integrating the update_leverage endpoint, I encountered 400 Invalid Message & 400 Verification error errors when using the flat signature structure shown in the current documentation.
Investigation: I inspected network traffic on the Pacifica Testnet to capture a successful leverage update request. By reverse-engineering the signature verification from that live transaction, I discovered that the API actually requires the business logic fields to be nested inside a data object.
The corrected payload with dummy data:
{ data: { leverage: 12, symbol: 'BTC' }, expiry_window: 30000, timestamp: 1768977047329, type: 'update_leverage' }
Verification: I have attached a script (debug-sig.js) that verifies this structure against real Testnet data. It confirms that the nested structure passes verification while the documented flat structure fails.
const nacl = require("tweetnacl");
const bs58 = require("bs58");
// CONTAINS REAL DATA
const WORKING_PAYLOAD = {
account: "2eijGcDCAoxgwaT3dSkAzTFz9R91ueNngQpBnv4o5JgP",
agent_wallet: "5HmxB7QgSLnPnNHyzk3y5gT7xos5KaCMtCm7cjW3z6yb",
expiry_window: 300000,
leverage: 50,
symbol: "BTC",
timestamp: 1768975643601
};
const SIGNATURE_STRING = "3xwpZcVkGtRfg7D4BaycxurcUzfKiaEKX1RbBuJgybDUkH82fenC3BzHKQKvvTtTVQf9CbRj6KQDY76g4Bs6VinY";
function sortRecursive(item) {
if (typeof item !== 'object' || item === null) return item;
if (Array.isArray(item)) return item.map(sortRecursive);
return Object.keys(item).sort().reduce((acc, key) => {
acc[key] = sortRecursive(item[key]);
return acc;
}, {});
}
function tryVerify(name, candidateObject) {
try {
const sorted = sortRecursive(candidateObject);
const msgString = JSON.stringify(sorted);
const msgBytes = new TextEncoder().encode(msgString);
const sigBytes = bs58.decode(SIGNATURE_STRING);
const pubKeyBytes = bs58.decode(WORKING_PAYLOAD.agent_wallet);
const isValid = nacl.sign.detached.verify(msgBytes, sigBytes, pubKeyBytes);
console.log(`[${isValid ? "matched" : "fail"}] Strategy: ${name}`);
if (isValid) {
console.log(" coorect structure:");
console.log(JSON.stringify(sorted, null, 2));
}
} catch (e) {
console.log(`[ERR] ${name}: ${e.message}`);
}
}
// --- NEW STRATEGIES ---
// Strategy D: Nested "data" object (Standard Pacifica format)
// Header = timestamp, expiry, type
// Data = symbol, leverage
const strategyD = {
timestamp: WORKING_PAYLOAD.timestamp,
expiry_window: WORKING_PAYLOAD.expiry_window,
type: "update_leverage",
data: {
symbol: WORKING_PAYLOAD.symbol,
leverage: WORKING_PAYLOAD.leverage
}
};
// Strategy E: Nested "data" WITH account inside 'data'
const strategyE = {
timestamp: WORKING_PAYLOAD.timestamp,
expiry_window: WORKING_PAYLOAD.expiry_window,
type: "update_leverage",
data: {
symbol: WORKING_PAYLOAD.symbol,
leverage: WORKING_PAYLOAD.leverage,
account: WORKING_PAYLOAD.account
}
};
// Strategy F: Nested "data" WITH account inside 'Header' (Top level)
const strategyF = {
account: WORKING_PAYLOAD.account,
timestamp: WORKING_PAYLOAD.timestamp,
expiry_window: WORKING_PAYLOAD.expiry_window,
type: "update_leverage",
data: {
symbol: WORKING_PAYLOAD.symbol,
leverage: WORKING_PAYLOAD.leverage
}
};
// Strategy G: Flat but WITHOUT Agent Wallet (Just Account + Data + Type)
const strategyG = {
account: WORKING_PAYLOAD.account,
expiry_window: WORKING_PAYLOAD.expiry_window,
leverage: WORKING_PAYLOAD.leverage,
symbol: WORKING_PAYLOAD.symbol,
timestamp: WORKING_PAYLOAD.timestamp,
type: "update_leverage"
};
console.log("TESTING NESTED STRUCTURES...\n");
tryVerify("Nested Data (Minimal)", strategyD);
tryVerify("Nested Data + Account Inside", strategyE);
tryVerify("Nested Data + Account Top", strategyF);
tryVerify("Flat + Account (No Agent)", strategyG);