Skip to content

Commit

Permalink
feat: [TD-1677] Add support for bids (#2186)
Browse files Browse the repository at this point in the history
  • Loading branch information
lfportal authored Sep 25, 2024
1 parent b475a9a commit bc5626d
Show file tree
Hide file tree
Showing 23 changed files with 1,346 additions and 147 deletions.
14 changes: 4 additions & 10 deletions packages/orderbook/src/api-client/api-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
anything, deepEqual, instance, mock, when,
} from 'ts-mockito';
import type { OrderComponents } from '@opensea/seaport-js/lib/types';
import { OrderType } from '@opensea/seaport-js/lib/constants';
import { ListingResult, OrdersService } from '../openapi/sdk';
import { ItemType } from '../seaport';
import { ImmutableApiClient } from './api-client';
Expand Down Expand Up @@ -172,19 +173,12 @@ describe('ImmutableApiClient', () => {
itemType: ItemType.NATIVE,
endAmount: '1',
startAmount: '1',
identifierOrCriteria: '456',
token: '0x123',
recipient: '0x123',
},
{
itemType: ItemType.NATIVE,
endAmount: '1',
startAmount: '1',
identifierOrCriteria: '456',
token: '0x123',
identifierOrCriteria: '0',
token: '0x',
recipient: '0x123',
},
];
orderComponents.orderType = OrderType.FULL_RESTRICTED;
orderComponents.endTime = new Date().getTime() / 1000;
orderComponents.startTime = new Date().getTime() / 1000;

Expand Down
140 changes: 96 additions & 44 deletions packages/orderbook/src/api-client/api-client.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import {
BidResult,
CancelOrdersResult,
Fee,
ListBidsResult,
ListingResult,
ListListingsResult,
ListTradeResult,
OrdersService,
ProtocolData,
TradeResult,
CancelOrdersResult,
} from '../openapi/sdk';
import { FulfillableOrder } from '../openapi/sdk/models/FulfillableOrder';
import { FulfillmentDataRequest } from '../openapi/sdk/models/FulfillmentDataRequest';
import { UnfulfillableOrder } from '../openapi/sdk/models/UnfulfillableOrder';
import { ItemType, SEAPORT_CONTRACT_VERSION_V1_5 } from '../seaport';
import { mapSeaportItemToImmutableItem, mapSeaportOrderTypeToImmutableProtocolDataOrderType } from '../seaport/map-to-immutable-order';
import {
CreateBidParams,
CreateListingParams,
FeeType,
ListBidsParams,
ListListingsParams,
ListTradesParams,
} from '../types';
import { FulfillableOrder } from '../openapi/sdk/models/FulfillableOrder';
import { UnfulfillableOrder } from '../openapi/sdk/models/UnfulfillableOrder';
import { FulfillmentDataRequest } from '../openapi/sdk/models/FulfillmentDataRequest';
import { ItemType, SEAPORT_CONTRACT_VERSION_V1_5 } from '../seaport';

export class ImmutableApiClient {
constructor(
Expand Down Expand Up @@ -47,6 +50,13 @@ export class ImmutableApiClient {
});
}

async getBid(bidId: string): Promise<BidResult> {
return this.orderbookService.getBid({
chainName: this.chainName,
bidId,
});
}

async getTrade(tradeId: string): Promise<TradeResult> {
return this.orderbookService.getTrade({
chainName: this.chainName,
Expand All @@ -63,6 +73,15 @@ export class ImmutableApiClient {
});
}

async listBids(
listOrderParams: ListBidsParams,
): Promise<ListBidsResult> {
return this.orderbookService.listBids({
chainName: this.chainName,
...listOrderParams,
});
}

async listTrades(
listTradesParams: ListTradesParams,
): Promise<ListTradeResult> {
Expand Down Expand Up @@ -94,66 +113,99 @@ export class ImmutableApiClient {
makerFees,
}: CreateListingParams): Promise<ListingResult> {
if (orderComponents.offer.length !== 1) {
throw new Error('Only one item can be listed at a time');
throw new Error('Only one item can be listed for a listing');
}

if (Number(orderComponents.offer[0].itemType) !== ItemType.ERC721
&& Number(orderComponents.offer[0].itemType) !== ItemType.ERC1155) {
if (orderComponents.consideration.length !== 1) {
throw new Error('Only one item can be used as currency for a listing');
}

if (![ItemType.ERC721, ItemType.ERC1155].includes(orderComponents.offer[0].itemType)) {
throw new Error('Only ERC721 / ERC1155 tokens can be listed');
}

const orderTypes = [
...orderComponents.consideration.map((c) => c.itemType),
];
const isSameConsiderationType = new Set(orderTypes).size === 1;
if (!isSameConsiderationType) {
throw new Error('All consideration items must be of the same type');
if (![ItemType.NATIVE, ItemType.ERC20].includes(orderComponents.consideration[0].itemType)) {
throw new Error('Only Native / ERC20 tokens can be used as currency items in a listing');
}

return this.orderbookService.createListing({
chainName: this.chainName,
requestBody: {
account_address: orderComponents.offerer,
buy: [
{
type:
Number(orderComponents.consideration[0].itemType)
=== ItemType.NATIVE
? 'NATIVE'
: 'ERC20',
amount: orderComponents.consideration[0].startAmount,
contract_address: orderComponents.consideration[0].token,
},
],
fees: makerFees.map((x) => ({
amount: x.amount,
type: FeeType.MAKER_ECOSYSTEM as unknown as Fee.type,
recipient_address: x.recipientAddress,
buy: orderComponents.consideration.map(mapSeaportItemToImmutableItem),
fees: makerFees.map((f) => ({
type: Fee.type.MAKER_ECOSYSTEM,
amount: f.amount,
recipient_address: f.recipientAddress,
})),
end_at: new Date(
parseInt(`${orderComponents.endTime.toString()}000`, 10),
).toISOString(),
order_hash: orderHash,
protocol_data: {
order_type:
mapSeaportOrderTypeToImmutableProtocolDataOrderType(orderComponents.orderType),
zone_address: orderComponents.zone,
seaport_address: this.seaportAddress,
seaport_version: SEAPORT_CONTRACT_VERSION_V1_5,
counter: orderComponents.counter.toString(),
},
salt: orderComponents.salt,
sell: orderComponents.offer.map(mapSeaportItemToImmutableItem),
signature: orderSignature,
start_at: new Date(
parseInt(`${orderComponents.startTime.toString()}000`, 10),
).toISOString(),
},
});
}

async createBid({
orderHash,
orderComponents,
orderSignature,
makerFees,
}: CreateBidParams): Promise<BidResult> {
if (orderComponents.offer.length !== 1) {
throw new Error('Only one item can be listed for a bid');
}

if (orderComponents.consideration.length !== 1) {
throw new Error('Only one item can be used as currency for a bid');
}

if (ItemType.ERC20 !== orderComponents.offer[0].itemType) {
throw new Error('Only ERC20 tokens can be used as the currency item in a bid');
}

if (![ItemType.ERC721, ItemType.ERC1155].includes(orderComponents.consideration[0].itemType)) {
throw new Error('Only ERC721 / ERC1155 tokens can be bid against');
}

return this.orderbookService.createBid({
chainName: this.chainName,
requestBody: {
account_address: orderComponents.offerer,
buy: orderComponents.consideration.map(mapSeaportItemToImmutableItem),
fees: makerFees.map((f) => ({
type: Fee.type.MAKER_ECOSYSTEM,
amount: f.amount,
recipient_address: f.recipientAddress,
})),
end_at: new Date(
parseInt(`${orderComponents.endTime.toString()}000`, 10),
).toISOString(),
order_hash: orderHash,
protocol_data: {
order_type: Number(orderComponents.offer[0].itemType) === ItemType.ERC1155
? ProtocolData.order_type.PARTIAL_RESTRICTED : ProtocolData.order_type.FULL_RESTRICTED,
order_type:
mapSeaportOrderTypeToImmutableProtocolDataOrderType(orderComponents.orderType),
zone_address: orderComponents.zone,
seaport_address: this.seaportAddress,
seaport_version: SEAPORT_CONTRACT_VERSION_V1_5,
counter: orderComponents.counter.toString(),
},
salt: orderComponents.salt,
sell: Number(orderComponents.offer[0].itemType) === ItemType.ERC1155
? [{
contract_address: orderComponents.offer[0].token,
token_id: orderComponents.offer[0].identifierOrCriteria,
type: 'ERC1155',
amount: orderComponents.offer[0].startAmount,
}] : [{
contract_address: orderComponents.offer[0].token,
token_id: orderComponents.offer[0].identifierOrCriteria,
type: 'ERC721',
}],
sell: orderComponents.offer.map(mapSeaportItemToImmutableItem),
signature: orderSignature,
start_at: new Date(
parseInt(`${orderComponents.startTime.toString()}000`, 10),
Expand Down
8 changes: 4 additions & 4 deletions packages/orderbook/src/openapi/mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function mapBidFromOpenApiOrder(order: OpenApiOrder): Bid {
throw new Error('Order type must be BID');
}

const sellItems: ERC20Item[] = order.buy.map((item) => {
const sellItems: ERC20Item[] = order.sell.map((item) => {
if (item.type === 'ERC20') {
return {
type: 'ERC20',
Expand All @@ -111,7 +111,7 @@ export function mapBidFromOpenApiOrder(order: OpenApiOrder): Bid {
throw new Error('Bid sell items must be ERC20');
});

const buyItems: (ERC721Item | ERC1155Item)[] = order.sell.map((item) => {
const buyItems: (ERC721Item | ERC1155Item)[] = order.buy.map((item) => {
if (item.type === 'ERC721') {
return {
type: 'ERC721',
Expand Down Expand Up @@ -168,7 +168,7 @@ export function mapCollectionBidFromOpenApiOrder(order: OpenApiOrder): Collectio
throw new Error('Order type must be COLLECTION_BID');
}

const sellItems: ERC20Item[] = order.buy.map((item) => {
const sellItems: ERC20Item[] = order.sell.map((item) => {
if (item.type === 'ERC20') {
return {
type: 'ERC20',
Expand All @@ -180,7 +180,7 @@ export function mapCollectionBidFromOpenApiOrder(order: OpenApiOrder): Collectio
throw new Error('Collection bid sell items must be ERC20');
});

const buyItems: (ERC721CollectionItem | ERC1155CollectionItem)[] = order.sell.map((item) => {
const buyItems: (ERC721CollectionItem | ERC1155CollectionItem)[] = order.buy.map((item) => {
if (item.type === 'ERC721_COLLECTION') {
return {
type: 'ERC721_COLLECTION',
Expand Down
Loading

0 comments on commit bc5626d

Please sign in to comment.