@salad-labs/loopz-typescript
Version:
The Official Loopz TypeScript SDK
634 lines • 30.6 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Seaport } from "@opensea/seaport-js";
import { ItemType } from "@opensea/seaport-js/lib/constants";
import { TOKEN_CONSTANTS } from "./constants/base/tokenconstants";
import { Client } from "./core/client";
import { ethers } from "ethers";
import { Auth, SEAPORT_1_5_CONTRACT_ADDRESS } from ".";
/**
* Order class that handles interactions with the OpenSea trading platform.
* @class Order
* @extends Client
*/
export class Order {
constructor() {
/**
* @property {Maybe<ethers.providers.Web3Provider | ethers.providers.JsonRpcProvider>} _provider - The provider instance.
*/
this._provider = null;
/**
* @property {Maybe<Seaport>} _seaport - The Seaport instance.
*/
this._seaport = null;
/**
* @property {number} _MIN_BLOCKS_REQUIRED - The minimum number of block confirmations required
*/
this._MIN_BLOCKS_REQUIRED = 3;
this._eventsCallbacks = [];
this._initialized = false;
if (!Order._config)
throw new Error("Order must be configured before getting the instance");
Order._client = new Client(Order._config.devMode);
this._blocksNumberConfirmationRequired = this._MIN_BLOCKS_REQUIRED;
Order._instance = this;
}
/** static methods */
static config(config) {
if (Order._config)
throw new Error("Order already configured");
Order._config = config;
}
static getInstance() {
var _a;
return (_a = Order._instance) !== null && _a !== void 0 ? _a : new Order();
}
/** private instance methods */
/**
* Asynchronously fetches the platform Gnosis multisig wallet from the backend API.
* @returns {Promise<Maybe<<MultiSigWallet>>>} A promise that resolves to the multisig wallet object if successful,
* or null if there was an error or no data was returned in the response.
*/
_getGnosis() {
return __awaiter(this, void 0, void 0, function* () {
if (!Order._config || !Order._instance || !Order._client)
throw new Error("Order has not been configured");
if (!Auth.account)
return null;
try {
const { response } = yield Order._client.fetch(Order._client.backendUrl(`/wallet/multisig/${Auth.account.getCurrentNetwork(false)}`));
if (!response)
return null;
const { data } = response;
return data[0];
}
catch (error) {
console.warn(error);
if ("statusCode" in error && error.statusCode === 401) {
yield Auth.getInstance().logout();
}
}
return null;
});
}
/**
* Asynchronously fetches platform fees from the backend API.
* @returns A Promise that resolves to a Maybe<Fee> object.
*/
_getMasterFee() {
return __awaiter(this, void 0, void 0, function* () {
if (!Order._config || !Order._instance || !Order._client)
throw new Error("Order has not been configured");
if (!Auth.account)
return null;
try {
const { response } = yield Order._client.fetch(Order._client.backendUrl(`/fee/platform/${Auth.account.getCurrentNetwork(false)}`));
if (!response)
return null;
const { data } = response;
return data[0];
}
catch (error) {
console.warn(error);
if ("statusCode" in error && error.statusCode === 401) {
yield Auth.getInstance().logout();
}
return null;
}
});
}
/**
* Analyzes the order initialization object to extract offer and consideration details.
* @param {CreateOrderInput} orderInit - The order initialization object.
* @returns An object containing offer and consideration details.
*/
_analyzeOrder(orderInit) {
var _a, _b;
if (!orderInit || orderInit.constructor.name !== "Object")
throw new Error("Invalid argument");
const offer = {
hasNFT: false,
hasToken: false,
NFTs: 0,
NFTcollections: [],
NFTcollectionsIdentifiers: {},
}, consideration = Object.assign({}, offer);
if ("offer" in orderInit &&
Array.isArray(orderInit.offer) &&
orderInit.offer.length)
for (const o of orderInit.offer.filter((rawOffer) => (rawOffer === null || rawOffer === void 0 ? void 0 : rawOffer.itemType) !== undefined && rawOffer.itemType !== null))
switch (o.itemType) {
case ItemType.ERC1155:
case ItemType.ERC721:
offer.hasNFT = true;
offer.NFTs++;
if (!offer.NFTcollections.includes(o.token))
offer.NFTcollections.push(o.token);
offer.NFTcollectionsIdentifiers[o.token] = [
...((_a = offer.NFTcollectionsIdentifiers[o.token]) !== null && _a !== void 0 ? _a : []),
o.identifier,
];
break;
case ItemType.ERC20:
offer.hasToken = true;
}
if ("consideration" in orderInit &&
Array.isArray(orderInit.consideration) &&
orderInit.consideration.length)
for (const c of orderInit.consideration)
if ("itemType" in c)
switch (c.itemType) {
case ItemType.ERC1155:
case ItemType.ERC721:
consideration.hasNFT = true;
consideration.NFTs++;
if (!consideration.NFTcollections.includes(c.token))
consideration.NFTcollections.push(c.token);
consideration.NFTcollectionsIdentifiers[c.token] = [
...((_b = consideration.NFTcollectionsIdentifiers[c.token]) !== null && _b !== void 0 ? _b : []),
c.identifier,
];
break;
case ItemType.ERC20:
consideration.hasToken = true;
break;
case ItemType.NATIVE:
consideration.hasToken = true;
}
else
consideration.hasToken = true;
return {
offer,
consideration,
};
}
/**
* Adds the master fees to the given order input.
* @param {CreateOrderInput} orderInit - The initial order input to add platform fees to.
* @returns {Promise<CreateOrderInput>} The order input with platform fees added.
*/
_addMasterFee(orderInit) {
return __awaiter(this, void 0, void 0, function* () {
const orderTypes = this._analyzeOrder(orderInit);
const masterFee = yield this._getMasterFee();
const gnosis = yield this._getGnosis();
let flatFee = "";
let basisPoints = 0;
let gnosisRecipient = "";
let checkFee = true;
if (masterFee) {
if (masterFee.flatFee)
flatFee = masterFee.flatFee;
else
checkFee = false;
if (masterFee.percentageFee)
basisPoints = masterFee.percentageFee;
else
checkFee = false;
}
else {
flatFee = "0";
basisPoints = 50;
}
if (!checkFee) {
flatFee = "0";
basisPoints = 50;
}
if (gnosis)
gnosisRecipient = gnosis.multisigAddress;
if (orderTypes.consideration.hasNFT &&
!orderTypes.consideration.hasToken &&
orderTypes.offer.hasNFT &&
!orderTypes.offer.hasToken) {
return Object.assign(Object.assign({}, orderInit), { offer: orderInit.offer, consideration: [
...orderInit.consideration,
{
recipient: gnosisRecipient,
itemType: TOKEN_CONSTANTS.NATIVE,
token: ethers.ZeroAddress,
amount: ethers.parseEther(flatFee).toString(),
identifier: "0",
},
] });
}
if (orderTypes.consideration.hasToken &&
orderTypes.offer.hasNFT &&
orderTypes.offer.NFTcollections.length > 0) {
if (!("fees" in orderInit) ||
typeof orderInit === "undefined" ||
(Array.isArray(orderInit.fees) && orderInit.fees.length === 0))
orderInit.fees = [
{
recipient: gnosisRecipient,
basisPoints,
},
];
else if (Array.isArray(orderInit.fees) && orderInit.fees.length > 0) {
orderInit.fees.push({
recipient: gnosisRecipient,
basisPoints,
});
}
}
return orderInit;
});
}
/** public instance methods */
isInitialized() {
return this._initialized === true;
}
init(wallet) {
return __awaiter(this, void 0, void 0, function* () {
try {
if (!Auth.account)
throw new Error("Account is not initialized.");
this._provider = yield wallet.getEthereumProvider();
const bp = new ethers.BrowserProvider(this._provider);
this._seaport = new Seaport(yield bp.getSigner(wallet.address), {
overrides: {
seaportVersion: "1.5",
contractAddress: SEAPORT_1_5_CONTRACT_ADDRESS,
},
});
this._initialized = true;
}
catch (error) {
console.log(error);
this._initialized = false;
}
});
}
/**
* Emits an event with the specified name and parameters to all registered callbacks for that event.
* @param {string} event - The name of the event to emit.
* @param {any} [params] - The parameters to pass to the event callbacks.
* @returns None
*/
_emit(event, params) {
const item = this._eventsCallbacks.find((item) => {
return item.eventName === event;
});
if (item)
for (const cb of item.callbacks)
cb(params);
}
on(eventName, callback, onlyOnce) {
const index = this._eventsCallbacks.findIndex((item) => {
return item.eventName === eventName;
});
if (index > -1 && onlyOnce === true)
return;
if (index > -1 && !onlyOnce)
this._eventsCallbacks[index].callbacks.push(callback);
this._eventsCallbacks.push({
eventName,
callbacks: [callback],
});
}
/**
* Unsubscribes a callback function from a specific event.
* @param {"onFinalizeError"} eventName - The name of the event to unsubscribe from.
* @returns None
* @throws {Error} If the event is not supported or the callback is not a function.
*/
off(eventName) {
const item = this._eventsCallbacks.find((eventItem) => {
return eventItem.eventName === eventName;
});
if (item)
item.callbacks = [];
}
/**
* Set the block numbers to wait in which consider a transaction mined by the create, cancel and finalize methods.
* @param {number} blocksNumberConfirmationRequired - The number of blocks required for confirmation.
* @throws {Error} If blocksNumberConfirmationRequired is less than 1.
*/
setBlocksNumberConfirmationRequired(blocksNumberConfirmationRequired) {
if (blocksNumberConfirmationRequired < 1)
throw new Error("blocksNumberConfirmationRequired cannot be lower than one.");
this._blocksNumberConfirmationRequired = blocksNumberConfirmationRequired;
}
/**
* Create an order instance with the given maker and taker assets, along with additional parameters.
* @param {{Array<Asset>; address: string}} participantOne - The assets offered by the maker.
* @param {{Array<Asset>; address: string}} participantTwo - The assets desired by the taker.
* @param {number} [end=0] - The end time for the order.
* @param {Array<SeaportFee>} [fees] - Optional fees for the order.
* @param {string} proposalId - The ID of the proposal.
*/
create(wallet_1, participantOne_1, participantTwo_1) {
return __awaiter(this, arguments, void 0, function* (wallet, participantOne, participantTwo, end = 0, fees = [], proposalId) {
var _a, _b;
if (!Order._config || !Order._instance || !Order._client)
throw new Error("Order has not been configured");
try {
if (!Auth.account)
throw new Error("An account must be initialized.");
if (!this._provider || !this._seaport)
throw new Error("init() must be called to initialize the client.");
if (end < 0)
throw new Error("end cannot be lower than zero.");
if ("assets" in participantOne &&
participantOne.assets &&
participantOne.assets.length > 0) {
//seaport supports erc20 tokens in the offer array object but platform not,
//so we throw an error if someone try to place tokens in the offer
const erc20 = participantOne.assets.find((asset) => {
return asset.itemType === TOKEN_CONSTANTS["ERC20"];
});
if (erc20)
throw new Error("You cannot add an ERC20 token in the maker assets.");
const token = participantOne.assets.find((asset) => {
return asset.itemType === TOKEN_CONSTANTS["NATIVE"];
});
if (token)
throw new Error("You cannot add NATIVE token in the maker assets.");
}
// Retrieve the maker address
const addressMaker = wallet.address;
const makerAssets = (_a = participantOne.assets) === null || _a === void 0 ? void 0 : _a.map((asset) => {
var _a, _b;
return Object.assign(Object.assign({}, asset), { recipient: (_a = asset.recipient) === null || _a === void 0 ? void 0 : _a.toLowerCase(), token: (_b = asset.token) === null || _b === void 0 ? void 0 : _b.toLowerCase() });
});
const takerAssets = (_b = participantTwo.assets) === null || _b === void 0 ? void 0 : _b.map((asset) => {
var _a, _b;
return Object.assign(Object.assign({}, asset), { recipient: (_a = asset.recipient) === null || _a === void 0 ? void 0 : _a.toLowerCase(), token: (_b = asset.token) === null || _b === void 0 ? void 0 : _b.toLowerCase() });
});
const orderInit = yield this._addMasterFee({
offer: [...(makerAssets !== null && makerAssets !== void 0 ? makerAssets : [])].map((asset) => (Object.assign(Object.assign({}, asset), { itemType: typeof asset.itemType === "string"
? TOKEN_CONSTANTS[asset.itemType]
: asset.itemType }))),
consideration: [...(takerAssets !== null && takerAssets !== void 0 ? takerAssets : [])].map((asset) => (Object.assign(Object.assign({}, asset), { itemType: typeof asset.itemType === "string"
? TOKEN_CONSTANTS[asset.itemType]
: asset.itemType, recipient: asset.recipient ? asset.recipient : addressMaker }))),
zone: participantTwo.address,
endTime: Math.floor(new Date().setDate(new Date().getDate() + end) / 1000).toString(), // days in seconds (UNIX timestamp)
fees,
restrictedByZone: true,
});
const { executeAllActions } = yield this._seaport.createOrder(orderInit, addressMaker);
const order = yield executeAllActions();
const orderHash = this._seaport.getOrderHash(order.parameters);
const { response } = yield Order._client.fetch(Order._client.backendUrl("/order/create"), {
method: "POST",
body: {
network: Auth.account.getCurrentNetwork(false),
orderInit,
order: Object.assign({ orderHash, orderType: order.parameters.orderType }, order),
proposalId,
},
});
if (!response || !response.data)
throw new Error("Order is created, but the system was not able to store it.");
const { orderId } = response.data[0];
return Object.assign({ hash: orderHash, orderId }, order);
}
catch (error) {
console.warn(error);
if ("statusCode" in error && error.statusCode === 401) {
yield Auth.getInstance().logout();
}
return null;
}
});
}
/**
* Finalizes a order by fetching order details, executing the order, and handling transaction events.
* @param {string} orderId - The ID of the trade to be finalized.
* @returns None
*/
finalize(orderId) {
return __awaiter(this, void 0, void 0, function* () {
if (!Order._config || !Order._instance || !Order._client)
throw new Error("Order has not been configured");
try {
if (!Auth.account)
throw new Error("Account must be initialized.");
if (!this._seaport)
throw new Error("init() must be called to initialize the client.");
const { response } = yield Order._client.fetch(Order._client.backendUrl(`/order/${Auth.account.getCurrentNetwork(false)}/${orderId}`));
if (!response || !response.data)
throw new Error("response data is empty");
const { data: orderDetail } = response;
const data = orderDetail[0];
const parameters = data.parameters.order.parameters;
const taker = data.parameters.addressTaker;
const order = {
hash: data.parameters.order.orderHash,
parameters: parameters,
signature: data.parameters.order.signature,
};
try {
const { executeAllActions } = yield this._seaport.fulfillOrder({
order,
accountAddress: taker,
overrides: {
gasLimit: "215120",
},
});
this._emit("onFulfillOrder");
try {
const transact = yield executeAllActions();
this._emit("onExecuteAllActions", { transact });
}
catch (error) {
console.warn(error);
return this._emit("onExecuteAllActionsError", {
error,
typeError: "waitError",
});
}
}
catch (error) {
console.warn(error);
this._emit("onFulfillOrderError", {
error,
typeError: "execOrderTransactionError",
});
}
}
catch (error) {
console.warn(error);
this._emit("onFinalizeError", {
error,
typeError: "ApiError",
});
}
});
}
/**
* Cancel an order with the given order ID.
* @param {string} orderId - The ID of the order to cancel.
* @param {number} [gasLimit=2000000] - The gas limit for the transaction.
* @param {Maybe<string>} [gasPrice=null] - The gas price for the transaction.
* @returns None
*/
cancel(orderId_1) {
return __awaiter(this, arguments, void 0, function* (orderId, gasLimit = 2000000, gasPrice = null) {
if (!Order._config || !Order._instance || !Order._client)
throw new Error("Order has not been configured");
try {
if (!Auth.account)
throw new Error("network must be defined.");
if (!this._seaport)
throw new Error("init() must be called to initialize the client.");
const { response } = yield Order._client.fetch(Order._client.backendUrl(`/order/${Auth.account.getCurrentNetwork(false)}/${orderId}`));
if (!response || !response.data)
throw new Error("response data is empty");
const { data: orderDetail } = response;
const data = orderDetail[0];
const parameters = data.parameters.order.parameters;
const maker = data.parameters.addressMaker;
const txOverrides = {};
if (gasLimit && gasLimit !== 2000000)
txOverrides.gasLimit = gasLimit;
if (gasPrice)
txOverrides.gasPrice = gasPrice;
const tx = this._seaport.cancelOrders([parameters], maker);
this._emit("onCancelOrders", { tx });
const transact = yield tx.transact(Object.assign({}, txOverrides));
const receipt = yield transact.wait(this._blocksNumberConfirmationRequired);
this._emit("onCancelOrdersMined", { receipt });
}
catch (error) {
this._emit("onCancelOrdersError", {
error,
});
if ("statusCode" in error && error.statusCode === 401) {
yield Auth.getInstance().logout();
}
}
});
}
/**
* Retrieves order details for a specific network and order ID.
* @param {string} networkId - The network ID for the order.
* @param {string} id - The ID of the order.
* @returns {Promise<Maybe<OrderDetail>>} A Promise that resolves to the order detail information, or null if an error occurs.
*/
get(networkId, id) {
return __awaiter(this, void 0, void 0, function* () {
if (!Order._config || !Order._instance || !Order._client)
throw new Error("Order has not been configured");
try {
const { response } = yield Order._client.fetch(Order._client.backendUrl(`/order/${networkId}/${id}`));
if (!response)
return null;
const { data } = response;
return data[0];
}
catch (error) {
console.warn(error);
if ("statusCode" in error && error.statusCode === 401) {
yield Auth.getInstance().logout();
}
return null;
}
});
}
/**
* Retrieves a list of global orderss based on the provided parameters.
* @param {object} options - An object containing the parameters for fetching the orders list.
* @param {Network | "*"} networkId - The network ID or "*" for all networks.
* @param {string | "*"} status - The status of the orders or "*" for all statuses.
* @param {number} skip - The number of orders to skip.
* @param {number} take - The number of orders to retrieve.
* @param {string} [from] - Optional parameter for filtering orders from a specific date.
* @param {string} [to] - Optional parameter for filtering orders up to a specific date.
* @param {Array<{ address: string; networkId: Network }>} [collectios] - an raary of collections paired with their respective network.
* @param {Array<string>} [search] - An array of search terms to filter results.
* @param {object} [order] - An object containing direction and field for ordering results.
* @param {string} order.direction - The direction of ordering, either "ASC" for ascending or "DESC" for descending.
* @param {string} order.field - The field to order results by.
*/
listOrders(_a) {
return __awaiter(this, arguments, void 0, function* ({ networkId, status, skip, take, from, to, collections, search, order, }) {
if (!Order._config || !Order._instance || !Order._client)
throw new Error("Order has not been configured");
try {
const { response } = yield Order._client.fetch(Order._client.backendUrl(`/order/get/all/${networkId}/${status}/${skip}/${take}`), {
method: "POST",
body: {
collections: typeof collections !== "undefined" ? collections : [],
search: typeof search !== "undefined" ? search : [],
order: typeof order !== "undefined" ? order : null,
from: typeof from !== "undefined" ? from : null,
to: typeof to !== "undefined" ? to : null,
},
});
if (!response || !response.data)
return null;
const { data } = response;
return data[0];
}
catch (error) {
console.warn(error);
if ("statusCode" in error && error.statusCode === 401) {
yield Auth.getInstance().logout();
}
return null;
}
});
}
/**
* Retrieves a list of user orders based on the provided parameters.
* @param {object} options - An object containing the parameters for fetching user orders.
* @param {Network | "*"} networkId - The network ID or "*" for all networks.
* @param {string} did - The user's did.
* @param {string | "*"} status - The status of the orders or "*" for all statuses.
* @param {number} skip - The number of orders to skip.
* @param {number} take - The number of orders to retrieve.
* @param {string} [from] - Optional parameter for filtering orders from a specific date.
* @param {string} [to] - Optional parameter for filtering orders up to a specific date.
* @param {Array<{ address: string; networkId: Network }>} [collectios] - an raary of collections paired with their respective network.
* @param {Array<string>} [search] - An array of search terms to filter results.
* @param {object} [order] - An object containing direction and field for ordering results.
* @param {string} order.direction - The direction of ordering, either "ASC" for ascending or "DESC" for descending.
* @param {string} order.field - The field to order results by.
*/
listUserOrders(_a) {
return __awaiter(this, arguments, void 0, function* ({ networkId, did, status, skip, take, from, to, collections, searchAddress, order, }) {
if (!Order._config || !Order._instance || !Order._client)
throw new Error("Order has not been configured");
try {
const { response } = yield Order._client.fetch(Order._client.backendUrl(`/order/user/all/${networkId}/${did}/${status}/${skip}/${take}${searchAddress ? `/${searchAddress}` : ""}`), {
method: "POST",
body: {
collections: typeof collections !== "undefined" ? collections : [],
order: typeof order !== "undefined" ? order : null,
from: typeof from !== "undefined" ? from : null,
to: typeof to !== "undefined" ? to : null,
},
});
if (!response || !response.data)
return null;
const { data } = response;
return data[0];
}
catch (error) {
console.warn(error);
if ("statusCode" in error && error.statusCode === 401) {
yield Auth.getInstance().logout();
}
return null;
}
});
}
/**
* Updates the configuration settings for the order module.
* @param {OrderConfig} config - The configuration object for the order module.
* @returns None
*/
config(config) {
if (config.minBlocksRequired)
this._MIN_BLOCKS_REQUIRED = config.minBlocksRequired;
}
}
//# sourceMappingURL=order.js.map