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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions