-
Notifications
You must be signed in to change notification settings - Fork 22
Open
Description
Following this
curl --location 'http://localhost:3000/src/agent/withdraw'
--header 'Content-Type: application/json'
--data '{
"chainId": 901901901,
"WalletAddress": "",
"token": "USDC",
"amount": "19000000",
"chainType": "SOL",
"accountId": """,
"orderlyKey": "ed25519:"",
"orderlyPrivateKey": """
}
'
export const signMessage = (message: string, privateKeyBase58: string): string => {
try {
// Decode the private key from base58
const privateKeyBytes = bs58.decode(privateKeyBase58);
// Convert message to bytes
const messageBytes = new TextEncoder().encode(message);
// Sign the message using nacl
// Note: nacl.sign.detached expects the full secretKey (64 bytes)
const signature = nacl.sign.detached(messageBytes, privateKeyBytes);
// Convert signature to base64
return Buffer.from(signature).toString('base64');
} catch (error) {
console.error("Error signing message:", error);
throw error;
}
};
on my backend API call for orderly
`// orderlyWithdraw.ts - Fixed Functions to handle withdrawals from Orderly
import { AbiCoder, solidityPackedKeccak256 } from "ethers";
import { bytesToHex, hexToBytes } from "ethereum-cryptography/utils";
import { keccak256 } from "ethereum-cryptography/keccak";
import bs58 from "bs58";
import nacl from 'tweetnacl';
// For Solana
import { Keypair, PublicKey, Transaction, TransactionInstruction } from "@solana/web3.js";
import { signMessage, signMessageSolana } from "../../utils/helper";
// Constants
const BASE_URL = "https://testnet-api.orderly.org";
const VERIFYING_CONTRACT = "0x1826B75e2ef249173FC735149AE4B8e9ea10abff";
// Type definitions
interface NonceResponse {
success: boolean;
timestamp: number;
data: {
withdraw_nonce: number;
};
message?: string;
}
interface WithdrawMessage {
brokerId: string;
chainId: number; // Changed from string to number
token: string;
amount: string;
timestamp: number;
withdrawNonce: number; // Changed from nonce to withdrawNonce
chainType: string;
receiver: string;
}
interface WithdrawRequestBody {
message: WithdrawMessage;
signature: string;
userAddress: string;
verifyingContract: string;
}
interface WithdrawResponse {
success: boolean;
timestamp: number;
data: {
withdraw_id: number;
};
message?: string;
}
/**
* Create a withdraw request on Orderly
* @param {string} accountId - Orderly account ID
* @param {string} orderlyKey - Orderly key (format: ed25519:publicKey)
* @param {string} orderlyPrivateKey - Base58 encoded private key
* @param {string} chainType - Chain type (EVM or SOL)
* @param {number} chainId - Chain ID
* @param {string} walletAddress - User's wallet address
* @param {string} token - Token to withdraw
* @param {string} amount - Amount to withdraw (in smallest unit, e.g., for USDC: amount * 1000000)
* @param {string} walletPrivateKey - Private key of the user's wallet
* @param {string} brokerId - Broker ID (default: "pegasus")
* @returns {Promise<WithdrawResponse['data']>} - Withdraw request response
*/
export const withdrawRequest = async (
accountId: string,
orderlyKey: string,
orderlyPrivateKey: string,
chainType: string,
chainId: number, // Changed from string to number
walletAddress: string,
token: string,
amount: string,
walletPrivateKey: string,
brokerId: string = "pegasus"
): Promise<WithdrawResponse['data']> => {
try {
// Step 1: Get withdraw nonce from Orderly API
let signature: string;
let timestamp: string = Date.now().toString();
let path: string = '/v1/withdraw_nonce';
const nonceMessage = `${timestamp}GET${path}`;
console.log("Message to sign:", nonceMessage);
// Sign the message
signature = signMessage(nonceMessage, orderlyPrivateKey);
console.log("Generated signature:", signature);
const nonceRes = await fetch(`${BASE_URL}${path}`, {
method: 'GET',
headers: {
'orderly-timestamp': timestamp,
'orderly-account-id': accountId,
'orderly-key': orderlyKey,
'orderly-signature': signature
}
});
const nonceJson: NonceResponse = await nonceRes.json();
if (!nonceJson.success || !nonceJson.data) {
throw new Error(`Failed to get withdraw nonce: ${nonceJson.message || "Unknown error"}`);
}
const withdrawNonce = nonceJson.data.withdraw_nonce;
console.log("Withdraw nonce:", withdrawNonce);
// Step 2: Create withdraw message with exact structure from curl
const messageTimestamp = Date.now();
// Create the message object that matches the curl request structure
const message: WithdrawMessage = {
brokerId: brokerId,
chainId: chainId, // This should be a number (901901901 in the curl)
receiver: walletAddress,
token: token,
amount: amount,
withdrawNonce: withdrawNonce,
timestamp: messageTimestamp,
chainType: chainType
};
console.log("Message to sign:", JSON.stringify(message, null, 2));
// Step 3: Create the message hash for signing (following EIP-712 structure for Solana)
const messageToSign = createMessageHash(message);
// Step 4: Sign the withdraw message with wallet private key
if (chainType === "SOL") {
signature = await signSolanaMessage(messageToSign, walletPrivateKey);
} else {
// For EVM, you would implement EIP-712 signing here
throw new Error("EVM signing not implemented in this example");
}
console.log("Generated withdraw signature:", signature);
// Step 5: Create request body matching the curl structure exactly
const reqBody: WithdrawRequestBody = {
signature: `0x${signature}`, // Add 0x prefix to match curl
message: message,
userAddress: walletAddress,
verifyingContract: VERIFYING_CONTRACT
};
console.log("Request body:", JSON.stringify(reqBody, null, 2));
// Step 6: Sign the API request with Orderly key
const apiTimestamp = Date.now().toString();
path = '/v1/withdraw_request';
const apiMessage = `${apiTimestamp}POST${path}${JSON.stringify(reqBody)}`;
const apiSignature = signMessageSolana(apiMessage, orderlyPrivateKey);
console.log("API signature:", apiSignature);
// Step 7: Make the API request
const response = await fetch(`${BASE_URL}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"orderly-timestamp": apiTimestamp,
"orderly-account-id": accountId,
"orderly-key": orderlyKey,
"orderly-signature": apiSignature
},
body: JSON.stringify(reqBody)
});
// Step 8: Process response
const responseText = await response.text();
console.log("API Response:", responseText);
const responseJson: WithdrawResponse = JSON.parse(responseText);
if (!responseJson.success || !responseJson.data) {
throw new Error(`Withdraw request failed: ${responseJson.message || "Unknown error"}`);
}
console.log("Withdraw request created successfully!");
return responseJson.data;
} catch (error) {
console.error("Error creating withdraw request:", error);
throw error;
}
};
/**
* Create message hash for signing (simplified approach for Solana)
*/
function createMessageHash(message: WithdrawMessage): Uint8Array {
// Create a deterministic string representation of the message
// This should match how Orderly expects the message to be formatted
const messageString = JSON.stringify({
brokerId: message.brokerId,
chainId: message.chainId,
receiver: message.receiver,
token: message.token,
amount: message.amount,
withdrawNonce: message.withdrawNonce,
timestamp: message.timestamp,
chainType: message.chainType
});
console.log("Message string for hashing:", messageString);
// Convert to bytes for signing
return new TextEncoder().encode(messageString);
}
/**
* Sign message with Solana wallet (simplified approach)
*/
async function signSolanaMessage(messageToSign: Uint8Array, privateKeyString: string): Promise<string> {
try {
// Decode the private key
const privateKeyBytes = bs58.decode(privateKeyString);
const keypair = Keypair.fromSecretKey(privateKeyBytes);
console.log("Signing with public key:", keypair.publicKey.toString());
// Use nacl to sign the message directly (this is more straightforward for message signing)
const signature = nacl.sign.detached(messageToSign, keypair.secretKey);
// Convert signature to hex string
return uint8ArrayToHexString(signature);
} catch (error) {
console.error("Error signing message:", error);
throw error;
}
}
/**
* Helper for converting Uint8Array to hex string
*/
function uint8ArrayToHexString(uint8Array: Uint8Array): string {
return Array.from(uint8Array)
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
}`
bbut geeting error
### Address and signature do not match
Metadata
Metadata
Assignees
Labels
No labels