RISE Logo-Light

Creating Permit Params

How to create permit params for authenticated API calls

Creating Permit Params

After registering a signer, you need to create permit params for each API call. Permit params include a signature from your signer that authorizes the transaction.

Step 1: Encode Contract Data

First, encode the contract data according to your operation. There are 3 different encoding methods depending on the API endpoint:

Method 1: Place Order Encoding (using encodePacked)

For place order operations, use packed encoding with a specific binary layout (47 bytes total):

import { encodePacked, type Hex } from 'viem';

// Enum values
enum OrderSide {
  Long = 0,
  Short = 1,
}

enum STPMode {
  ExpireMaker = 0,
  ExpireTaker = 1,
  ExpireBoth = 2,
  None = 3,
}

enum OrderType {
  Market = 0,
  Limit = 1,
}

enum TimeInForce {
  GoodTillCancelled = 0,
  GoodTillTime = 1,
  FillOrKill = 2,
  ImmediateOrCancel = 3,
}

interface EncodePlaceOrderParams {
  marketId: string;
  size: bigint; // uint128
  price: bigint; // uint128
  side: OrderSide; // 0 = Long/Buy, 1 = Short/Sell
  stpMode: STPMode; // 0-3
  orderType: OrderType; // 0 = Market, 1 = Limit
  postOnly: boolean;
  reduceOnly: boolean;
  timeInForce?: TimeInForce; // Default: GoodTillCancelled (0)
  expiry: number; // uint32, Unix timestamp
}

const encodePlaceOrderData = ({
  marketId,
  size,
  price,
  side,
  stpMode,
  orderType,
  postOnly,
  reduceOnly,
  timeInForce = TimeInForce.GoodTillCancelled,
  expiry,
}: EncodePlaceOrderParams): Hex => {
  // Pack flags into a single uint8 byte:
  // bit 0: side (0 = Long/Buy, 1 = Short/Sell)
  // bit 1: postOnly
  // bit 2: reduceOnly
  // bits 3-4: stpMode (2 bits)
  // bits 5-7: unused
  let flags = 0;
  if (side === OrderSide.Short) flags |= 0x01; // bit 0
  if (postOnly) flags |= 0x02; // bit 1
  if (reduceOnly) flags |= 0x04; // bit 2
  flags |= stpMode << 3; // bits 3-4

  // Binary layout (47 bytes total):
  // bytes[0:8]    - marketId (uint64, 8 bytes)
  // bytes[8:24]   - size (uint128, 16 bytes)
  // bytes[24:40]  - price (uint128, 16 bytes)
  // bytes[40]     - flags (uint8, 1 byte)
  // bytes[41]     - orderType (uint8, 1 byte)
  // bytes[42]     - timeInForce (uint8, 1 byte)
  // bytes[43:47]  - expiry (uint32, 4 bytes)
  return encodePacked(
    ['uint64', 'uint128', 'uint128', 'uint8', 'uint8', 'uint8', 'uint32'],
    [BigInt(marketId), size, price, flags, orderType, timeInForce, expiry],
  );
};

// Example usage:
const encodedData = encodePlaceOrderData({
  marketId: '1',
  size: BigInt('1000000000000000000'), // 1 token with 18 decimals
  price: BigInt('2000000000000000000000'), // 2000 with 18 decimals
  side: OrderSide.Long,
  stpMode: STPMode.None,
  orderType: OrderType.Limit,
  postOnly: false,
  reduceOnly: false,
  timeInForce: TimeInForce.GoodTillCancelled,
  expiry: Math.floor(Date.now() / 1000) + 86400, // 24 hours from now
});

Method 2: Cancel Order Encoding (using encodePacked with manual concatenation)

For cancel order operations, use packed encoding with manual concatenation for uint192 (since viem doesn't support uint192 directly):

import { encodePacked, type Hex } from 'viem';

interface EncodeCancelOrderParams {
  marketId: string; // uint64
  orderId: bigint; // uint192 (24 bytes)
}

const encodeCancelOrderData = ({ marketId, orderId }: EncodeCancelOrderParams): Hex => {
  // Binary layout (32 bytes total):
  // bytes[0:8]    - marketId (uint64, 8 bytes)
  // bytes[8:32]   - orderId (uint192, 24 bytes)
  
  // Encode marketId as uint64
  const marketIdBytes = encodePacked(['uint64'], [BigInt(marketId)]);
  
  // Convert orderId to 24-byte hex string (uint192)
  // 24 bytes = 48 hex characters
  const orderIdHex = orderId.toString(16).padStart(48, '0');
  
  // Concatenate: marketId (8 bytes) + orderId (24 bytes) = 32 bytes
  // Remove 0x prefix from marketIdBytes and prepend 0x to the final result
  return `0x${marketIdBytes.slice(2)}${orderIdHex}` as Hex;
};

// Example usage:
const encodedData = encodeCancelOrderData({
  marketId: '1',
  orderId: BigInt('123456789012345678901234567890123456789012345678'),
});

Method 3: Other APIs Encoding (using encodeAbiParameters)

For all other APIs (update leverage, update margin mode, update isolated margin, etc.), use ABI encoding which matches Solidity's abi.encode:

import { encodeAbiParameters, type Hex } from 'viem';

// Example 1: Update Leverage
interface EncodeUpdateLeverageParams {
  marketId: string; // uint256
  leverage: bigint; // uint128
}

const encodeUpdateLeverageData = ({ marketId, leverage }: EncodeUpdateLeverageParams): Hex => {
  return encodeAbiParameters(
    [
      { name: 'marketId', type: 'uint256' },
      { name: 'leverage', type: 'uint128' },
    ],
    [BigInt(marketId), leverage],
  );
};

// Example 2: Update Margin Mode
interface EncodeUpdateMarginModeParams {
  marketId: string; // uint256
  marginMode: number; // uint8 (0 = Cross, 1 = Isolated)
}

const encodeUpdateMarginModeData = ({ marketId, marginMode }: EncodeUpdateMarginModeParams): Hex => {
  return encodeAbiParameters(
    [
      { name: 'marketId', type: 'uint256' },
      { name: 'marginMode', type: 'uint8' },
    ],
    [BigInt(marketId), marginMode],
  );
};

// Example 3: Update Isolated Position Margin Balance
interface EncodeUpdateIsolatedPositionMarginBalanceParams {
  marketId: string; // uint256
  amount: bigint; // int256 (positive for add, negative for remove)
}

const encodeUpdateIsolatedPositionMarginBalanceData = ({
  marketId,
  amount,
}: EncodeUpdateIsolatedPositionMarginBalanceParams): Hex => {
  return encodeAbiParameters(
    [
      { name: 'marketId', type: 'uint256' },
      { name: 'amount', type: 'int256' },
    ],
    [BigInt(marketId), amount],
  );
};

// Example usage:
const leverageEncoded = encodeUpdateLeverageData({
  marketId: '1',
  leverage: BigInt('10'), // 10x leverage (plain value, no 18 decimals)
});

const marginModeEncoded = encodeUpdateMarginModeData({
  marketId: '1',
  marginMode: 1, // Isolated margin mode
});

const isolatedMarginEncoded = encodeUpdateIsolatedPositionMarginBalanceData({
  marketId: '1',
  amount: BigInt('1000000000000000000'), // Add 1 token (positive)
  // amount: BigInt('-1000000000000000000'), // Remove 1 token (negative)
});

Summary

  • Place Order: Use encodePacked with specific 47-byte layout
  • Cancel Order: Use encodePacked with manual concatenation for uint192
  • All Other APIs: Use encodeAbiParameters matching Solidity's abi.encode(params)

Step 2: Sign Encoded Data and Create Permit Params

const createPermitParams = async (
  encodedData: Hex,
  signingKey: `0x${string}`, // The signing key from registerSigner
  account: `0x${string}`,
  signerAddress: `0x${string}`, // The signer address from registerSigner
  perpContractAddress: `0x${string}`,
  authContractAddress: `0x${string}`,
) => {
  const signerAccount = privateKeyToAccount(signingKey);

  // Hash the encoded data before signing
  const messageHash = keccak256(encodedData);

  // Create nonce
  const nonce = createClientNonce(account);

  // Set deadline (typically 7 days from now, in seconds)
  const deadline = dayjs().add(7, 'day').unix();

  // Sign the hash with signer's private key using EIP-712
  const domain = getRISExDomain(authContractAddress);
  const signature = await signerAccount.signTypedData({
    domain: domain as any,
    types: { VerifySignature: VERIFY_SIGNATURE_TYPES.VerifySignature },
    message: {
      account,
      target: perpContractAddress,
      hash: messageHash,
      nonce: BigInt(nonce),
      deadline: BigInt(deadline),
    },
    primaryType: 'VerifySignature',
  });

  return {
    account: account,
    signer: signerAddress,
    deadline: deadline.toString(),
    signature: signature,
    nonce: nonce,
  };
};

Step 3: Use Permit Params in API Calls

// Example: Place order with permit params
const placeOrderWithPermit = async (
  orderParams: {
    market_id: string;
    size: string;
    price: string;
    side: string;
    stp_mode: string;
    order_type: string;
    post_only: boolean;
    reduce_only: boolean;
    tif: string;
    expiry: number;
  },
  signingKey: `0x${string}`,
  signerAddress: `0x${string}`,
  account: `0x${string}`,
) => {
  try {
    // Step 1: Get contract addresses from system config or check "Contract Address" section
    const configResponse = await apiClient.get('/v1/system/config');
    const perpContractAddress = configResponse.data.addresses_config.perp;
    const authContractAddress = configResponse.data.addresses_config.auth;

    // Step 2: Encode order data (implementation depends on your contract ABI)
    // const encodedData = encodePlaceOrderData(orderParams);

    // Step 3: Create permit params
    // const permitParams = await createPermitParams(
    //   encodedData,
    //   signingKey,
    //   account,
    //   signerAddress,
    //   perpContractAddress,
    //   authContractAddress,
    // );

    // Step 4: Make API call with permit params
    const response = await apiClient.post('/v1/orders/place', {
      order_params: orderParams,
      permit_params: {
        account: account,
        signer: signerAddress,
        deadline: dayjs().add(7, 'day').unix().toString(),
        signature: '0x...', // From createPermitParams
        nonce: '1234567890', // From createPermitParams
      },
    });

    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.error('Order failed:', error.response?.data?.message || error.message);
    } else {
      console.error('Order failed:', error);
    }
    throw error;
  }
};

Important Notes

  • Required for all authenticated calls: Every API call that modifies state needs permit params
  • Unique nonce: Each permit param must have a unique nonce (use createClientNonce)
  • Deadline: Typically set to 7 days from now
  • Signature: Must sign the encoded contract data hash with the signer's private key