|
| 1 | +import type { JsonRpcSigner } from '@ethersproject/providers'; |
| 2 | +import type { Wallet } from '@ethersproject/wallet'; |
| 3 | +import { |
| 4 | + EIP712_DOMAIN, |
| 5 | + PROTOCOL_NAME, |
| 6 | +} from './exchange.order.const.ts'; |
| 7 | +import { |
| 8 | + PROTOCOL_VERSION_V2, |
| 9 | + ORDER_V2_STRUCTURE, |
| 10 | +} from './exchange.order-v2.const.ts'; |
| 11 | +import type { EIP712TypedData } from './model/eip712.model.ts'; |
| 12 | +import { hashTypedData } from 'viem'; |
| 13 | +import type { |
| 14 | + OrderV2, |
| 15 | + OrderDataV2, |
| 16 | + OrderHashV2, |
| 17 | + OrderSignatureV2, |
| 18 | + SignedOrderV2, |
| 19 | +} from './model/order-v2.model.ts'; |
| 20 | +import { SignatureType } from './model/signature-types.model.ts'; |
| 21 | +import { generateOrderSalt } from './utils.ts'; |
| 22 | + |
| 23 | +const BYTES32_ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; |
| 24 | + |
| 25 | +export class ExchangeOrderBuilderV2 { |
| 26 | + constructor( |
| 27 | + private readonly contractAddress: string, |
| 28 | + private readonly chainId: number, |
| 29 | + private readonly signer: Wallet | JsonRpcSigner, |
| 30 | + private readonly generateSalt = generateOrderSalt, |
| 31 | + ) {} |
| 32 | + |
| 33 | + /** |
| 34 | + * Build an order object including the signature. |
| 35 | + * @param orderData |
| 36 | + * @returns a SignedOrderV2 object (order + signature) |
| 37 | + */ |
| 38 | + async buildSignedOrder(orderData: OrderDataV2): Promise<SignedOrderV2> { |
| 39 | + const order = await this.buildOrder(orderData); |
| 40 | + const orderTypedData = this.buildOrderTypedData(order); |
| 41 | + const orderSignature = await this.buildOrderSignature(orderTypedData); |
| 42 | + |
| 43 | + return { |
| 44 | + ...order, |
| 45 | + signature: orderSignature, |
| 46 | + } as SignedOrderV2; |
| 47 | + } |
| 48 | + |
| 49 | + /** |
| 50 | + * Creates an OrderV2 object from order data. |
| 51 | + * @param orderData |
| 52 | + * @returns an OrderV2 object (not signed) |
| 53 | + */ |
| 54 | + async buildOrder({ |
| 55 | + maker, |
| 56 | + tokenId, |
| 57 | + makerAmount, |
| 58 | + takerAmount, |
| 59 | + side, |
| 60 | + signer, |
| 61 | + signatureType, |
| 62 | + timestamp, |
| 63 | + metadata, |
| 64 | + builder, |
| 65 | + expiration, |
| 66 | + }: OrderDataV2): Promise<OrderV2> { |
| 67 | + if (typeof signer == 'undefined' || !signer) { |
| 68 | + signer = maker; |
| 69 | + } |
| 70 | + |
| 71 | + const resolvedSignatureType = signatureType ?? SignatureType.EOA; |
| 72 | + |
| 73 | + // For POLY_DEPOSIT_WALLET, the order's signer field is the wallet |
| 74 | + // contract address, while the actual ECDSA signing is done by the EOA owner |
| 75 | + if (resolvedSignatureType !== SignatureType.POLY_DEPOSIT_WALLET) { |
| 76 | + const signerAddress = await this.signer.getAddress(); |
| 77 | + if (signer !== signerAddress) { |
| 78 | + throw new Error('signer does not match'); |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + return { |
| 83 | + salt: this.generateSalt(), |
| 84 | + maker, |
| 85 | + signer, |
| 86 | + tokenId, |
| 87 | + makerAmount, |
| 88 | + takerAmount, |
| 89 | + side, |
| 90 | + signatureType: resolvedSignatureType, |
| 91 | + timestamp: timestamp ?? Date.now().toString(), |
| 92 | + metadata: metadata ?? BYTES32_ZERO, |
| 93 | + builder: builder ?? BYTES32_ZERO, |
| 94 | + expiration: expiration ?? '0', |
| 95 | + }; |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * Parses an OrderV2 object to EIP712 typed data |
| 100 | + * @param order |
| 101 | + * @returns a EIP712TypedData object |
| 102 | + */ |
| 103 | + buildOrderTypedData(order: OrderV2): EIP712TypedData { |
| 104 | + return { |
| 105 | + primaryType: 'Order', |
| 106 | + types: { |
| 107 | + EIP712Domain: EIP712_DOMAIN, |
| 108 | + Order: ORDER_V2_STRUCTURE, |
| 109 | + }, |
| 110 | + domain: { |
| 111 | + name: PROTOCOL_NAME, |
| 112 | + version: PROTOCOL_VERSION_V2, |
| 113 | + chainId: this.chainId, |
| 114 | + verifyingContract: this.contractAddress, |
| 115 | + }, |
| 116 | + message: { |
| 117 | + salt: order.salt, |
| 118 | + maker: order.maker, |
| 119 | + signer: order.signer, |
| 120 | + tokenId: order.tokenId, |
| 121 | + makerAmount: order.makerAmount, |
| 122 | + takerAmount: order.takerAmount, |
| 123 | + side: order.side, |
| 124 | + signatureType: order.signatureType, |
| 125 | + timestamp: order.timestamp, |
| 126 | + metadata: order.metadata, |
| 127 | + builder: order.builder, |
| 128 | + }, |
| 129 | + }; |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * Generates order's signature from a EIP712TypedData object + the signer address |
| 134 | + * @param typedData |
| 135 | + * @returns a OrderSignatureV2 string |
| 136 | + */ |
| 137 | + buildOrderSignature(typedData: EIP712TypedData): Promise<OrderSignatureV2> { |
| 138 | + delete typedData.types.EIP712Domain; |
| 139 | + return this.signer._signTypedData( |
| 140 | + typedData.domain, |
| 141 | + typedData.types, |
| 142 | + typedData.message, |
| 143 | + ); |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * Generates the hash of the order from a EIP712TypedData object. |
| 148 | + * @param orderTypedData |
| 149 | + * @returns a OrderHashV2 string |
| 150 | + */ |
| 151 | + buildOrderHash(orderTypedData: EIP712TypedData): OrderHashV2 { |
| 152 | + const digest = hashTypedData(orderTypedData); |
| 153 | + return digest; |
| 154 | + } |
| 155 | +} |
0 commit comments