Skip to content

Commit

Permalink
add offererSignature and a way to derive it from the connected ethers…
Browse files Browse the repository at this point in the history
… signer (#1461)
  • Loading branch information
ryanio authored May 9, 2024
1 parent b143166 commit 6b82f31
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opensea-js",
"version": "7.1.8",
"version": "7.1.9",
"description": "TypeScript SDK for the OpenSea marketplace helps developers build new experiences using NFTs and our marketplace data",
"license": "MIT",
"author": "OpenSea Developers",
Expand Down
8 changes: 7 additions & 1 deletion src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,16 +639,22 @@ export class OpenSeaAPI {
* @param protocolAddress The Seaport address for the order.
* @param orderHash The order hash, or external identifier, of the order.
* @param chain The chain where the order is located.
* @param offererSignature An EIP-712 signature from the offerer of the order.
* If this is not provided, the user associated with the API Key will be checked instead.
* The signature must be a EIP-712 signature consisting of the order's Seaport contract's
* name, version, address, and chain. The struct to sign is `OrderHash` containing a
* single bytes32 field.
* @returns The response from the API.
*/
public async offchainCancelOrder(
protocolAddress: string,
orderHash: string,
chain: Chain = this.chain,
offererSignature?: string,
): Promise<CancelOrderResponse> {
const response = await this.post<CancelOrderResponse>(
getCancelOrderPath(chain, protocolAddress, orderHash),
{},
{ offererSignature },
);
return response;
}
Expand Down
84 changes: 76 additions & 8 deletions src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
isTestChain,
basisPointsForFee,
totalBasisPointsForFees,
getChainId,
} from "./utils/utils";

/**
Expand Down Expand Up @@ -916,21 +917,81 @@ export class OpenSeaSDK {
);
}

private _getSeaportVersion(protocolAddress: string) {
const protocolAddressChecksummed = ethers.getAddress(protocolAddress);
switch (protocolAddressChecksummed) {
case CROSS_CHAIN_SEAPORT_V1_6_ADDRESS:
return "1.6";
case CROSS_CHAIN_SEAPORT_V1_5_ADDRESS:
return "1.5";
default:
throw new Error("Unknown or unsupported protocol address");
}
}

/**
* Get the offerer signature for canceling an order offchain.
* The signature will only be valid if the signer address is the address of the order's offerer.
*/
private async _getOffererSignature(
protocolAddress: string,
orderHash: string,
chain: Chain,
) {
const chainId = getChainId(chain);
const name = "Seaport";
const version = this._getSeaportVersion(protocolAddress);

if (
typeof (this._signerOrProvider as Signer).signTypedData == "undefined"
) {
throw new Error(
"Please pass an ethers Signer into this sdk to derive an offerer signature",
);
}

return (this._signerOrProvider as Signer).signTypedData(
{ chainId, name, version, verifyingContract: protocolAddress },
{ OrderHash: [{ name: "orderHash", type: "bytes32" }] },
{ orderHash },
);
}

/**
* Offchain cancel an order, offer or listing, by its order hash when protected by the SignedZone.
* Protocol and Chain are required to prevent hash collisions.
* Please note cancellation is only assured if a fulfillment signature was not vended prior to cancellation.
* @param protocolAddress The Seaport address for the order.
* @param orderJash The order hash, or external identifier, of the order.
* @param orderHash The order hash, or external identifier, of the order.
* @param chain The chain where the order is located.
* @param offererSignature An EIP-712 signature from the offerer of the order.
* If this is not provided, the user associated with the API Key will be checked instead.
* The signature must be a EIP-712 signature consisting of the order's Seaport contract's
* name, version, address, and chain. The struct to sign is `OrderHash` containing a
* single bytes32 field.
* @param useSignerToDeriveOffererSignature Derive the offererSignature from the Ethers signer passed into this sdk.
* @returns The response from the API.
*/
public async offchainCancelOrder(
protocolAddress: string,
orderHash: string,
chain: Chain = this.chain,
offererSignature?: string,
useSignerToDeriveOffererSignature?: boolean,
) {
return this.api.offchainCancelOrder(protocolAddress, orderHash, chain);
if (useSignerToDeriveOffererSignature) {
offererSignature = await this._getOffererSignature(
protocolAddress,
orderHash,
chain,
);
}
return this.api.offchainCancelOrder(
protocolAddress,
orderHash,
chain,
offererSignature,
);
}

/**
Expand Down Expand Up @@ -1220,12 +1281,8 @@ export class OpenSeaSDK {
this._emitter.emit(event, data);
}

/**
* Throws an error if an account is not available through the provider.
* @param accountAddress The account address to check is available.
*/
private async _requireAccountIsAvailable(accountAddress: string) {
const accountAddressChecksummed = ethers.getAddress(accountAddress);
/** Get the accounts available from the signer or provider. */
private async _getAvailableAccounts() {
const availableAccounts: string[] = [];

if ("address" in this._signerOrProvider) {
Expand All @@ -1237,6 +1294,17 @@ export class OpenSeaSDK {
availableAccounts.push(...addresses);
}

return availableAccounts;
}

/**
* Throws an error if an account is not available through the provider.
* @param accountAddress The account address to check is available.
*/
private async _requireAccountIsAvailable(accountAddress: string) {
const accountAddressChecksummed = ethers.getAddress(accountAddress);
const availableAccounts = await this._getAvailableAccounts();

if (availableAccounts.includes(accountAddressChecksummed)) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export interface OpenSeaAPIConfig {

/**
* Each of the possible chains that OpenSea supports.
* ⚠️NOTE: When adding to this list, also add to the util function `getWETHAddress`
* ⚠️NOTE: When adding to this list, also add to the util functions `getChainId` and `getWETHAddress`
*/
export enum Chain {
// Mainnet Chains
Expand Down
49 changes: 49 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,55 @@ export const getAssetItemType = (tokenStandard: TokenStandard) => {
}
};

export const getChainId = (chain: Chain) => {
switch (chain) {
case Chain.Mainnet:
return "1";
case Chain.Polygon:
return "137";
case Chain.Amoy:
return "80002";
case Chain.Sepolia:
return "11155111";
case Chain.Klaytn:
return "8217";
case Chain.Baobab:
return "1001";
case Chain.Avalanche:
return "43114";
case Chain.Fuji:
return "43113";
case Chain.BNB:
return "56";
case Chain.BNBTestnet:
return "97";
case Chain.Arbitrum:
return "42161";
case Chain.ArbitrumNova:
return "42170";
case Chain.ArbitrumSepolia:
return "421614";
case Chain.Blast:
return "238";
case Chain.BlastSepolia:
return "168587773";
case Chain.Base:
return "8453";
case Chain.BaseSepolia:
return "84532";
case Chain.Optimism:
return "10";
case Chain.OptimismSepolia:
return "11155420";
case Chain.Zora:
return "7777777";
case Chain.ZoraSepolia:
return "999999999";
default:
throw new Error(`Unknown chainId for ${chain}`);
}
};

export const getWETHAddress = (chain: Chain) => {
switch (chain) {
case Chain.Mainnet:
Expand Down
19 changes: 18 additions & 1 deletion test/integration/postOrder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,11 @@ suite("SDK: order posting", () => {
paymentTokenAddress,
};
const offerResponse = await sdk.createCollectionOffer(postOrderRequest);
expect(offerResponse).to.exist.and.to.have.property("protocol_address");
expect(offerResponse).to.exist.and.to.have.property("protocol_data");
expect(offerResponse).to.exist.and.to.have.property("order_hash");

// Cancel the order
// Cancel the order using self serve API key tied to the offerer
const { protocol_address, order_hash } = offerResponse!;
const cancelResponse = await sdk.offchainCancelOrder(
protocol_address,
Expand All @@ -139,7 +141,22 @@ suite("SDK: order posting", () => {
};
const offerResponse =
await sdkPolygon.createCollectionOffer(postOrderRequest);
expect(offerResponse).to.exist.and.to.have.property("protocol_address");
expect(offerResponse).to.exist.and.to.have.property("protocol_data");
expect(offerResponse).to.exist.and.to.have.property("order_hash");

// Cancel the order using the offerer signature, deriving it from the ethers signer
const { protocol_address, order_hash } = offerResponse!;
const cancelResponse = await sdkPolygon.offchainCancelOrder(
protocol_address,
order_hash,
undefined,
undefined,
true,
);
expect(cancelResponse).to.exist.and.to.have.property(
"last_signature_issued_valid_until",
);
});

test("Post Trait Offer - Ethereum", async () => {
Expand Down

0 comments on commit 6b82f31

Please sign in to comment.