@georgeroman/wyvern-v2-sdk
Version:
Wyvern V2 SDK
252 lines • 11 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const abi_1 = require("@ethersproject/abi");
const constants_1 = require("@ethersproject/constants");
const order_1 = __importDefault(require("../../helpers/order"));
const types_1 = require("../../types");
const utils_1 = require("../../utils");
const Erc721_json_1 = __importDefault(require("../../abis/Erc721.json"));
// ERC721 calldata format:
// transferFrom(from, to, tokenId)
// ERC721 replacement pattern format:
// 4 bytes - "transferFrom" function signature
// 64 bytes - "from"
// 64 bytes - "to"
// 64 bytes - "tokenId"
const REPLACEMENT_PATTERN_BUY = "0x00000000" + "f".repeat(64) + "0".repeat(64) + "0".repeat(64);
const REPLACEMENT_PATTERN_SELL = "0x00000000" + "0".repeat(64) + "f".repeat(64) + "0".repeat(64);
class SingleItemErc721OrderBuilder {
static extractTokenId(order) {
try {
const result = new abi_1.Interface(Erc721_json_1.default).decodeFunctionData("transferFrom", order.calldata);
return result.tokenId.toString();
}
catch {
return undefined;
}
}
static isWellFormatted(order) {
try {
const tokenId = this.extractTokenId(order);
if (!tokenId) {
return false;
}
// Build a mock order that is for sure well-formatted
let mock;
if (order.side === types_1.Side.SELL) {
mock = this.sell({
...order,
tokenId,
fee: "0",
});
}
else if (order.side === types_1.Side.BUY) {
mock = this.buy({
...order,
tokenId,
fee: "0",
});
}
if (!mock) {
return false;
}
if (Number(order.makerRelayerFee) > 10000 ||
Number(order.takerRelayerFee) > 10000) {
return false;
}
mock.taker = order.taker;
mock.makerRelayerFee = order.makerRelayerFee;
mock.takerRelayerFee = order.takerRelayerFee;
// Make sure the two orders exactly match
return order_1.default.hash(mock) === order_1.default.hash(order);
}
catch {
return false;
}
}
static sell(params) {
try {
const isDutchAuction = (0, utils_1.bn)(params.extra || 0).gt(0);
if (isDutchAuction) {
if ((0, utils_1.bn)(params.listingTime).gte((0, utils_1.bn)(params.expirationTime))) {
throw new Error("Invalid listing/expiration time");
}
}
return order_1.default.normalize({
exchange: params.exchange,
maker: params.maker,
taker: constants_1.AddressZero,
makerRelayerFee: params.fee,
takerRelayerFee: "0",
feeRecipient: params.feeRecipient,
side: types_1.Side.SELL,
saleKind: isDutchAuction
? types_1.SaleKind.DUTCH_AUCTION
: types_1.SaleKind.FIXED_PRICE,
target: params.target,
howToCall: types_1.HowToCall.CALL,
calldata: new abi_1.Interface(Erc721_json_1.default).encodeFunctionData("transferFrom", [
params.maker,
constants_1.AddressZero,
params.tokenId,
]),
replacementPattern: REPLACEMENT_PATTERN_SELL,
staticTarget: constants_1.AddressZero,
staticExtradata: "0x",
paymentToken: params.paymentToken,
basePrice: params.basePrice,
extra: params.extra || "0",
listingTime: params.listingTime,
expirationTime: params.expirationTime,
salt: params.salt,
v: params.v || 0,
r: params.r || utils_1.Bytes32Zero,
s: params.s || utils_1.Bytes32Zero,
});
}
catch {
return undefined;
}
}
static buy(params) {
try {
return order_1.default.normalize({
exchange: params.exchange,
maker: params.maker,
taker: constants_1.AddressZero,
makerRelayerFee: "0",
takerRelayerFee: params.fee,
feeRecipient: params.feeRecipient,
side: types_1.Side.BUY,
// Notice: we don't support dutch auction buys
saleKind: types_1.SaleKind.FIXED_PRICE,
target: params.target,
howToCall: types_1.HowToCall.CALL,
calldata: new abi_1.Interface(Erc721_json_1.default).encodeFunctionData("transferFrom", [
constants_1.AddressZero,
params.maker,
params.tokenId,
]),
replacementPattern: REPLACEMENT_PATTERN_BUY,
staticTarget: constants_1.AddressZero,
staticExtradata: "0x",
paymentToken: params.paymentToken,
basePrice: params.basePrice,
extra: params.extra || "0",
listingTime: params.listingTime,
expirationTime: params.expirationTime,
salt: params.salt,
v: params.v || 0,
r: params.r || utils_1.Bytes32Zero,
s: params.s || utils_1.Bytes32Zero,
});
}
catch {
return undefined;
}
}
static matchingSell(seller, buyOrder) {
try {
if (buyOrder.side !== types_1.Side.BUY) {
throw new Error("Invalid buy order side");
}
if (buyOrder.replacementPattern !== REPLACEMENT_PATTERN_BUY) {
throw new Error("Invalid buy order replacement pattern");
}
// Notice: we don't support dutch auction buys
if (buyOrder.saleKind !== types_1.SaleKind.FIXED_PRICE) {
throw new Error("Unsupported sale kind");
}
// The buy order's calldata must have the following format:
// "transferFrom(address(0), buyer, tokenId)"
const buyCalldata = new abi_1.Interface(Erc721_json_1.default).decodeFunctionData("transferFrom", buyOrder.calldata);
if (buyCalldata.to.toLowerCase() !== buyOrder.maker.toLowerCase()) {
throw new Error("Invalid buy order calldata");
}
return order_1.default.normalize({
exchange: buyOrder.exchange,
maker: seller,
taker: buyOrder.maker,
makerRelayerFee: buyOrder.makerRelayerFee,
takerRelayerFee: buyOrder.takerRelayerFee,
feeRecipient: constants_1.AddressZero,
side: types_1.Side.SELL,
saleKind: types_1.SaleKind.FIXED_PRICE,
target: buyOrder.target,
howToCall: types_1.HowToCall.CALL,
calldata: new abi_1.Interface(Erc721_json_1.default).encodeFunctionData("transferFrom", [seller, constants_1.AddressZero, buyCalldata.tokenId]),
replacementPattern: REPLACEMENT_PATTERN_SELL,
staticTarget: constants_1.AddressZero,
staticExtradata: "0x",
paymentToken: buyOrder.paymentToken,
basePrice: buyOrder.basePrice,
extra: "0",
listingTime: (0, utils_1.getListingTime)().toString(),
expirationTime: "0",
salt: (0, utils_1.getSalt)().toString(),
v: 0,
r: utils_1.Bytes32Zero,
s: utils_1.Bytes32Zero,
});
}
catch {
return undefined;
}
}
static matchingBuy(buyer, sellOrder) {
try {
if (sellOrder.side !== types_1.Side.SELL) {
throw new Error("Invalid sell order side");
}
if (sellOrder.replacementPattern !== REPLACEMENT_PATTERN_SELL) {
throw new Error("Invalid sell order replacement pattern");
}
// The sell order's calldata must have the following format:
// "transferFrom(seller, address(0), tokenId)"
const sellCalldata = new abi_1.Interface(Erc721_json_1.default).decodeFunctionData("transferFrom", sellOrder.calldata);
if (sellCalldata.from.toLowerCase() !== sellOrder.maker.toLowerCase()) {
throw new Error("Invalid buy order calldata");
}
// As in https://github.com/ProjectWyvern/wyvern-ethereum/blob/bfca101b2407e4938398fccd8d1c485394db7e01/contracts/exchange/SaleKindInterface.sol#L70-L87
const listingTime = (0, utils_1.getListingTime)();
const basePrice = sellOrder.saleKind === types_1.SaleKind.FIXED_PRICE
? (0, utils_1.bn)(sellOrder.basePrice)
: (0, utils_1.bn)(sellOrder.basePrice).sub((0, utils_1.bn)(sellOrder.extra)
.mul(listingTime.sub((0, utils_1.bn)(sellOrder.listingTime)))
.div((0, utils_1.bn)(sellOrder.expirationTime).sub((0, utils_1.bn)(sellOrder.listingTime))));
return order_1.default.normalize({
exchange: sellOrder.exchange,
maker: buyer,
taker: sellOrder.maker,
makerRelayerFee: sellOrder.makerRelayerFee,
takerRelayerFee: sellOrder.takerRelayerFee,
feeRecipient: constants_1.AddressZero,
side: types_1.Side.BUY,
saleKind: types_1.SaleKind.FIXED_PRICE,
target: sellOrder.target,
howToCall: types_1.HowToCall.CALL,
calldata: new abi_1.Interface(Erc721_json_1.default).encodeFunctionData("transferFrom", [constants_1.AddressZero, buyer, sellCalldata.tokenId]),
replacementPattern: REPLACEMENT_PATTERN_BUY,
staticTarget: constants_1.AddressZero,
staticExtradata: "0x",
paymentToken: sellOrder.paymentToken,
basePrice: basePrice.toString(),
extra: "0",
listingTime: listingTime.toString(),
expirationTime: "0",
salt: (0, utils_1.getSalt)().toString(),
v: 0,
r: utils_1.Bytes32Zero,
s: utils_1.Bytes32Zero,
});
}
catch {
return undefined;
}
}
}
exports.default = SingleItemErc721OrderBuilder;
//# sourceMappingURL=single-item.js.map