Skip to content

Commit

Permalink
add multichain support to createBuyOrder/createSellOrder (#1070)
Browse files Browse the repository at this point in the history
* add multichain support to createBuyOrder/createSellOrder

* add new integration test env vars

* bump package.json version
  • Loading branch information
ryanio authored Jul 7, 2023
1 parent 891022f commit 4d9eaac
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 79 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,12 @@ jobs:
env:
OPENSEA_API_KEY: ${{ secrets.OPENSEA_API_KEY }}
ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }}
ALCHEMY_API_KEY_POLYGON: ${{ secrets.ALCHEMY_API_KEY_POLYGON }}
WALLET_PRIV_KEY: ${{ secrets.WALLET_PRIV_KEY }}
SELL_ORDER_CONTRACT_ADDRESS: ${{ secrets.SELL_ORDER_CONTRACT_ADDRESS }}
SELL_ORDER_TOKEN_ID: ${{ secrets.SELL_ORDER_TOKEN_ID }}
SELL_ORDER_CONTRACT_ADDRESS_POLYGON: ${{ secrets.SELL_ORDER_CONTRACT_ADDRESS_POLYGON }}
SELL_ORDER_TOKEN_ID_POLYGON: ${{ secrets.SELL_ORDER_TOKEN_ID_POLYGON }}
run: npm run test:integration

test-earliest-node-engine-support:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opensea-js",
"version": "6.1.0",
"version": "6.1.1",
"description": "JavaScript SDK for the OpenSea marketplace helps developers build new experiences using NFTs and our marketplace data!",
"license": "MIT",
"author": "OpenSea Developers",
Expand Down Expand Up @@ -33,7 +33,7 @@
"prettier:check:package.json": "prettier-package-json --list-different",
"prettier:fix": "prettier --write .",
"test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' TS_NODE_TRANSPILE_ONLY=true nyc mocha -r ts-node/register test/**/*.spec.ts --exclude test/integration/**/*.ts --timeout 15000",
"test:integration": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' TS_NODE_TRANSPILE_ONLY=true nyc mocha -r ts-node/register -r dotenv/config test/integration/**/*.spec.ts --timeout 15000"
"test:integration": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' TS_NODE_TRANSPILE_ONLY=true nyc mocha -r ts-node/register -r dotenv/config test/integration/**/*.spec.ts --timeout 25000"
},
"types": "lib/index.d.ts",
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export type GetNFTResponse = {
nft: NFT;
};

type NFT = {
export type NFT = {
identifier: string;
collection: string;
contract: string;
Expand Down
110 changes: 72 additions & 38 deletions src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "ethers";
import { parseEther } from "ethers/lib/utils";
import { OpenSeaAPI } from "./api/api";
import { PostOfferResponse } from "./api/types";
import { PostOfferResponse, NFT } from "./api/types";
import { INVERSE_BASIS_POINT, DEFAULT_ZONE } from "./constants";
import {
constructPrivateListingCounterOrder,
Expand All @@ -42,6 +42,7 @@ import {
OpenSeaCollection,
OrderSide,
TokenStandard,
OpenSeaFungibleToken,
} from "./types";
import {
delay,
Expand Down Expand Up @@ -301,6 +302,23 @@ export class OpenSeaSDK {
}));
}

private getNFTItems(
nfts: NFT[],
quantities: BigNumber[] = []
): CreateInputItem[] {
return nfts.map((nft, index) => ({
itemType: getAssetItemType(
nft.token_standard.toUpperCase() as TokenStandard
),
token:
getAddressAfterRemappingSharedStorefrontAddressToLazyMintAdapterAddress(
nft.contract
),
identifier: nft.identifier ?? undefined,
amount: quantities[index].toString() ?? "1",
}));
}

/**
* Create a buy order to make an offer on an asset.
* @param options Options for creating the buy order
Expand Down Expand Up @@ -343,9 +361,13 @@ export class OpenSeaSDK {
}
paymentTokenAddress = paymentTokenAddress ?? getWETHAddress(this.chain);

const openseaAsset = await this.api.getAsset(asset);
const considerationAssetItems = this.getAssetItems(
[openseaAsset],
const { nft } = await this.api.getNFT(
this.chain,
asset.tokenAddress,
asset.tokenId
);
const considerationAssetItems = this.getNFTItems(
[nft],
[BigNumber.from(quantity ?? 1)]
);

Expand All @@ -356,12 +378,13 @@ export class OpenSeaSDK {
startAmount
);

const { openseaSellerFees, collectionSellerFees: collectionSellerFees } =
await this.getFees({
collection: openseaAsset.collection,
paymentTokenAddress,
startAmount: basePrice,
});
const collection = await this.api.getCollection(nft.collection);

const { openseaSellerFees, collectionSellerFees } = await this.getFees({
collection,
paymentTokenAddress,
startAmount: basePrice,
});
const considerationFeeItems = [
...openseaSellerFees,
...collectionSellerFees,
Expand Down Expand Up @@ -440,16 +463,14 @@ export class OpenSeaSDK {
if (!asset.tokenId) {
throw new Error("Asset must have a tokenId");
}
//TODO: Make this function multichain compatible
if (this.chain != Chain.Mainnet && this.chain != Chain.Goerli) {
throw new Error(
`Creating orders on ${this.chain} not yet supported by the SDK.`
);
}

const openseaAsset = await this.api.getAsset(asset);
const offerAssetItems = this.getAssetItems(
[openseaAsset],
const { nft } = await this.api.getNFT(
this.chain,
asset.tokenAddress,
asset.tokenId
);
const offerAssetItems = this.getNFTItems(
[nft],
[BigNumber.from(quantity ?? 1)]
);

Expand All @@ -461,16 +482,15 @@ export class OpenSeaSDK {
endAmount ?? undefined
);

const {
sellerFee,
openseaSellerFees,
collectionSellerFees: collectionSellerFees,
} = await this.getFees({
collection: openseaAsset.collection,
paymentTokenAddress,
startAmount: basePrice,
endAmount: endPrice,
});
const collection = await this.api.getCollection(nft.collection);

const { sellerFee, openseaSellerFees, collectionSellerFees } =
await this.getFees({
collection,
paymentTokenAddress,
startAmount: basePrice,
endAmount: endPrice,
});
const considerationFeeItems = [
sellerFee,
...openseaSellerFees,
Expand Down Expand Up @@ -550,13 +570,12 @@ export class OpenSeaSDK {
expirationTime ?? getMaxOrderExpirationTimestamp(),
amount
);
const { openseaSellerFees, collectionSellerFees: collectionSellerFees } =
await this.getFees({
collection,
paymentTokenAddress,
startAmount: basePrice,
endAmount: basePrice,
});
const { openseaSellerFees, collectionSellerFees } = await this.getFees({
collection,
paymentTokenAddress,
startAmount: basePrice,
endAmount: basePrice,
});

const considerationItems = [
convertedConsiderationItem,
Expand Down Expand Up @@ -938,7 +957,7 @@ export class OpenSeaSDK {
englishAuctionReservePrice?: BigNumberish
) {
const isEther = tokenAddress === ethers.constants.AddressZero;
let paymentToken;
let paymentToken: OpenSeaFungibleToken | undefined;
if (!isEther) {
const { tokens } = await this.api.getPaymentTokens({
address: tokenAddress.toLowerCase(),
Expand Down Expand Up @@ -971,7 +990,22 @@ export class OpenSeaSDK {
throw new Error(`Starting price must be a number >= 0`);
}
if (!isEther && !paymentToken) {
throw new Error(`No ERC-20 token found for ${tokenAddress}`);
try {
if (
tokenAddress.toLowerCase() == getWETHAddress(this.chain).toLowerCase()
) {
paymentToken = {
name: "Wrapped Ether",
symbol: "WETH",
decimals: 18,
address: tokenAddress,
};
}
} catch (error) {
throw new Error(
`No ERC-20 token found for ${tokenAddress}, only WETH is currently supported for chains other than Mainnet Ethereum`
);
}
}
if (isEther && waitingForBestCounterOrder) {
throw new Error(
Expand Down
17 changes: 17 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,23 @@ export const getWETHAddress = (chain: Chain) => {
return "0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6";
case Chain.Sepolia:
return "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14";
case Chain.Klaytn:
return "0xfd844c2fca5e595004b17615f891620d1cb9bbb2";
case Chain.Baobab:
return "0x9330dd6713c8328a8d82b14e3f60a0f0b4cc7bfb";
case Chain.Avalanche:
return "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7";
case Chain.Fuji:
return "0xd00ae08403B9bbb9124bB305C09058E32C39A48c";
case Chain.BNB:
return "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c";
case Chain.BNBTestnet:
return "0xae13d989dac2f0debff460ac112a837c89baa7cd";
// OP Chains have weth at the same address
case Chain.Optimism:
case Chain.Zora:
case Chain.ZoraTestnet:
return "0x4200000000000000000000000000000000000006";
default:
throw new Error(`WETH is not supported on ${chain}`);
}
Expand Down
19 changes: 12 additions & 7 deletions test/integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@ These tests were built to test the order posting functionality of the SDK. Signi
Environment variables for integration tests are set using `.env`. This file is not in the source code for the repository so you will need to create a file with the following fields:

```bash
OPENSEA_API_KEY = "" # OpenSea API Key
ALCHEMY_API_KEY = "" # Alchemy API Key
WALLET_PRIV_KEY = "0x" # Wallet private key
OPENSEA_API_KEY="" # OpenSea API Key
ALCHEMY_API_KEY="" # Alchemy API Key for ETH Mainnet
ALCHEMY_API_KEY_POLYGON="" # Alchemy API Key for Polygon
WALLET_PRIV_KEY="0x" # Wallet private key

# The following needs to be an NFT owned by the wallet address derived from WALLET_PRIV_KEY
SELL_ORDER_CONTRACT_ADDRESS = "0x"
SELL_ORDER_TOKEN_ID = "123"
## Mainnet
SELL_ORDER_CONTRACT_ADDRESS="0x"
SELL_ORDER_TOKEN_ID="123"
## Polygon
SELL_ORDER_CONTRACT_ADDRESS_POLYGON="0x"
SELL_ORDER_TOKEN_ID_POLYGON="123"
```

Optional:

```bash
OFFER_AMOUNT = "0.004" # Defaults to 0.004
LISTING_AMOUNT = "40" # Defaults to 40
OFFER_AMOUNT="0.004" # Defaults to 0.004
LISTING_AMOUNT="40" # Defaults to 40
```

#### WETH Tests
Expand Down
Loading

0 comments on commit 4d9eaac

Please sign in to comment.