Skip to content

Commit ac67184

Browse files
authored
Merge pull request #250 from Polymarket/feature/dev-424-lift-exchange-v3-order-construction
refactor(client): lift Exchange v3 order construction into the exchange module
2 parents cd30173 + 91b5c7d commit ac67184

3 files changed

Lines changed: 99 additions & 51 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@polymarket/client": patch
3+
---
4+
5+
Internal refactor: Exchange v3 order construction helpers now live in the exchange module. No behavior change.

packages/client/src/exchange.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,70 @@ export function createExchangeOrderSignature(
163163
);
164164
}
165165

166+
/** @internal */
167+
export type CreateExchangeV3OrderDomainParams = {
168+
chainId: number;
169+
exchange: EvmAddress;
170+
};
171+
172+
/** @internal */
173+
export function createExchangeV3OrderDomain(
174+
params: CreateExchangeV3OrderDomainParams,
175+
): ExchangeOrderDomain {
176+
return {
177+
chainId: params.chainId,
178+
exchange: params.exchange,
179+
protocolVersion: ExchangeOrderProtocolVersion.V3,
180+
};
181+
}
182+
183+
/**
184+
* Amount the order owner gives, in e6 base units: `price * size` collateral
185+
* rounded up for a BUY, the outcome-token `size` for a SELL. `price` and
186+
* `size` are both e6-scaled.
187+
*
188+
* @internal
189+
*/
190+
export function calculateExchangeOrderMakerAmount(
191+
side: OrderSide,
192+
price: bigint,
193+
size: bigint,
194+
): string {
195+
if (side === OrderSide.SELL) return size.toString();
196+
197+
return ((price * size + 999_999n) / 1_000_000n).toString();
198+
}
199+
200+
/**
201+
* Amount the order owner receives, in e6 base units: the outcome-token
202+
* `size` for a BUY, `price * size` collateral rounded down for a SELL.
203+
* Rounding always favors the counterparty. `price` and `size` are both
204+
* e6-scaled.
205+
*
206+
* @internal
207+
*/
208+
export function calculateExchangeOrderTakerAmount(
209+
side: OrderSide,
210+
price: bigint,
211+
size: bigint,
212+
): string {
213+
if (side === OrderSide.SELL) {
214+
return ((price * size) / 1_000_000n).toString();
215+
}
216+
217+
return size.toString();
218+
}
219+
220+
/** @internal */
221+
export function generateExchangeOrderSalt(): bigint {
222+
const bytes = new Uint8Array(8);
223+
globalThis.crypto.getRandomValues(bytes);
224+
225+
return BigInt(
226+
`0x${Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')}`,
227+
);
228+
}
229+
166230
/** @internal */
167231
export function createExchangeOrderMessage(
168232
order: ExchangeOrderInput,
@@ -280,7 +344,8 @@ function protocolVersionHash(
280344
);
281345
}
282346

283-
function encodeExchangeOrderSide(side: OrderSide | 0 | 1): 0 | 1 {
347+
/** @internal */
348+
export function encodeExchangeOrderSide(side: OrderSide | 0 | 1): 0 | 1 {
284349
if (side === 0 || side === 1) return side;
285350

286351
return side === OrderSide.BUY ? 0 : 1;
Lines changed: 28 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
import { OrderSide, type PositionId, toBaseUnits } from '@polymarket/bindings';
1+
import {
2+
type OrderSide,
3+
type PositionId,
4+
toBaseUnits,
5+
} from '@polymarket/bindings';
26
import type { RfqSignedOrder } from '@polymarket/bindings/combos';
37
import type { EvmAddress, EvmSignature, HexString } from '@polymarket/types';
48
import { SigningError } from '../../errors';
59
import {
10+
calculateExchangeOrderMakerAmount,
11+
calculateExchangeOrderTakerAmount,
612
createExchangeOrderSignature,
713
createExchangeOrderTypedDataPayload,
8-
ExchangeOrderProtocolVersion,
14+
createExchangeV3OrderDomain,
15+
encodeExchangeOrderSide,
16+
generateExchangeOrderSalt,
917
} from '../../exchange';
1018
import type { Signer } from '../../types';
1119
import type { AccountIdentity } from '../../wallet';
@@ -29,19 +37,31 @@ export async function signRfqQuoteOrder(
2937
params: SignRfqQuoteOrderParams,
3038
): Promise<RfqSignedOrder> {
3139
const identity = resolveOrderIdentity(params.account);
40+
const domain = createExchangeV3OrderDomain({
41+
chainId: params.chainId,
42+
exchange: params.exchange,
43+
});
3244
const order: Omit<RfqSignedOrder, 'signature'> = {
3345
builder: BYTES32_ZERO,
3446
maker: identity.maker,
3547
makerAmount: toBaseUnits(
36-
makerAmount(params.orderSide, params.orderPrice, params.size),
48+
calculateExchangeOrderMakerAmount(
49+
params.orderSide,
50+
params.orderPrice,
51+
params.size,
52+
),
3753
),
3854
metadata: BYTES32_ZERO,
39-
salt: generateOrderSalt().toString(),
40-
side: encodeOrderSide(params.orderSide),
55+
salt: generateExchangeOrderSalt().toString(),
56+
side: encodeExchangeOrderSide(params.orderSide),
4157
signatureType: identity.signatureType,
4258
signer: identity.signer,
4359
takerAmount: toBaseUnits(
44-
takerAmount(params.orderSide, params.orderPrice, params.size),
60+
calculateExchangeOrderTakerAmount(
61+
params.orderSide,
62+
params.orderPrice,
63+
params.size,
64+
),
4565
),
4666
timestamp: Math.floor(Date.now() / 1000).toString(),
4767
tokenId: params.tokenId,
@@ -50,56 +70,14 @@ export async function signRfqQuoteOrder(
5070
let signature: EvmSignature;
5171
try {
5272
signature = await params.signer.signTypedData(
53-
createExchangeOrderTypedDataPayload({
54-
domain: createRfqExchangeDomain(params),
55-
order,
56-
}),
73+
createExchangeOrderTypedDataPayload({ domain, order }),
5774
);
5875
} catch (error) {
5976
throw SigningError.fromError(error, 'Could not sign the RFQ quote order.');
6077
}
6178

6279
return {
6380
...order,
64-
signature: createExchangeOrderSignature({
65-
domain: createRfqExchangeDomain(params),
66-
order,
67-
signature,
68-
}),
81+
signature: createExchangeOrderSignature({ domain, order, signature }),
6982
};
7083
}
71-
72-
function createRfqExchangeDomain(params: SignRfqQuoteOrderParams) {
73-
return {
74-
chainId: params.chainId,
75-
exchange: params.exchange,
76-
protocolVersion: ExchangeOrderProtocolVersion.V3,
77-
};
78-
}
79-
80-
function makerAmount(side: OrderSide, price: bigint, size: bigint): string {
81-
if (side === OrderSide.SELL) return size.toString();
82-
83-
return ((price * size + 999_999n) / 1_000_000n).toString();
84-
}
85-
86-
function takerAmount(side: OrderSide, price: bigint, size: bigint): string {
87-
if (side === OrderSide.SELL) {
88-
return ((price * size) / 1_000_000n).toString();
89-
}
90-
91-
return size.toString();
92-
}
93-
94-
function encodeOrderSide(side: OrderSide): 0 | 1 {
95-
return side === OrderSide.BUY ? 0 : 1;
96-
}
97-
98-
function generateOrderSalt(): bigint {
99-
const bytes = new Uint8Array(8);
100-
globalThis.crypto.getRandomValues(bytes);
101-
102-
return BigInt(
103-
`0x${Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')}`,
104-
);
105-
}

0 commit comments

Comments
 (0)