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
encodePackedwith specific 47-byte layout - Cancel Order: Use
encodePackedwith manual concatenation for uint192 - All Other APIs: Use
encodeAbiParametersmatching Solidity'sabi.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