-
Notifications
You must be signed in to change notification settings - Fork 18
deposit wallet init #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| import type { JsonRpcSigner } from '@ethersproject/providers'; | ||
| import type { Wallet } from '@ethersproject/wallet'; | ||
| import { | ||
| EIP712_DOMAIN, | ||
| PROTOCOL_NAME, | ||
| } from './exchange.order.const.ts'; | ||
| import { | ||
| PROTOCOL_VERSION_V2, | ||
| ORDER_V2_STRUCTURE, | ||
| } from './exchange.order-v2.const.ts'; | ||
| import type { EIP712TypedData } from './model/eip712.model.ts'; | ||
| import { hashTypedData } from 'viem'; | ||
| import type { | ||
| OrderV2, | ||
| OrderDataV2, | ||
| OrderHashV2, | ||
| OrderSignatureV2, | ||
| SignedOrderV2, | ||
| } from './model/order-v2.model.ts'; | ||
| import { SignatureType } from './model/signature-types.model.ts'; | ||
| import { generateOrderSalt } from './utils.ts'; | ||
|
|
||
| const BYTES32_ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; | ||
|
|
||
| export class ExchangeOrderBuilderV2 { | ||
| constructor( | ||
| private readonly contractAddress: string, | ||
| private readonly chainId: number, | ||
| private readonly signer: Wallet | JsonRpcSigner, | ||
| private readonly generateSalt = generateOrderSalt, | ||
| ) {} | ||
|
|
||
| /** | ||
| * Build an order object including the signature. | ||
| * @param orderData | ||
| * @returns a SignedOrderV2 object (order + signature) | ||
| */ | ||
| async buildSignedOrder(orderData: OrderDataV2): Promise<SignedOrderV2> { | ||
| const order = await this.buildOrder(orderData); | ||
| const orderTypedData = this.buildOrderTypedData(order); | ||
| const orderSignature = await this.buildOrderSignature(orderTypedData); | ||
|
|
||
| return { | ||
| ...order, | ||
| signature: orderSignature, | ||
| } as SignedOrderV2; | ||
| } | ||
|
|
||
| /** | ||
| * Creates an OrderV2 object from order data. | ||
| * @param orderData | ||
| * @returns an OrderV2 object (not signed) | ||
| */ | ||
| async buildOrder({ | ||
| maker, | ||
| tokenId, | ||
| makerAmount, | ||
| takerAmount, | ||
| side, | ||
| signer, | ||
| signatureType, | ||
| timestamp, | ||
| metadata, | ||
| builder, | ||
| expiration, | ||
| }: OrderDataV2): Promise<OrderV2> { | ||
| if (typeof signer == 'undefined' || !signer) { | ||
| signer = maker; | ||
| } | ||
|
|
||
| const resolvedSignatureType = signatureType ?? SignatureType.EOA; | ||
|
|
||
| // For POLY_DEPOSIT_WALLET, the order's signer field is the wallet | ||
| // contract address, while the actual ECDSA signing is done by the EOA owner | ||
| if (resolvedSignatureType !== SignatureType.POLY_DEPOSIT_WALLET) { | ||
| const signerAddress = await this.signer.getAddress(); | ||
| if (signer !== signerAddress) { | ||
| throw new Error('signer does not match'); | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| salt: this.generateSalt(), | ||
| maker, | ||
| signer, | ||
| tokenId, | ||
| makerAmount, | ||
| takerAmount, | ||
| side, | ||
| signatureType: resolvedSignatureType, | ||
| timestamp: timestamp ?? Date.now().toString(), | ||
| metadata: metadata ?? BYTES32_ZERO, | ||
| builder: builder ?? BYTES32_ZERO, | ||
| expiration: expiration ?? '0', | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Parses an OrderV2 object to EIP712 typed data | ||
| * @param order | ||
| * @returns a EIP712TypedData object | ||
| */ | ||
| buildOrderTypedData(order: OrderV2): EIP712TypedData { | ||
| return { | ||
| primaryType: 'Order', | ||
| types: { | ||
| EIP712Domain: EIP712_DOMAIN, | ||
| Order: ORDER_V2_STRUCTURE, | ||
| }, | ||
| domain: { | ||
| name: PROTOCOL_NAME, | ||
| version: PROTOCOL_VERSION_V2, | ||
| chainId: this.chainId, | ||
| verifyingContract: this.contractAddress, | ||
| }, | ||
| message: { | ||
| salt: order.salt, | ||
| maker: order.maker, | ||
| signer: order.signer, | ||
| tokenId: order.tokenId, | ||
| makerAmount: order.makerAmount, | ||
| takerAmount: order.takerAmount, | ||
| side: order.side, | ||
| signatureType: order.signatureType, | ||
| timestamp: order.timestamp, | ||
| metadata: order.metadata, | ||
| builder: order.builder, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Generates order's signature from a EIP712TypedData object + the signer address | ||
| * @param typedData | ||
| * @returns a OrderSignatureV2 string | ||
| */ | ||
| buildOrderSignature(typedData: EIP712TypedData): Promise<OrderSignatureV2> { | ||
| delete typedData.types.EIP712Domain; | ||
| return this.signer._signTypedData( | ||
| typedData.domain, | ||
| typedData.types, | ||
| typedData.message, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Generates the hash of the order from a EIP712TypedData object. | ||
| * @param orderTypedData | ||
| * @returns a OrderHashV2 string | ||
| */ | ||
| buildOrderHash(orderTypedData: EIP712TypedData): OrderHashV2 { | ||
| const digest = hashTypedData(orderTypedData); | ||
| return digest; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // V2 Exchange constants | ||
| // Domain name is shared with V1; only the version changes. | ||
| export const PROTOCOL_NAME_V2 = 'Polymarket CTF Exchange'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exported
|
||
| export const PROTOCOL_VERSION_V2 = '2'; | ||
|
|
||
| // V2 Order EIP-712 struct. | ||
| // Note: `expiration` is intentionally absent — it is an API-level field, | ||
| // not part of the on-chain EIP-712 signature. | ||
| export const ORDER_V2_STRUCTURE = [ | ||
| { name: 'salt', type: 'uint256' }, | ||
| { name: 'maker', type: 'address' }, | ||
| { name: 'signer', type: 'address' }, | ||
| { name: 'tokenId', type: 'uint256' }, | ||
| { name: 'makerAmount', type: 'uint256' }, | ||
| { name: 'takerAmount', type: 'uint256' }, | ||
| { name: 'side', type: 'uint8' }, | ||
| { name: 'signatureType', type: 'uint8' }, | ||
| { name: 'timestamp', type: 'uint256' }, | ||
| { name: 'metadata', type: 'bytes32' }, | ||
| { name: 'builder', type: 'bytes32' }, | ||
| ]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,11 @@ | ||
| export * from './exchange.order.builder.ts'; | ||
| export * from './exchange.order.const.ts'; | ||
| export * from './exchange.order-v2.builder.ts'; | ||
| export * from './exchange.order-v2.const.ts'; | ||
|
|
||
| export * from './model/abi.model.ts'; | ||
| export * from './model/eip712.model.ts'; | ||
| export * from './model/order.model.ts'; | ||
| export * from './model/order-v2.model.ts'; | ||
| export * from './model/order-side.model.ts'; | ||
| export * from './model/signature-types.model.ts'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| import type { EIP712Object } from './eip712.model.ts'; | ||
| import type { Side } from './order-side.model.ts'; | ||
| import type { SignatureType } from './signature-types.model.ts'; | ||
|
|
||
| export type OrderSignatureV2 = string; | ||
|
|
||
| export type OrderHashV2 = string; | ||
|
|
||
| export interface OrderDataV2 { | ||
| /** | ||
| * Maker of the order, i.e the source of funds for the order | ||
| */ | ||
| maker: string; | ||
|
|
||
| /** | ||
| * Token Id of the CTF ERC1155 asset to be bought or sold. | ||
| * If BUY, this is the tokenId of the asset to be bought, i.e the makerAssetId | ||
| * If SELL, this is the tokenId of the asset to be sold, i.e the takerAssetId | ||
| */ | ||
| tokenId: string; | ||
|
|
||
| /** | ||
| * Maker amount, i.e the max amount of tokens to be sold | ||
| */ | ||
| makerAmount: string; | ||
|
|
||
| /** | ||
| * Taker amount, i.e the minimum amount of tokens to be received | ||
| */ | ||
| takerAmount: string; | ||
|
|
||
| /** | ||
| * The side of the order, BUY or SELL | ||
| */ | ||
| side: Side; | ||
|
|
||
| /** | ||
| * Signer of the order. Optional, if it is not present the signer is the maker of the order. | ||
| */ | ||
| signer?: string; | ||
|
|
||
| /** | ||
| * Signature type used by the Order. Default value 'EOA' | ||
| */ | ||
| signatureType?: SignatureType; | ||
|
|
||
| /** | ||
| * Timestamp of the order | ||
| */ | ||
| timestamp?: string; | ||
|
|
||
| /** | ||
| * Metadata of the order (bytes32) | ||
| */ | ||
| metadata?: string; | ||
|
|
||
| /** | ||
| * Builder of the order (bytes32) | ||
| */ | ||
| builder?: string; | ||
|
|
||
| /** | ||
| * Expiration timestamp of the order (unix seconds, "0" = no expiration) | ||
| */ | ||
| expiration?: string; | ||
| } | ||
|
|
||
| export interface OrderV2 extends EIP712Object { | ||
| /** | ||
| * Unique salt to ensure entropy | ||
| */ | ||
| readonly salt: string; | ||
|
|
||
| /** | ||
| * Maker of the order, i.e the source of funds for the order | ||
| */ | ||
| readonly maker: string; | ||
|
|
||
| /** | ||
| * Signer of the order | ||
| */ | ||
| readonly signer: string; | ||
|
|
||
| /** | ||
| * Token Id of the CTF ERC1155 asset to be bought or sold. | ||
| * If BUY, this is the tokenId of the asset to be bought, i.e the makerAssetId | ||
| * If SELL, this is the tokenId of the asset to be sold, i.e the takerAssetId | ||
| */ | ||
| readonly tokenId: string; | ||
|
|
||
| /** | ||
| * Maker amount, i.e the max amount of tokens to be sold | ||
| */ | ||
| readonly makerAmount: string; | ||
|
|
||
| /** | ||
| * Taker amount, i.e the minimum amount of tokens to be received | ||
| */ | ||
| readonly takerAmount: string; | ||
|
|
||
| /** | ||
| * The side of the order, BUY or SELL | ||
| */ | ||
| readonly side: Side; | ||
|
|
||
| /** | ||
| * Signature type used by the Order | ||
| */ | ||
| readonly signatureType: SignatureType; | ||
|
|
||
| /** | ||
| * Timestamp of the order | ||
| */ | ||
| readonly timestamp: string; | ||
|
|
||
| /** | ||
| * Metadata of the order (bytes32) | ||
| */ | ||
| readonly metadata: string; | ||
|
|
||
| /** | ||
| * Builder of the order (bytes32) | ||
| */ | ||
| readonly builder: string; | ||
|
|
||
| /** | ||
| * Expiration timestamp of the order (unix seconds, "0" = no expiration) | ||
| */ | ||
| readonly expiration: string; | ||
| } | ||
|
|
||
| export interface SignedOrderV2 extends OrderV2 { | ||
| /** | ||
| * The order signature | ||
| */ | ||
| readonly signature: OrderSignatureV2; | ||
| } |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default timestamp uses milliseconds instead of seconds
High Severity
The default
timestampvalue usesDate.now().toString(), which returns milliseconds since epoch. In blockchain contexts, timestamps are in seconds (block.timestampis unix seconds). The siblingexpirationfield is explicitly documented as "unix seconds, '0' = no expiration", strongly indicatingtimestampis also expected in seconds. A millisecond value will be ~1000x too large, causing incorrect order timestamps and likely on-chain validation failures.Reviewed by Cursor Bugbot for commit f5c8bcc. Configure here.