EIP-712 주문 서명
Conviction 의 모든 주문은 EIP-712 typed data 로 서명됩니다. 일반 사용자는 UI 가 이 과정을 자동으로 처리하므로 신경 쓸 필요가 없습니다. 이 페이지는 프로그램으로 직접 호가를 올리려는 봇·마켓 메이커 를 위한 참고 자료입니다.
#도메인 (EIP-712 Domain Separator)
{
name: 'Conviction CTF Exchange', // byte-for-byte 일치 필수
version: '1',
chainId: 56, // BSC mainnet
verifyingContract: CTF_EXCHANGE_ADDRESS, // 환경별 주소 다름
}도메인 name 의 한 글자도 틀리면 안 됩니다
typo 가 있어도 ECDSA 자체는 통과하므로 클라이언트 시점에는 정상으로 보입니다. 그러나 컨트랙트 측 도메인과 다르므로 체결 시점에 reverts 됩니다.
verifyingContract 는 BSC 메인넷의 CTFExchange 주소입니다. 서버 응답이나 환경 변수에서 가져오세요 — 클라이언트에 하드코딩하지 마세요.
#Order 구조
| 필드 | 타입 | 의미 |
|---|---|---|
salt | uint256 | 무작위 식별자 (재서명 시마다 새로 생성) |
maker | address | 사용자 주소. EIP-7702 위임으로 EOA 가 곧 AA 이므로 maker == signer. |
signer | address | 사용자의 EOA — maker 와 동일한 주소. |
taker | address | 0x0000... (공개 호가) |
tokenId | uint256 | 결과의 position ID (= outcome 의 position_id) |
makerAmount | uint256 | maker 가 내놓는 자산의 wei 양 |
takerAmount | uint256 | maker 가 받고 싶은 자산의 wei 양 |
expiration | uint256 | 주문 만료 unix 초. 0 = 만료 없음 |
nonce | uint256 | CTFExchange.nonces(maker) 로 매번 fresh |
feeRateBps | uint256 | 수수료 상한 (basis points) |
side | uint8 | BUY = 0, SELL = 1 |
signatureType | uint8 | EOA = 0 (사용자 주문) |
#EIP-712 type 정의
const types = {
Order: [
{ name: 'salt', type: 'uint256' },
{ name: 'maker', type: 'address' },
{ name: 'signer', type: 'address' },
{ name: 'taker', type: 'address' },
{ name: 'tokenId', type: 'uint256' },
{ name: 'makerAmount', type: 'uint256' },
{ name: 'takerAmount', type: 'uint256' },
{ name: 'expiration', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'feeRateBps', type: 'uint256' },
{ name: 'side', type: 'uint8' },
{ name: 'signatureType', type: 'uint8' },
],
};#단일 주문 서명 예시 (viem)
import { parseUnits } from 'viem';
const SHARES = parseUnits('100', 18);
const order = {
salt: BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)),
maker: eoaAddress, // EIP-7702: EOA 가 곧 AA
signer: eoaAddress, // 같은 주소
taker: '0x0000000000000000000000000000000000000000',
tokenId: BigInt(outcome.position_id),
// BUY 100 shares at 0.5 USDT each
makerAmount: parseUnits('50', 18), // 100 × 0.5 = 50 USDT
takerAmount: SHARES, // 100 shares
expiration: BigInt(Math.floor(Date.now() / 1000) + 3600),
nonce: await ctfExchange.read.nonces([eoaAddress]),
feeRateBps: BigInt(market.platformFee),
side: 0n, // BUY
signatureType: 0, // EOA
};
const signature = await walletClient.signTypedData({
account: eoaAddress,
domain,
types: { Order: types.Order },
primaryType: 'Order',
message: order,
});
// POST /orders
await fetch('/api/orders', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
marketId: market.id,
outcomeId: outcome.id,
side: 'buy',
type: 'limit',
price: 0.5,
shares: 100,
userId: user.id,
salt: '0x' + order.salt.toString(16),
signature,
signerAddress: order.signer,
makerAddress: order.maker,
expiration: order.expiration.toString(),
signatureType: 0,
tokenId: order.tokenId.toString(),
makerAmount: order.makerAmount.toString(),
takerAmount: order.takerAmount.toString(),
feeRateBps: market.platformFee,
}),
});#makerAmount / takerAmount 계산 규칙
모든 금액은 18-decimal wei 정수입니다.
| Side | Maker 가 내놓는 것 | Maker 가 받는 것 | makerAmount | takerAmount |
|---|---|---|---|---|
| BUY | USDT | 결과 토큰 | shares × price (USDT wei) | shares (token wei) |
| SELL | 결과 토큰 | USDT | shares (token wei) | shares × price (USDT wei) |
price는 0..1 float 이지만, wei-space 에서는price × 1e18로 곱셈에 사용합니다.- 모든 곱은 bigint 산술 로 — JS
Number금지 (1 USDT =10^18이라 안전한 정수 범위를 넘습니다).
#흔한 실수
이 네 가지를 가장 자주 봅니다
maker와signer에 다른 주소 넣기. EIP-7702 모델에서는 둘 다 사용자의 EOA 주소로 동일해야 합니다.- nonce 캐시. 매번
CTFExchange.nonces(maker)로 fresh fetch 하세요.incrementNonce()가 호출되면 직전 nonce 의 모든 미체결 주문이 reverts 됩니다. - price 를 percent 또는 bps 로 보내기. payload 의
price는 0..1 float 입니다. - makerAmount / takerAmount 를 사람이 읽는 단위로 보내기. 항상 18-dec wei 의 decimal string.
#서명은 체결 시점에 검증됩니다
- 백엔드는 서명을 검증하지 않습니다 — 서명된 페이로드를 그대로 저장하고 체결 시 체인에 넘깁니다.
- 잘못된 서명은 체결 시점에 온체인 ECDSA 복원이 다른 주소를 돌려주어 reverts → 해당 주문이 실패로 마크됩니다.
- 따라서 서명 단계가 정확해야 합니다 — 위의 네 가지 실수를 피하세요.
#다음
- REST → Markets —
feeRateBps등 주문에 필요한 시장 데이터 조회 - 사용자 지갑 — 단일 주소(EOA == AA) 모델
이 페이지가 도움이 되었나요?