Skip to content

Address and signature do not match #10

@Adil7767

Description

@Adil7767

Following this

https://orderly.network/docs/build-on-omnichain/evm-api/restful-api/private/create-withdraw-request#create-withdraw-request

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions