npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@edgex-fe/typescript-sdk

v0.1.4

Published

Official TypeScript SDK for EdgeX API - Comprehensive trading and market data integration

Readme

EdgeX TypeScript SDK API 接口文档

用于 EdgeX 交易平台的 TypeScript SDK,提供完整的交易、认证、WebSocket 和提现功能。

安装

npm install @edgex/typescript-sdk

快速开始

import { EdgeXSDK } from '@edgex/typescript-sdk';

// 初始化 SDK
const sdk = new EdgeXSDK({
  clientId: 'your-client-id'
});

// 或使用单例模式
const sdk = EdgeXSDK.getInstance({
  clientId: 'your-client-id'
});

目录


获取 Metadata 配置

Metadata 包含交易所的全局配置信息,包括币种列表、合约信息、链配置等。

API 调用

import { ApiService } from './api/services';

// 获取 metadata 配置
const response = await ApiService.getMetadata();
const metadata = response.data;

// 设置到 SDK 中
sdk.setMetadata(metadata);

使用 React Hook

import { useMetadata } from './hooks/useMetadata';

function App() {
  const { meta, config, loading } = useMetadata();

  useEffect(() => {
    if (meta) {
      sdk.setMetadata(meta);
    }
  }, [meta]);

  // config 包含:
  // - appName: 应用名称 (默认: "edgeX")
  // - appEnv: 环境 (默认: "testnet")
  // - onlySignOn: 签名域名 (默认: "https://testnet.edgex.exchange")
}

Metadata 数据结构

interface Metadata {
  global: {
    appName: string;
    appEnv: string;
    appOnlySignOn: string;
    starkExChainId: string;
    starkExContractAddress: string;
    starkExCollateralCoin: {
      coinId: string;
      coinName: string;
      starkExAssetId: string;
      starkExResolution: string;
    };
    // ... 其他全局配置
  };
  coinList: Array<{
    coinId: string;
    coinName: string;
    stepSize: string;
    showStepSize: string;
    iconUrl: string;
    starkExAssetId: string | null;
    starkExResolution: string | null;
  }>;
  contractList: Array<{
    contractId: string;
    contractName: string;
    baseCoinId: string;
    quoteCoinId: string;
    tickSize: string;
    stepSize: string;
    minOrderSize: string;
    maxOrderSize: string;
    // ... 其他合约配置
  }>;
  multiChain: {
    coinId: string;
    maxWithdraw: string;
    minWithdraw: string;
    chainList: Array<{
      chain: string;
      chainId: string;
      contractAddress: string;
      rpcUrl: string;
      tokenList: Array<{
        tokenAddress: string;
        decimals: string;
        token: string;
        // ... 其他代币配置
      }>;
      // ... 其他链配置
    }>;
  };
}

登录认证

EdgeX 使用钱包签名进行身份认证,需要生成 API 密钥和 L2 密钥对。

1. 生成 API 密钥

API 密钥用于调用私有 API 接口。

import { createWalletClient, custom, getAddress } from "viem";
import { usePrivy, useWallets } from "@privy-io/react-auth";

// 生成 API 密钥
async function generateApiKey() {
  const { user } = usePrivy();
  const { wallets } = useWallets();

  const effectiveAddress = user?.wallet?.address;
  if (!effectiveAddress) throw new Error("钱包未连接");

  // 获取 Privy 钱包
  const privyWallet = wallets.find(
    w => w.address.toLowerCase() === effectiveAddress.toLowerCase()
  );

  // 创建钱包客户端
  const walletClient = createWalletClient({
    account: getAddress(effectiveAddress),
    chain: currentChain,
    transport: custom({
      async request({ method, params }) {
        const provider = await privyWallet.getEthereumProvider();
        return await provider.request({ method, params });
      },
    }),
  });

  // 签名消息
  const message = `action: edgeX Onboard\nonlySignOn: https://testnet.edgex.exchange`;
  const signature = await walletClient.signMessage({
    account: getAddress(effectiveAddress),
    message,
  });

  // 生成 API 密钥
  const apiKeys = sdk.generateApiKeyFromSignature(signature);
  sdk.setApiKeys(apiKeys);

  return apiKeys;
}

2. 生成 L2 密钥对

L2 密钥对用于 StarkEx 层的交易签名。

// 生成 L2 密钥对
async function generateL2KeyPair() {
  const clientAccountId = "main";
  const message = `name: edgeX\nenvId: testnet\naction: L2 Key\nonlySignOn: https://testnet.edgex.exchange\nclientAccountId: ${clientAccountId}`;

  const signature = await walletClient.signMessage({
    account: getAddress(effectiveAddress),
    message,
  });

  // 生成 L2 密钥对
  const l2KeyPair = sdk.generateL2KeyPairFromSignature(signature);
  sdk.setL2KeyPair(l2KeyPair);

  return l2KeyPair;
}

3. 生成认证头

使用 API 密钥生成请求认证头。

// 生成认证头
const headers = sdk.createAuthHeaders({
  method: 'GET',
  path: '/api/v1/private/user/getUserInfo',
  timestamp: Date.now().toString(),
  params: { userId: '123456' }, // 可选的查询参数
});

// headers 包含:
// {
//   'X-EdgeX-Api-Key': 'your-api-key',
//   'X-EdgeX-Passphrase': 'your-passphrase',
//   'X-EdgeX-Signature': 'generated-signature',
//   'X-EdgeX-Timestamp': 'timestamp'
// }

4. 密钥数据结构

// API 密钥结构
interface ApiKeys {
  apiKey: string;
  apiPassphrase: string;
  apiSecret: string;
}

// L2 密钥对结构
interface L2KeyPair {
  l2PrivateKey: string;
  l2PublicKey: string;
  l2PublicKeyY: string;
}

创建订单

创建交易订单需要使用 L2 签名进行 StarkEx 层验证。

1. 订单参数

import { TradeOrderParams } from '@edgex/typescript-sdk';

const tradeParams: TradeOrderParams = {
  price: '30.000',              // 订单价格
  size: '1.0',                  // 订单数量
  type: 'LIMIT',                // 订单类型: LIMIT | MARKET
  timeInForce: 'GOOD_TIL_CANCEL', // 时效性: GOOD_TIL_CANCEL | IMMEDIATE_OR_CANCEL | FILL_OR_KILL
  reduceOnly: false,            // 是否只减仓
  isPositionTpsl: false,        // 是否为止盈止损单
  isSetOpenTp: false,           // 是否设置开仓止盈
  isSetOpenSl: false,           // 是否设置开仓止损
  contractId: '20000018',       // 合约ID (AVAXUSD)
  side: 'BUY',                  // 买卖方向: BUY | SELL
  triggerPrice: '',             // 触发价格 (条件单)
  triggerPriceType: 'LAST_PRICE', // 触发价格类型
  extraType: '',                // 额外类型
  extraDataJson: '',            // 额外数据
  accountId: 'your-account-id', // 账户ID
};

2. 生成订单签名

// 获取合约信息
const symbolInfo = {
  contractId: '20000018',
  symbol: 'AVAXUSD',
  contractName: 'AVAXUSD',
  oraclePrice: '29.641',
  tickSize: '0.001',
  takerFeeRate: '0.001',
  makerFeeRate: '0.001',
};

// 链信息
const chainInfo = {
  chainId: '11155111', // Sepolia testnet
};

// 账户信息
const accountInfo = {
  accountId: 'your-account-id',
};

// 生成交易参数和签名
const result = await sdk.generateTradeParams({
  tradeParams,
  symbolInfo,
  chainInfo,
  accountInfo,
  requestPath: '/api/v1/private/order/createOrder',
  requestMethod: 'POST',
});

// result 包含:
// {
//   headers: { /* 认证头 */ },
//   params: {
//     ...tradeParams,
//     l2Signature: 'generated-l2-signature'
//   }
// }

3. 发送订单请求

import { apiClient } from './api/client';

// 创建订单
async function createOrder(tradeParams: TradeOrderParams) {
  // 生成签名参数
  const { headers, params } = await sdk.generateTradeParams({
    tradeParams,
    symbolInfo,
    chainInfo,
    accountInfo,
    requestPath: '/api/v1/private/order/createOrder',
    requestMethod: 'POST',
  });

  // 发送请求
  const response = await apiClient.post('/api/v1/private/order/createOrder', params, {
    headers,
  });

  return response.data;
}

4. 订单类型说明

| 类型 | 说明 | |------|------| | LIMIT | 限价单 - 指定价格执行 | | MARKET | 市价单 - 按市场价格立即执行 |

5. 时效性说明

| 类型 | 说明 | |------|------| | GOOD_TIL_CANCEL | 有效直到取消 | | IMMEDIATE_OR_CANCEL | 立即执行或取消 | | FILL_OR_KILL | 全部成交或取消 |


获取 WebSocket 连接

EdgeX 提供公有和私有两种 WebSocket 连接,分别用于获取市场数据和用户数据。

1. 公有 WebSocket (市场数据)

用于获取行情、K线、深度等公开市场数据,无需认证。

import { usePublicWebSocket } from './hooks/usePublicWebSocket';

function MarketData() {
  const {
    wsStatus,
    connect,
    disconnect,
    subscribe,
    unsubscribe,
    isConnected
  } = usePublicWebSocket({
    wsHost: 'wss://quote-testnet.edgex.exchange',
    onMessage: (data) => {
      console.log('收到市场数据:', data);
      // 处理行情数据
      if (data.type === 'ticker') {
        // 处理ticker数据
      }
    }
  });

  // 连接WebSocket
  const handleConnect = () => {
    connect();
  };

  // 订阅行情数据
  useEffect(() => {
    if (isConnected) {
      subscribe('ticker.all.1s'); // 订阅所有ticker 1秒数据
      subscribe('depth.BTCUSD.20'); // 订阅BTC深度数据
      subscribe('kline.ETHUSD.1m'); // 订阅ETH 1分钟K线
    }
  }, [isConnected]);

  return (
    <div>
      <p>状态: {wsStatus}</p>
      <button onClick={handleConnect} disabled={isConnected}>
        连接市场数据
      </button>
    </div>
  );
}

2. 私有 WebSocket (用户数据)

用于获取持仓、订单、账户余额等私有数据,需要 API 签名认证。

import { usePrivateWebSocket } from './hooks/usePrivateWebSocket';

function UserData({ apiOutput, l2Output }) {
  const {
    wsStatus,
    connect,
    disconnect,
    isConnected
  } = usePrivateWebSocket({
    apiOutput,  // API密钥
    l2Output,   // L2密钥对
    wsHost: 'wss://quote-testnet.edgex.exchange',
    onMessage: (data) => {
      console.log('收到用户数据:', data);

      // 处理用户数据更新
      if (data.type === 'trade-event' || data.type === 'assets-event') {
        const { collateral, position, order } = data.content?.data || {};

        // 更新资产信息
        if (collateral) {
          updateCollateralInfo(collateral);
        }

        // 更新持仓信息
        if (position) {
          updatePositionInfo(position);
        }

        // 更新订单信息
        if (order) {
          updateOrderInfo(order);
        }
      }
    }
  });

  return (
    <div>
      <p>状态: {wsStatus}</p>
      <button
        onClick={connect}
        disabled={isConnected || !apiOutput || !l2Output}
      >
        连接用户数据
      </button>
    </div>
  );
}

3. WebSocket 消息类型

公有消息类型

  • ticker - 行情数据
  • depth - 深度数据
  • kline - K线数据
  • trade - 成交数据

私有消息类型

  • trade-event - 交易事件 (订单成交、持仓变化)
  • assets-event - 资产事件 (余额变化)
  • order-event - 订单事件 (订单状态变化)

4. 订阅频道格式

// 行情订阅
subscribe('ticker.all.1s');        // 所有合约1秒ticker
subscribe('ticker.BTCUSD.1s');     // BTC合约1秒ticker

// 深度订阅
subscribe('depth.BTCUSD.20');      // BTC 20档深度
subscribe('depth.ETHUSD.50');      // ETH 50档深度

// K线订阅
subscribe('kline.BTCUSD.1m');      // BTC 1分钟K线
subscribe('kline.ETHUSD.5m');      // ETH 5分钟K线

ETH 链提现

EdgeX 的普通(慢速)提现会把资金直接打回以太坊主链或测试网,接口为 /api/v1/private/assets/createNormalWithdrawEdgeXSDK.generateETHWithdrawParams 默认就是针对这个接口构造 L2 签名和认证头,流程如下:

1. 构造 ETHWithdrawInput

import { ETHWithdrawInput, generateRandomClientId } from '@edgex/typescript-sdk';

const withdrawInput: ETHWithdrawInput = {
  accountId: currentAccount.accountId,      // 账户 ID
  coinId: metadata.multiChain.coinId,       // 例如 1000 = USDT
  amount: '100.0',                          // 提现金额(字符串)
  ethAddress: currentAccount.ethAddress,    // 目标 L1 地址
  clientWithdrawId: generateRandomClientId(),
  expireTime: (
    Math.ceil(Date.now() / 3600000) * 3600000 + 14 * 24 * 60 * 60 * 1000
  ).toString(), // 向上取整至整点 + 14 天
};

clientWithdrawId 必须全局唯一,可使用 SDK 自带的 generateRandomClientId() 或自定义 UUID。

2. 生成 L2 签名与认证头

async function createETHWithdraw(withdrawInput: ETHWithdrawInput, networkId: number) {
  // networkId: 1 = Mainnet, 5 = Goerli, 11155111 = Sepolia
  return sdk.generateETHWithdrawParams(withdrawInput, networkId);
  // 返回 { headers, params: { ...withdrawInput, l2Signature } }
}

如需自定义路由,可使用 sdk.generateETHWithdrawParams(input, networkId, '/api/v1/private/assets/createNormalWithdraw') 显式指定 requestPath

3. 调用 createNormalWithdraw

import { apiClient } from './api/client';

async function executeETHWithdraw(input: ETHWithdrawInput, networkId: number) {
  const { headers, params } = await createETHWithdraw(input, networkId);

  const response = await apiClient.post(
    '/api/v1/private/assets/createNormalWithdraw',
    params,
    { headers },
  );

  return response.data; // data.withdrawId 可用于状态查询
}

4. 支持的网络

| Network ID | 网络名称 | 说明 | |------------|----------|------| | 1 | Ethereum Mainnet | 以太坊主网 | | 5 | Goerli Testnet | Goerli 测试网 | | 11155111 | Sepolia Testnet | Sepolia 测试网 |

5. 提现状态查询

createNormalWithdraw 返回的 withdrawId 可通过 /api/v1/private/assets/getNormalWithdrawById 查询状态:

async function getNormalWithdrawStatus(accountId: string, withdrawId: string) {
  const params = { accountId, normalWithdrawIdList: withdrawId };
  const headers = sdk.createAuthHeaders({
    method: 'GET',
    path: '/api/v1/private/assets/getNormalWithdrawById',
    params,
  });

  const response = await apiClient.get(
    '/api/v1/private/assets/getNormalWithdrawById',
    { headers, params },
  );

  return response.data;
}

快速提现

快速提现依赖 LP 在 L1 垫付资金,需要提前查询 LP 公钥、Fact 合约地址以及费用。前端必须按照以下步骤调用 /api/v1/private/assets/createFastWithdraw

1. 查询快速提现签名信息

import { apiClient } from './api/client';

async function getFastWithdrawSignInfo(params: {
  chainId: number;
  amount: string;
  tokenAddress?: string;
}) {
  const headers = sdk.createAuthHeaders({
    method: 'GET',
    path: '/api/v1/private/assets/getFastWithdrawSignInfo',
    params,
  });

  const response = await apiClient.get(
    '/api/v1/private/assets/getFastWithdrawSignInfo',
    { headers, params },
  );

  return response.data.data; // fee / lpAccountId / fastWithdrawL2Key / fastWithdrawFactRegisterAddress / fastWithdrawMaxAmount
}
  • chainId:目标 L1 链 ID。
  • amount:用户希望提现的数量(未扣手续费)。
  • tokenAddress:目标代币的 L1 合约地址,原生资产可留空。

amount ≥ fastWithdrawMaxAmount 必须提示用户重新输入。

2. 构造并签名条件转账

import BigNumber from 'bignumber.js';
import { SignableConditionalTransfer } from '@edgex/typescript-sdk/starkex-lib';
import { generateRandomClientId } from '@edgex/typescript-sdk';
import { encodePacked, keccak256, parseUnits, sha256, stringToHex } from 'viem';

function buildFastWithdrawFact(opts: {
  recipient: string;
  tokenAddress: string;
  decimals: number;
  humanAmount: string;
  salt: bigint;
}) {
  return keccak256(
    encodePacked(
      ['address', 'uint256', 'address', 'uint256'],
      [
        opts.recipient,
        parseUnits(opts.humanAmount, opts.decimals),
        opts.tokenAddress,
        opts.salt,
      ],
    ),
  );
}

async function createFastWithdrawPayload({
  accountId,
  coinId,
  ethAddress,
  token,
  amount,
  chainId,
}: {
  accountId: string;
  coinId: string;
  ethAddress: string;
  token: { tokenAddress: string; decimals: number };
  amount: string;
  chainId: number;
}) {
  const info = await getFastWithdrawSignInfo({
    chainId,
    amount,
    tokenAddress: token.tokenAddress,
  });

  if (BigNumber(amount).gte(info.fastWithdrawMaxAmount)) {
    throw new Error('超过快速提现限额');
  }

  const clientFastWithdrawId = generateRandomClientId();
  const expireTime =
    Math.ceil(Date.now() / 3600000) * 3600000 + 14 * 24 * 60 * 60 * 1000;
  const salt = BigInt(sha256(stringToHex(clientFastWithdrawId)));
  const fact = buildFastWithdrawFact({
    recipient: ethAddress,
    tokenAddress: token.tokenAddress,
    decimals: token.decimals,
    humanAmount: amount,
    salt,
  });

  const debitAmount = BigNumber(amount).plus(info.fee).toString();
  const transferToSign = {
    senderPositionId: accountId,
    receiverPositionId: info.lpAccountId,
    receiverPublicKey: info.fastWithdrawL2Key,
    factRegistryAddress: info.fastWithdrawFactRegisterAddress,
    fact,
    humanAmount: debitAmount,
    clientId: clientFastWithdrawId,
    expirationIsoTimestamp: `${expireTime}`,
  };

  const l2KeyPair = sdk.getL2KeyPair();
  if (!l2KeyPair) throw new Error('请先设置 L2 密钥对');

  const signable = SignableConditionalTransfer.fromTransfer(transferToSign, chainId);
  const l2Signature = await signable.sign(l2KeyPair.l2PrivateKey);

  const requestBody = {
    accountId,
    coinId,
    amount,
    ethAddress,
    erc20Address: token.tokenAddress,
    lpAccountId: info.lpAccountId,
    clientFastWithdrawId,
    expireTime: `${expireTime}`,
    l2Signature,
    fee: info.fee,
    chainId,
    factRegistryAddress: info.fastWithdrawFactRegisterAddress,
    fact,
  };

  const headers = sdk.createAuthHeaders({
    method: 'POST',
    path: '/api/v1/private/assets/createFastWithdraw',
    body: requestBody,
  });

  return { headers, params: requestBody };
}

关键字段说明:

  • amount:用户期望收到的数量;LP 会额外扣除 fee,因此签名时需要 debitAmount = amount + fee
  • fact:对 (recipient, amountInWei, tokenAddress, salt) 执行 Solidity keccak256 的结果,必须与 LP Fact 合约匹配。
  • lpAccountId / fastWithdrawL2Key / factRegistryAddress:全部来自第一步的接口响应。

3. 提交快速提现

async function executeFastWithdraw(options: {
  accountId: string;
  coinId: string;
  ethAddress: string;
  token: { tokenAddress: string; decimals: number };
  amount: string;
  chainId: number;
}) {
  const { headers, params } = await createFastWithdrawPayload(options);

  const response = await apiClient.post(
    '/api/v1/private/assets/createFastWithdraw',
    params,
    { headers },
  );

  return response.data; // data.fastWithdrawId
}

4. 快速提现状态查询

async function getFastWithdrawStatus(accountId: string, fastWithdrawId: string) {
  const params = { accountId, fastWithdrawIdList: fastWithdrawId };
  const headers = sdk.createAuthHeaders({
    method: 'GET',
    path: '/api/v1/private/assets/getFastWithdrawById',
    params,
  });

  const response = await apiClient.get(
    '/api/v1/private/assets/getFastWithdrawById',
    { headers, params },
  );

  return response.data;
}

跨链提现

跨链提现(速度中等)会通过 LP 把资金从 EdgeX L2 转移到其他 L1,接口 /api/v1/private/assets/createCrossWithdraw 需要多个参数来自 getCrossWithdrawSignInfo,并且提交的 amount 需要先扣除手续费。

1. 查询跨链提现签名信息

async function getCrossWithdrawSignInfo(params: {
  chainId: number;
  amount: string;
  tokenAddress?: string;
}) {
  const headers = sdk.createAuthHeaders({
    method: 'GET',
    path: '/api/v1/private/assets/getCrossWithdrawSignInfo',
    params,
  });

  const response = await apiClient.get(
    '/api/v1/private/assets/getCrossWithdrawSignInfo',
    { headers, params },
  );

  return response.data.data; // fee / lpAccountId / crossWithdrawL2Key / crossWithdrawMaxAmount
}

与快速提现类似,amount 不能超过 crossWithdrawMaxAmount

2. 生成跨链提现参数

import BigNumber from 'bignumber.js';
import { CrossWithdrawInput, generateRandomClientId } from '@edgex/typescript-sdk';

async function buildCrossWithdrawInput({
  accountId,
  coinId,
  chainId,
  amount,
  tokenAddress,
  receiverAddress,
}: {
  accountId: string;
  coinId: string;
  chainId: number;
  amount: string;
  tokenAddress: string;
  receiverAddress: string;
}) {
  const info = await getCrossWithdrawSignInfo({
    chainId,
    amount,
    tokenAddress,
  });

  if (BigNumber(amount).gte(info.crossWithdrawMaxAmount)) {
    throw new Error('超过跨链提现限额');
  }

  const clientCrossWithdrawId = generateRandomClientId();
  const expireTime =
    Math.ceil(Date.now() / 3600000) * 3600000 + 14 * 24 * 60 * 60 * 1000;

  const crossWithdrawInput: CrossWithdrawInput = {
    accountId,
    coinId,
    amount: BigNumber(amount).minus(info.fee).toString(), // 注意:真正提交的 amount 已扣手续费
    ethAddress: receiverAddress,
    erc20Address: tokenAddress,
    lpAccountId: info.lpAccountId,
    clientCrossWithdrawId,
    expireTime: `${expireTime}`,
    fee: info.fee,
    chainId,
    crossWithdrawL2Key: info.crossWithdrawL2Key,
  };

  return crossWithdrawInput;
}

3. 计算签名并发送 createCrossWithdraw

import { apiClient } from './api/client';

async function executeCrossWithdraw(options: {
  accountId: string;
  coinId: string;
  chainId: number;
  amount: string;
  tokenAddress: string;
  receiverAddress: string;
  extraAaPayload?: {
    mpcAddress: string;
    mpcSignature: string;
    mpcSignTime: string;
  };
}) {
  const input = await buildCrossWithdrawInput(options);

  const { headers, params } = await sdk.generateCrossWithdrawParams(
    input,
    options.chainId,
  );

  const { crossWithdrawL2Key, ...body } = params; // API 不接受该字段

  const response = await apiClient.post(
    '/api/v1/private/assets/createCrossWithdraw',
    {
      ...body,
      ...(options.extraAaPayload ?? {}),
    },
    { headers },
  );

  return response.data; // data.crossWithdrawId
}

4. MPC / AA 提现附加字段

当链配置 allowAaWithdraw = true 且当前钱包类型为 MPC,需要额外携带:

  • mpcAddress:MPC 智能钱包地址。
  • mpcSignTime:秒级 Unix 时间戳。
  • mpcSignature:对 receiverAddress + (amount-after-fee 按 decimals 转 uint256) + tokenAddress + mpcSignTime + chainId 做 keccak256 后使用 MPC 钱包 signMessage 的结果。
  • ethAddress:最终实际接收地址(会覆盖上面 body 中的 ethAddress)。

计算方式可直接参考 apps/web/src/modules/trade/components/withdraw/useWithdraw.ts 中的实现(normalizeHex32bnToHex32signMessage 等 helper)。

5. 跨链提现状态查询

async function getCrossWithdrawStatus(accountId: string, crossWithdrawId: string) {
  const params = { accountId, crossWithdrawIdList: crossWithdrawId };
  const headers = sdk.createAuthHeaders({
    method: 'GET',
    path: '/api/v1/private/assets/getCrossWithdrawById',
    params,
  });

  const response = await apiClient.get(
    '/api/v1/private/assets/getCrossWithdrawById',
    { headers, params },
  );

  return response.data;
}

6. 获取链配置信息

function getChainConfig(metadata: any, chainId: string) {
  const multiChain = metadata.multiChain;
  return multiChain.chainList.find((chain: any) => chain.chainId === chainId);
}

const chainConfig = getChainConfig(metadata, '97'); // BSC testnet
console.log('链配置:', {
  chain: chainConfig.chain,
  contractAddress: chainConfig.contractAddress,
  rpcUrl: chainConfig.rpcUrl,
  tokenList: chainConfig.tokenList,
});

示例链配置:

| Chain ID | 网络名称 | 代币示例 | |----------|----------|----------| | 97 | BSC Testnet | USDT, BUSD | | 56 | BSC Mainnet | USDT, BUSD | | 421614 | Arbitrum Sepolia | USDT, USDC | | 42161 | Arbitrum One | USDT, USDC |


完整示例

以下是一个完整的 React 应用示例,展示了如何使用 EdgeX TypeScript SDK 的所有主要功能。

1. 主应用组件

import React, { useCallback, useEffect, useMemo, useState } from "react";
import { usePrivy, useWallets } from "@privy-io/react-auth";
import { useAccount, useChainId } from "wagmi";
import { createWalletClient, custom, getAddress } from "viem";
import { EdgeXSDK } from "@edgex/typescript-sdk";
import { useMetadata } from "./hooks/useMetadata";
import { usePublicWebSocket } from "./hooks/usePublicWebSocket";
import { usePrivateWebSocket } from "./hooks/usePrivateWebSocket";

export function App() {
  const { authenticated, login, logout, user } = usePrivy();
  const { wallets } = useWallets();
  const { address } = useAccount();
  const chainId = useChainId();

  const [apiOutput, setApiOutput] = useState<any | null>(null);
  const [l2Output, setL2Output] = useState<any | null>(null);

  // 获取metadata配置
  const { config: metaConfig, meta } = useMetadata();
  const edgeXSDK = useMemo(() => {
    return EdgeXSDK.getInstance({
      clientId: "client_001",
    });
  }, []);

  useEffect(() => {
    if (meta) {
      edgeXSDK.setMetadata(meta);
    }
  }, [meta]);

  // 公有WebSocket连接 (市场数据)
  const {
    wsStatus: publicWsStatus,
    connect: publicWsConnect,
    subscribe: publicWsSubscribe,
  } = usePublicWebSocket({
    onMessage: (data) => {
      console.log("收到市场数据:", data);
    },
  });

  // 私有WebSocket连接 (用户数据)
  const {
    wsStatus: privateWsStatus,
    connect: privateWsConnect,
  } = usePrivateWebSocket({
    apiOutput,
    l2Output,
    onMessage: (data) => {
      console.log("收到用户数据:", data);
    },
  });

  // 生成API密钥
  const generateApiKey = useCallback(async () => {
    try {
      const effectiveAddress = address ?? (user?.wallet?.address as string);
      if (!effectiveAddress) throw new Error("钱包未连接");

      const privyWallet = wallets.find(
        w => w.address.toLowerCase() === effectiveAddress.toLowerCase()
      );
      if (!privyWallet) throw new Error("未找到钱包");

      const walletClient = createWalletClient({
        account: getAddress(effectiveAddress),
        chain: currentChain,
        transport: custom({
          async request({ method, params }) {
            const provider = await privyWallet.getEthereumProvider();
            return await provider.request({ method, params });
          },
        }),
      });

      const message = `action: ${metaConfig.appName} Onboard\nonlySignOn: ${metaConfig.onlySignOn}`;
      const signature = await walletClient.signMessage({
        account: getAddress(effectiveAddress),
        message,
      });

      const result = edgeXSDK.generateApiKeyFromSignature(signature);
      setApiOutput(result);
      edgeXSDK.setApiKeys(result);
    } catch (error) {
      console.error("生成API密钥失败:", error);
    }
  }, [address, user, wallets, metaConfig]);

  // 生成L2密钥对
  const generateL2KeyPair = useCallback(async () => {
    try {
      const effectiveAddress = address ?? (user?.wallet?.address as string);
      if (!effectiveAddress) throw new Error("钱包未连接");

      const privyWallet = wallets.find(
        w => w.address.toLowerCase() === effectiveAddress.toLowerCase()
      );
      if (!privyWallet) throw new Error("未找到钱包");

      const walletClient = createWalletClient({
        account: getAddress(effectiveAddress),
        chain: currentChain,
        transport: custom({
          async request({ method, params }) {
            const provider = await privyWallet.getEthereumProvider();
            return await provider.request({ method, params });
          },
        }),
      });

      const clientAccountId = "main";
      const message = `name: ${metaConfig.appName}\nenvId: ${metaConfig.appEnv}\naction: L2 Key\nonlySignOn: ${metaConfig.onlySignOn}\nclientAccountId: ${clientAccountId}`;

      const signature = await walletClient.signMessage({
        account: getAddress(effectiveAddress),
        message,
      });

      const result = edgeXSDK.generateL2KeyPairFromSignature(signature);
      setL2Output(result);
      edgeXSDK.setL2KeyPair(result);
    } catch (error) {
      console.error("生成L2密钥失败:", error);
    }
  }, [address, user, wallets, metaConfig]);

  // 自动订阅市场数据
  useEffect(() => {
    if (publicWsStatus === "CONNECTED") {
      publicWsSubscribe("ticker.all.1s");
    }
  }, [publicWsStatus, publicWsSubscribe]);

  return (
    <div style={{ padding: 24, fontFamily: "ui-sans-serif, system-ui" }}>
      <h1>EdgeX SDK 完整示例</h1>

      {!authenticated ? (
        <button onClick={login}>连接钱包</button>
      ) : (
        <div>
          <p>地址: {address || user?.wallet?.address}</p>

          <div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
            <button onClick={generateApiKey}>生成API密钥</button>
            <button onClick={generateL2KeyPair}>生成L2密钥</button>
            <button onClick={logout}>登出</button>
          </div>

          <div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
            <button onClick={publicWsConnect}>连接市场数据</button>
            <button onClick={privateWsConnect}>连接用户数据</button>
          </div>

          {apiOutput && (
            <div>
              <h3>API密钥</h3>
              <pre>{JSON.stringify(apiOutput, null, 2)}</pre>
            </div>
          )}

          {l2Output && (
            <div>
              <h3>L2密钥对</h3>
              <pre>{JSON.stringify(l2Output, null, 2)}</pre>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

2. 交易示例

import { TradeOrderParams } from '@edgex/typescript-sdk';
import { apiClient } from './api/client';

// 创建限价买单
async function createBuyOrder() {
  const tradeParams: TradeOrderParams = {
    price: '30000.0',
    size: '0.001',
    type: 'LIMIT',
    timeInForce: 'GOOD_TIL_CANCEL',
    reduceOnly: false,
    isPositionTpsl: false,
    isSetOpenTp: false,
    isSetOpenSl: false,
    contractId: '10000001', // BTCUSD
    side: 'BUY',
    triggerPrice: '',
    triggerPriceType: 'LAST_PRICE',
    extraType: '',
    extraDataJson: '',
    accountId: 'your-account-id',
  };

  const { headers, params } = await sdk.generateTradeParams({
    tradeParams,
    symbolInfo: {
      contractId: '10000001',
      symbol: 'BTCUSD',
      contractName: 'BTCUSD',
      oraclePrice: '30000.0',
      tickSize: '0.1',
      takerFeeRate: '0.00038',
      makerFeeRate: '0.00015',
    },
    chainInfo: { chainId: '11155111' },
    accountInfo: { accountId: 'your-account-id' },
    requestPath: '/api/v1/private/order/createOrder',
    requestMethod: 'POST',
  });

  const response = await apiClient.post('/api/v1/private/order/createOrder', params, {
    headers,
  });

  return response.data;
}

3. 提现示例

// ETH链提现
async function withdrawToETH() {
  const withdrawInput = {
    accountId: 'your-account-id',
    coinId: '1000',
    amount: '100.0',
    ethAddress: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
    clientWithdrawId: Date.now().toString(),
    expireTime: (Date.now() + 24 * 60 * 60 * 1000).toString(),
  };

  const { headers, params } = await sdk.generateETHWithdrawParams(withdrawInput, 11155111);

  const response = await apiClient.post('/api/v1/private/withdraw/eth', params, {
    headers,
  });

  return response.data;
}

// 跨链提现到BSC
async function withdrawToBSC() {
  const crossWithdrawInput = {
    accountId: 'your-account-id',
    coinId: '1000',
    amount: '50.0',
    ethAddress: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
    erc20Address: '0xda6c748A7593826e410183F05893dbB363D025a1',
    lpAccountId: '642774866310726088',
    clientCrossWithdrawId: Date.now().toString(),
    expireTime: (Date.now() + 24 * 60 * 60 * 1000).toString(),
    fee: '1.0',
    chainId: 97,
    crossWithdrawL2Key: '0x06038e1248a1caea0e9c3d7f61e0a00fb928f6775a74d8ac35845cfa44eecb55',
  };

  const { headers, params } = await sdk.generateCrossWithdrawParams(crossWithdrawInput, 97);

  const response = await apiClient.post('/api/v1/private/withdraw/cross', params, {
    headers,
  });

  return response.data;
}

错误处理

常见错误类型

  1. 认证错误 - API密钥无效或过期
  2. 签名错误 - L2签名验证失败
  3. 参数错误 - 请求参数格式不正确
  4. 网络错误 - WebSocket连接失败
  5. 余额不足 - 账户余额不足以完成操作

错误处理示例

try {
  const result = await createBuyOrder();
  console.log('订单创建成功:', result);
} catch (error) {
  if (error.response?.status === 401) {
    console.error('认证失败,请重新生成API密钥');
  } else if (error.response?.status === 400) {
    console.error('参数错误:', error.response.data.message);
  } else {
    console.error('未知错误:', error.message);
  }
}

更多资源