Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f13408a33d | ||
|
|
74b19012c2 | ||
|
|
8f85868324 | ||
|
|
448b357ed4 | ||
|
|
912738bd0a | ||
|
|
f1aecc3049 | ||
|
|
0b38dac852 | ||
|
|
440a4ade95 | ||
|
|
29d0c62610 | ||
|
|
f295ec67b4 | ||
|
|
1686fb4b2c | ||
|
|
85dd18e8f0 | ||
|
|
ec1f2324c7 | ||
|
|
b59a31c365 | ||
|
|
d29f7e78f0 | ||
|
|
3e2b392b6b | ||
|
|
cf96333fa2 | ||
|
|
4213cfee77 | ||
|
|
25ef4be50f | ||
|
|
ab1f8bb6f6 | ||
|
|
5f8ec45af7 | ||
|
|
b45d761f8f | ||
|
|
04de5b2783 | ||
|
|
dee4596549 | ||
|
|
3547901ddd | ||
|
|
fe7ab1c5d3 | ||
|
|
bdd9ed2298 | ||
|
|
0977454d45 | ||
|
|
19b8e18b6b | ||
|
|
7c0060b7fd |
@@ -38,6 +38,7 @@
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
|
||||
"eslint": "^8.21.0",
|
||||
"mocha": "^10.2.0",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.7.1",
|
||||
"turbo": "^1.4.6",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 3.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#533](https://github.com/thirdweb-dev/js/pull/533) [`dee4596`](https://github.com/thirdweb-dev/js/commit/dee45965496d5d0298944031dd13a4345f9e1683) Thanks [@adam-maj](https://github.com/adam-maj)! - Remove next major version pin on auth
|
||||
|
||||
## 3.0.2
|
||||
|
||||
## 3.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
Install the latest version of the SDK with either `npm` or `yarn`:
|
||||
|
||||
```shell
|
||||
npm install @thirdweb-dev/auth @thirdweb-dev/sdk ethers
|
||||
npm install @thirdweb-dev/auth
|
||||
```
|
||||
|
||||
```shell
|
||||
yarn add @thirdweb-dev/auth @thirdweb-dev/sdk ethers
|
||||
yarn add @thirdweb-dev/auth
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Auth Docs](https://portal.thirdweb.com/auth)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.3",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"exports": {
|
||||
@@ -66,7 +66,6 @@
|
||||
"@solana/web3.js": "^1.73.0",
|
||||
"@swc-node/register": "^1.5.4",
|
||||
"@swc/core": "^1.3.23",
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"@thirdweb-dev/wallets": "*",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/chai": "^4.3.4",
|
||||
@@ -97,13 +96,12 @@
|
||||
"peerDependencies": {
|
||||
"@noble/ed25519": "^1.7.1",
|
||||
"@solana/web3.js": "^1.73.0",
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"bs58": "^5.0.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"ethers": ">=5.5.1",
|
||||
"express": "^4.18.1",
|
||||
"next": "^12.2.0",
|
||||
"next-auth": "^4.10.3",
|
||||
"ethers": "^5",
|
||||
"express": "^4",
|
||||
"next": "^12 || ^13",
|
||||
"next-auth": "^4",
|
||||
"tweetnacl": "^1.0.3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -140,4 +138,4 @@
|
||||
"uuid": "^9.0.0",
|
||||
"zod": "^3.20.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,50 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.9.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`448b357`](https://github.com/thirdweb-dev/js/commit/448b357ed4b864e05e531837ca3bc97fbcab3291)]:
|
||||
- @thirdweb-dev/sdk@3.8.1
|
||||
|
||||
## 0.9.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#549](https://github.com/thirdweb-dev/js/pull/549) [`912738b`](https://github.com/thirdweb-dev/js/commit/912738bd0afdf81e118c720811911ba3d1979ac0) Thanks [@kumaryash90](https://github.com/kumaryash90)! - add staking bases to CLI
|
||||
|
||||
- Updated dependencies [[`0b38dac`](https://github.com/thirdweb-dev/js/commit/0b38dac852e54d0bccd263631f4342aee39d7c29), [`29d0c62`](https://github.com/thirdweb-dev/js/commit/29d0c62610cdb88f75d19574bddcb028b5c40393), [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c), [`f295ec6`](https://github.com/thirdweb-dev/js/commit/f295ec67b4cc422c51beda94cbc4d54a31ce9566), [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c), [`f1aecc3`](https://github.com/thirdweb-dev/js/commit/f1aecc30499e39fb23a205bade939bd939f0d0e4)]:
|
||||
- @thirdweb-dev/sdk@3.8.0
|
||||
|
||||
## 0.9.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ec1f232`](https://github.com/thirdweb-dev/js/commit/ec1f2324c7a0e58c2d2a209cfe0d194807ba5b70), [`d29f7e7`](https://github.com/thirdweb-dev/js/commit/d29f7e78f09063630e812dd41164d8a41d4f97dd)]:
|
||||
- @thirdweb-dev/sdk@3.7.4
|
||||
|
||||
## 0.9.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#537](https://github.com/thirdweb-dev/js/pull/537) [`cf96333`](https://github.com/thirdweb-dev/js/commit/cf96333fa2f46a4a73a822a0b1248891cc890267) Thanks [@adam-maj](https://github.com/adam-maj)! - Fix cache error on windows OS
|
||||
|
||||
## 0.9.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#528](https://github.com/thirdweb-dev/js/pull/528) [`ab1f8bb`](https://github.com/thirdweb-dev/js/commit/ab1f8bb6f658dadbca2cb53decbc893050217407) Thanks [@adam-maj](https://github.com/adam-maj)! - Added --skip-update-check flag to CLI to prevent checking for new versions and automatically updating before running commands. Also, only check for check for updates every 24 hours.
|
||||
|
||||
- Updated dependencies [[`b45d761`](https://github.com/thirdweb-dev/js/commit/b45d761f8f2695b48312ec6eff15e0549e6a8a41), [`5f8ec45`](https://github.com/thirdweb-dev/js/commit/5f8ec45af7c81d075a84846098f38cad90d03fb9), [`04de5b2`](https://github.com/thirdweb-dev/js/commit/04de5b278362573991140c3a496de7451e5c770d)]:
|
||||
- @thirdweb-dev/sdk@3.7.3
|
||||
|
||||
## 0.9.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`19b8e18`](https://github.com/thirdweb-dev/js/commit/19b8e18b6bf68e374c817fefbf2fd29ac5573052)]:
|
||||
- @thirdweb-dev/sdk@3.7.2
|
||||
|
||||
## 0.9.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+11
-2
@@ -101,8 +101,17 @@ $ npx thirdweb@latest release
|
||||
|
||||
The simplest way to work on the CLI locally is to:
|
||||
|
||||
1. Install the package locally
|
||||
2. Run the `build:watch` command to compile any changes in realtime
|
||||
1. Install the package locally by running
|
||||
|
||||
```bash
|
||||
git clone https://github.com/thirdweb-dev/js.git
|
||||
```
|
||||
2. From the project root run
|
||||
|
||||
```bash
|
||||
yarn && yarn build
|
||||
```
|
||||
3. From inside `js/packages/cli` Run the `build:watch` command to compile any changes in real time
|
||||
|
||||
```bash
|
||||
$ npm install -g ./
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.9.12",
|
||||
"version": "0.9.18",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -41,6 +41,7 @@
|
||||
"prompts": "^2.4.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"solc": "0.8.17",
|
||||
"sync-disk-cache": "^2.1.0",
|
||||
"tar": "^6.1.11",
|
||||
"tslog": "^3.3.3",
|
||||
"update-notifier": "^6.0.2",
|
||||
|
||||
+165
-129
@@ -3,6 +3,7 @@ import { detectExtensions } from "../common/feature-detector";
|
||||
import { processProject } from "../common/processor";
|
||||
import { cliVersion, pkg } from "../constants/urls";
|
||||
import { info, logger, spinner } from "../core/helpers/logger";
|
||||
import { CacheEntry } from "../core/types/cache";
|
||||
import { twCreate } from "../create/command";
|
||||
import { deploy } from "../deploy";
|
||||
import { findPackageInstallation } from "../helpers/detect-local-packages";
|
||||
@@ -13,12 +14,14 @@ import { exec, spawn } from "child_process";
|
||||
import { Command } from "commander";
|
||||
import open from "open";
|
||||
import prompts from "prompts";
|
||||
import Cache from "sync-disk-cache";
|
||||
|
||||
const main = async () => {
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
const skipIntro = process.env.THIRDWEB_CLI_SKIP_INTRO === "true";
|
||||
|
||||
const program = new Command();
|
||||
const cache = new Cache("thirdweb:cli");
|
||||
|
||||
// yes this has to look like this, eliminates whitespace
|
||||
if (!skipIntro) {
|
||||
@@ -37,7 +40,168 @@ const main = async () => {
|
||||
program
|
||||
.name("thirdweb-cli")
|
||||
.description("Official thirdweb command line interface")
|
||||
.version(cliVersion, "-v, --version");
|
||||
.version(cliVersion, "-v, --version")
|
||||
.option("--skip-update-check", "Skip check for auto updates")
|
||||
.hook("preAction", async () => {
|
||||
if (skipIntro || program.opts().skipUpdateCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldCheckVersion = true;
|
||||
try {
|
||||
const lastCheckCache: CacheEntry = cache.get("last-version-check");
|
||||
|
||||
if (lastCheckCache.isCached) {
|
||||
const lastVersionCheck = new Date(lastCheckCache.value);
|
||||
// Don't check for updates if already checked within past 24 hours
|
||||
if (
|
||||
new Date().getTime() - lastVersionCheck.getTime() <
|
||||
1000 * 60 * 60 * 24
|
||||
) {
|
||||
shouldCheckVersion = false;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
if (!shouldCheckVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
const versionSpinner = spinner("Checking for updates...");
|
||||
await import("update-notifier").then(
|
||||
async ({ default: updateNotifier }) => {
|
||||
const notifier = updateNotifier({
|
||||
pkg,
|
||||
shouldNotifyInNpmScript: true,
|
||||
// check every time while we're still building the CLI
|
||||
updateCheckInterval: 0,
|
||||
});
|
||||
|
||||
const versionInfo = await notifier.fetchInfo();
|
||||
versionSpinner.stop();
|
||||
|
||||
try {
|
||||
// Set cache to prevent checking for updates again for 24 hours
|
||||
cache.set("last-version-check", new Date().toISOString());
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
if (versionInfo.type !== "latest") {
|
||||
const res = await prompts({
|
||||
type: "toggle",
|
||||
name: "upgrade",
|
||||
message: `A new version of the CLI is available. Would you like to upgrade?`,
|
||||
initial: true,
|
||||
active: "yes",
|
||||
inactive: "no",
|
||||
});
|
||||
|
||||
if (res.upgrade) {
|
||||
const updateSpinner = spinner(
|
||||
`Upgrading CLI to version ${versionInfo.latest}...`,
|
||||
);
|
||||
|
||||
const clonedEnvironment = { ...process.env };
|
||||
clonedEnvironment.THIRDWEB_CLI_SKIP_INTRO = "true";
|
||||
|
||||
const installation = await findPackageInstallation();
|
||||
|
||||
// If the package isn't installed anywhere, just defer to npx thirdweb@latest
|
||||
if (!installation) {
|
||||
updateSpinner.succeed(
|
||||
`Now using CLI version ${versionInfo.latest}. Continuing execution...`,
|
||||
);
|
||||
|
||||
await new Promise((done, failed) => {
|
||||
const shell = spawn(
|
||||
`npx --yes thirdweb@latest ${process.argv
|
||||
.slice(2)
|
||||
.join(" ")}`,
|
||||
[],
|
||||
{ stdio: "inherit", shell: true, env: clonedEnvironment },
|
||||
);
|
||||
shell.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
done("");
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return process.exit(0);
|
||||
}
|
||||
|
||||
// Otherwise, get the correct command based on package manager and local vs. global
|
||||
let command = "";
|
||||
switch (installation.packageManager) {
|
||||
case "npm":
|
||||
command = installation.isGlobal
|
||||
? `npm install -g thirdweb`
|
||||
: `npm install thirdweb`;
|
||||
break;
|
||||
case "yarn":
|
||||
command = installation.isGlobal
|
||||
? `yarn global add thirdweb`
|
||||
: `yarn add thirdweb`;
|
||||
break;
|
||||
case "pnpm":
|
||||
command = installation.isGlobal
|
||||
? `pnpm add -g thirdweb@latest`
|
||||
: `pnpm add thirdweb@latest`;
|
||||
break;
|
||||
default:
|
||||
console.error(
|
||||
`Could not detect package manager in use, aborting automatic upgrade.\nIf you want to upgrade the CLI, please do it manually with your package manager.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await new Promise((done, failed) => {
|
||||
exec(command, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
failed(err);
|
||||
return;
|
||||
}
|
||||
|
||||
done({ stdout, stderr });
|
||||
});
|
||||
});
|
||||
|
||||
updateSpinner.succeed(
|
||||
`Successfully upgraded CLI to version ${versionInfo.latest}. Continuing execution...`,
|
||||
);
|
||||
|
||||
// If the package is installed globally with yarn or pnpm, then npx won't recognize it
|
||||
// So we need to make sure to run the command directly
|
||||
const executionCommand =
|
||||
!installation.isGlobal || installation.packageManager === "npm"
|
||||
? `npx thirdweb`
|
||||
: `thirdweb`;
|
||||
await new Promise((done, failed) => {
|
||||
const shell = spawn(
|
||||
`${executionCommand} ${process.argv.slice(2).join(" ")}`,
|
||||
[],
|
||||
{ stdio: "inherit", shell: true, env: clonedEnvironment },
|
||||
);
|
||||
shell.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
done("");
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
program
|
||||
.command("create [projectType] [projectPath]")
|
||||
@@ -201,134 +365,6 @@ const main = async () => {
|
||||
await detectExtensions(options);
|
||||
});
|
||||
|
||||
if (!skipIntro) {
|
||||
const versionSpinner = spinner("Checking for updates...");
|
||||
await import("update-notifier").then(
|
||||
async ({ default: updateNotifier }) => {
|
||||
const notifier = updateNotifier({
|
||||
pkg,
|
||||
shouldNotifyInNpmScript: true,
|
||||
// check every time while we're still building the CLI
|
||||
updateCheckInterval: 0,
|
||||
});
|
||||
|
||||
const versionInfo = await notifier.fetchInfo();
|
||||
versionSpinner.stop();
|
||||
|
||||
if (versionInfo.type !== "latest") {
|
||||
const res = await prompts({
|
||||
type: "toggle",
|
||||
name: "upgrade",
|
||||
message: `A new version of the CLI is available. Would you like to upgrade?`,
|
||||
initial: true,
|
||||
active: "yes",
|
||||
inactive: "no",
|
||||
});
|
||||
|
||||
if (res.upgrade) {
|
||||
const updateSpinner = spinner(
|
||||
`Upgrading CLI to version ${versionInfo.latest}...`,
|
||||
);
|
||||
|
||||
const clonedEnvironment = { ...process.env };
|
||||
clonedEnvironment.THIRDWEB_CLI_SKIP_INTRO = "true";
|
||||
|
||||
const installation = await findPackageInstallation();
|
||||
|
||||
// If the package isn't installed anywhere, just defer to npx thirdweb@latest
|
||||
if (!installation) {
|
||||
updateSpinner.succeed(
|
||||
`Now using CLI version ${versionInfo.latest}. Continuing execution...`,
|
||||
);
|
||||
|
||||
await new Promise((done, failed) => {
|
||||
const shell = spawn(
|
||||
`npx --yes thirdweb@latest ${process.argv
|
||||
.slice(2)
|
||||
.join(" ")}`,
|
||||
[],
|
||||
{ stdio: "inherit", shell: true, env: clonedEnvironment },
|
||||
);
|
||||
shell.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
done("");
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return process.exit(0);
|
||||
}
|
||||
|
||||
// Otherwise, get the correct command based on package manager and local vs. global
|
||||
let command = "";
|
||||
switch (installation.packageManager) {
|
||||
case "npm":
|
||||
command = installation.isGlobal
|
||||
? `npm install -g thirdweb`
|
||||
: `npm install thirdweb`;
|
||||
break;
|
||||
case "yarn":
|
||||
command = installation.isGlobal
|
||||
? `yarn global add thirdweb`
|
||||
: `yarn add thirdweb`;
|
||||
break;
|
||||
case "pnpm":
|
||||
command = installation.isGlobal
|
||||
? `pnpm add -g thirdweb@latest`
|
||||
: `pnpm add thirdweb@latest`;
|
||||
break;
|
||||
default:
|
||||
console.error(
|
||||
`Could not detect package manager in use, aborting automatic upgrade.\nIf you want to upgrade the CLI, please do it manually with your package manager.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await new Promise((done, failed) => {
|
||||
exec(command, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
failed(err);
|
||||
return;
|
||||
}
|
||||
|
||||
done({ stdout, stderr });
|
||||
});
|
||||
});
|
||||
|
||||
updateSpinner.succeed(
|
||||
`Successfully upgraded CLI to version ${versionInfo.latest}. Continuing execution...`,
|
||||
);
|
||||
|
||||
// If the package is installed globally with yarn or pnpm, then npx won't recognize it
|
||||
// So we need to make sure to run the command directly
|
||||
const executionCommand =
|
||||
!installation.isGlobal || installation.packageManager === "npm"
|
||||
? `npx thirdweb`
|
||||
: `thirdweb`;
|
||||
await new Promise((done, failed) => {
|
||||
const shell = spawn(
|
||||
`${executionCommand} ${process.argv.slice(2).join(" ")}`,
|
||||
[],
|
||||
{ stdio: "inherit", shell: true, env: clonedEnvironment },
|
||||
);
|
||||
shell.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
done("");
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await program.parseAsync();
|
||||
};
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ export async function processProject(
|
||||
if (projectType === "none") {
|
||||
if (command === "deploy") {
|
||||
info(
|
||||
"No contracts detected in this directory. Redirecting to the thirdweb contract deployment page.",
|
||||
"No contracts detected in this directory. Redirecting to the thirdweb contract explore page.",
|
||||
);
|
||||
return "https://thirdweb.com/contracts";
|
||||
return "https://thirdweb.com/explore";
|
||||
}
|
||||
|
||||
error("No detected contracts in this directory.");
|
||||
|
||||
@@ -112,6 +112,28 @@ contract Contract is ERC1155Base {
|
||||
)
|
||||
{}
|
||||
|
||||
}`,
|
||||
Staking721Base: `// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@thirdweb-dev/contracts/base/Staking721Base.sol";
|
||||
|
||||
contract Contract is Staking721Base {
|
||||
constructor(
|
||||
uint256 _timeUnit,
|
||||
uint256 _rewardsPerUnitTime,
|
||||
address _nftCollection,
|
||||
address _rewardToken,
|
||||
address _nativeTokenWrapper
|
||||
)
|
||||
Staking721Base(
|
||||
_timeUnit,
|
||||
_rewardsPerUnitTime,
|
||||
_nftCollection,
|
||||
_rewardToken,
|
||||
_nativeTokenWrapper
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
ERC1155SignatureMint: `// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity ^0.8.0;
|
||||
@@ -182,6 +204,28 @@ contract Contract is ERC1155Drop {
|
||||
_primarySaleRecipient
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
Staking1155Base: `// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@thirdweb-dev/contracts/base/Staking1155Base.sol";
|
||||
|
||||
contract Contract is Staking1155Base {
|
||||
constructor(
|
||||
uint256 _defaultTimeUnit,
|
||||
uint256 _defaultRewardsPerUnitTime,
|
||||
address _stakingToken,
|
||||
address _rewardToken,
|
||||
address _nativeTokenWrapper
|
||||
)
|
||||
Staking1155Base(
|
||||
_defaultTimeUnit,
|
||||
_defaultRewardsPerUnitTime,
|
||||
_stakingToken,
|
||||
_rewardToken,
|
||||
_nativeTokenWrapper
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
ERC20Base: `// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
@@ -288,6 +332,30 @@ contract Contract is ERC20DropVote {
|
||||
_primarySaleRecipient
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
Staking20Base: `// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@thirdweb-dev/contracts/base/Staking20Base.sol";
|
||||
|
||||
contract Contract is Staking20Base {
|
||||
constructor(
|
||||
uint256 _timeUnit,
|
||||
uint256 _rewardRatioNumerator,
|
||||
uint256 _rewardRatioDenominator,
|
||||
address _stakingToken,
|
||||
address _rewardToken,
|
||||
address _nativeTokenWrapper
|
||||
)
|
||||
Staking20Base(
|
||||
_timeUnit,
|
||||
_rewardRatioNumerator,
|
||||
_rewardRatioDenominator,
|
||||
_stakingToken,
|
||||
_rewardToken,
|
||||
_nativeTokenWrapper
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export type CacheEntry = {
|
||||
isCached: boolean;
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
declare module 'sync-disk-cache';
|
||||
declare module "solc";
|
||||
-1
@@ -1 +0,0 @@
|
||||
declare module "solc";
|
||||
@@ -1,5 +1,17 @@
|
||||
# @thirdweb-dev/contracts-js
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#474](https://github.com/thirdweb-dev/js/pull/474) [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Update contracts to 3.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#549](https://github.com/thirdweb-dev/js/pull/549) [`912738b`](https://github.com/thirdweb-dev/js/commit/912738bd0afdf81e118c720811911ba3d1979ac0) Thanks [@kumaryash90](https://github.com/kumaryash90)! - add staking bases to CLI
|
||||
|
||||
- [#544](https://github.com/thirdweb-dev/js/pull/544) [`f1aecc3`](https://github.com/thirdweb-dev/js/commit/f1aecc30499e39fb23a205bade939bd939f0d0e4) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Contracts v3.4.0
|
||||
|
||||
## 1.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/contracts-js",
|
||||
"version": "1.2.3",
|
||||
"version": "1.3.0",
|
||||
"main": "dist/thirdweb-dev-contracts-js.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js.esm.js",
|
||||
"license": "Apache-2.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/contracts-js",
|
||||
"dependencies": {
|
||||
"@thirdweb-dev/contracts": "3.3.0"
|
||||
"@thirdweb-dev/contracts": "3.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
@@ -34,6 +34,7 @@
|
||||
"clean": "rm -rf dist/",
|
||||
"typechain": "rm -rf ./src && typechain --target ethers-v5 --out-dir src '../../node_modules/@thirdweb-dev/contracts/abi/*.json'",
|
||||
"extract-abi": "node ./scripts/extract-abis.mjs",
|
||||
"build": "yarn typechain && tsc && yarn preconstruct build && yarn extract-abi"
|
||||
"build": "yarn typechain && tsc && yarn preconstruct build && yarn extract-abi",
|
||||
"push": "yalc push"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,29 @@
|
||||
# @thirdweb-dev/react-core
|
||||
|
||||
## 3.8.1
|
||||
|
||||
## 3.8.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#546](https://github.com/thirdweb-dev/js/pull/546) [`440a4ad`](https://github.com/thirdweb-dev/js/commit/440a4ade95874e696c589eaa7aae9f0fecc862be) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add missing marketplace v3 hooks
|
||||
|
||||
## 3.7.4
|
||||
|
||||
## 3.7.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`dee4596`](https://github.com/thirdweb-dev/js/commit/dee45965496d5d0298944031dd13a4345f9e1683)]:
|
||||
- @thirdweb-dev/auth@3.0.3
|
||||
|
||||
## 3.7.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/auth@3.0.2
|
||||
|
||||
## 3.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-core",
|
||||
"version": "3.7.1",
|
||||
"version": "3.8.1",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -21,14 +21,17 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type {
|
||||
AuctionListing,
|
||||
DirectListing,
|
||||
Marketplace,
|
||||
MarketplaceFilter,
|
||||
MarketplaceV3,
|
||||
NewAuctionListing,
|
||||
NewDirectListing,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import { ListingType } from "@thirdweb-dev/sdk";
|
||||
import { Marketplace } from "@thirdweb-dev/sdk/dist/declarations/src/evm/contracts/prebuilt-implementations/marketplace";
|
||||
import { BigNumberish } from "ethers";
|
||||
import { BigNumber, BigNumberish } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
import { DirectListingInputParams } from "@thirdweb-dev/sdk/dist/declarations/src/evm/schema/marketplacev3/direct-listings";
|
||||
import { EnglishAuctionInputParams } from "@thirdweb-dev/sdk/dist/declarations/src/evm/schema/marketplacev3/english-auctions";
|
||||
|
||||
/** **********************/
|
||||
/** READ HOOKS **/
|
||||
@@ -44,7 +47,7 @@ import invariant from "tiny-invariant";
|
||||
*
|
||||
* @param contract - an instance of a marketplace contract
|
||||
* @param listingId - the listing id to check
|
||||
* @returns a response object that includes an array of listings
|
||||
* @returns a response object that includes the desired listing
|
||||
* @beta
|
||||
*/
|
||||
export function useListing(
|
||||
@@ -66,6 +69,76 @@ export function useListing(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a specific direct listing from the marketplace v3.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: directListing, isLoading, error } = useListing(<YourMarketplaceV3ContractInstance>, <listingId>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace v3 contract
|
||||
* @param listingId - the listing id to check
|
||||
* @returns a response object that includes the desired direct listing
|
||||
* @internal
|
||||
*/
|
||||
export function useDirectListing(
|
||||
contract: RequiredParam<MarketplaceV3>,
|
||||
listingId: RequiredParam<BigNumberish>,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.directListings.getListing(
|
||||
contractAddress,
|
||||
listingId,
|
||||
),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
return contract.directListings.getListing(listingId);
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a specific english auction from the marketplace v3.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: englishAuction, isLoading, error } = useEnglishAuction(<YourMarketplaceV3ContractInstance>, <auctionId>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace v3 contract
|
||||
* @param auctionId - the auction id to check
|
||||
* @returns a response object that includes the desired english auction
|
||||
* @internal
|
||||
*/
|
||||
export function useEnglishAuction(
|
||||
contract: RequiredParam<MarketplaceV3>,
|
||||
auctionId: RequiredParam<BigNumberish>,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.englishAuctions.getAuction(
|
||||
contractAddress,
|
||||
auctionId,
|
||||
),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(auctionId, "No auction id provided");
|
||||
return contract.englishAuctions.getAuction(auctionId);
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a list all listings from your marketplace contract.
|
||||
*
|
||||
@@ -97,16 +170,152 @@ export function useListings(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a list all direct listings from your marketplace v3 contract.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: directListings, isLoading, error } = useDirectListings(<YourMarketplaceV3ContractInstance>, { start: 0, count: 100 });
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace v3 contract
|
||||
* @param filter - filter to pass to the query for the sake of pagination & filtering
|
||||
* @returns a response object that includes an array of direct listings
|
||||
* @internal
|
||||
*/
|
||||
export function useDirectListings(
|
||||
contract: RequiredParam<MarketplaceV3>,
|
||||
filter?: MarketplaceFilter,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.directListings.getAll(
|
||||
contractAddress,
|
||||
filter,
|
||||
),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.directListings.getAll(filter);
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a list all valid direct listings from your marketplace v3 contract.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: directListings, isLoading, error } = useValidDirectListings(<YourMarketplaceV3ContractInstance>, { start: 0, count: 100 });
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace v3 contract
|
||||
* @param filter - filter to pass to the query for the sake of pagination & filtering
|
||||
* @returns a response object that includes an array of direct listings
|
||||
* @internal
|
||||
*/
|
||||
export function useValidDirectListings(
|
||||
contract: RequiredParam<MarketplaceV3>,
|
||||
filter?: MarketplaceFilter,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.directListings.getAllValid(
|
||||
contractAddress,
|
||||
filter,
|
||||
),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.directListings.getAllValid(filter);
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a list all english auctions from your marketplace v3 contract.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: englishAuctions, isLoading, error } = useEnglishAuctions(<YourMarketplaceV3ContractInstance>, { start: 0, count: 100 });
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace v3 contract
|
||||
* @param filter - filter to pass to the query for the sake of pagination & filtering
|
||||
* @returns a response object that includes an array of english auctions
|
||||
* @internal
|
||||
*/
|
||||
export function useEnglishAuctions(
|
||||
contract: RequiredParam<MarketplaceV3>,
|
||||
filter?: MarketplaceFilter,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.englishAuctions.getAll(
|
||||
contractAddress,
|
||||
filter,
|
||||
),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.englishAuctions.getAll(filter);
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a list all valid english auctions from your marketplace v3 contract.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: englishAuctions, isLoading, error } = useValidEnglishAuctions(<YourMarketplaceV3ContractInstance>, { start: 0, count: 100 });
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace v3 contract
|
||||
* @param filter - filter to pass to the query for the sake of pagination & filtering
|
||||
* @returns a response object that includes an array of english auctions
|
||||
* @internal
|
||||
*/
|
||||
export function useValidEnglishAuctions(
|
||||
contract: RequiredParam<MarketplaceV3>,
|
||||
filter?: MarketplaceFilter,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.englishAuctions.getAllValid(
|
||||
contractAddress,
|
||||
filter,
|
||||
),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.englishAuctions.getAllValid(filter);
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a count of all listings on your marketplace contract.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: listings, isLoading, error } = useListings(<YourMarketplaceContractInstance>);
|
||||
* const { data: listingsCount, isLoading, error } = useListingsCount(<YourMarketplaceContractInstance>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace contract
|
||||
* @returns a response object that includes an array of listings
|
||||
* @returns a response object that includes the listing count
|
||||
* @beta
|
||||
*/
|
||||
export function useListingsCount(contract: RequiredParam<Marketplace>) {
|
||||
@@ -123,6 +332,64 @@ export function useListingsCount(contract: RequiredParam<Marketplace>) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a count of all direct listings on your marketplace v3 contract.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: listingsCount, isLoading, error } = useListingsCount(<YourMarketplaceV3ContractInstance>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace v3 contract
|
||||
* @returns a response object that includes the direct listings count
|
||||
* @internal
|
||||
*/
|
||||
export function useDirectListingsCount(contract: RequiredParam<MarketplaceV3>) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.directListings.getTotalCount(
|
||||
contractAddress,
|
||||
),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.directListings.getTotalCount();
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a count of all direct listings on your marketplace v3 contract.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: englishAuctionsCount, isLoading, error } = useEnglishAuctionsCount(<YourMarketplaceV3ContractInstance>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace v3 contract
|
||||
* @returns a response object that includes the direct english actions count
|
||||
* @internal
|
||||
*/
|
||||
export function useEnglishAuctionsCount(
|
||||
contract: RequiredParam<MarketplaceV3>,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.englishAuctions.getTotalCount(
|
||||
contractAddress,
|
||||
),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.englishAuctions.getTotalCount();
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a list active listings from your marketplace contract.
|
||||
*
|
||||
@@ -181,6 +448,10 @@ export function useWinningBid(
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
invariant(
|
||||
contract.auction.getWinningBid,
|
||||
"contract does not support auction.getWinningBid",
|
||||
);
|
||||
return contract.auction.getWinningBid(listingId);
|
||||
},
|
||||
{
|
||||
@@ -189,6 +460,44 @@ export function useWinningBid(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get a the winning bid for an auction listing from your marketplace v3 contract.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: winningBid, isLoading, error } = useWinningBid(<YourMarketplaceV3ContractInstance>, <listingId>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace contract
|
||||
* @param auctionId - the auction id to check
|
||||
* @returns a response object that includes the {@link Bid} that is winning the auction
|
||||
* @beta
|
||||
*/
|
||||
export function useEnglishAuctionWinningBid(
|
||||
contract: RequiredParam<MarketplaceV3>,
|
||||
auctionId: RequiredParam<BigNumberish>,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.auction.getWinningBid(
|
||||
contractAddress,
|
||||
auctionId,
|
||||
),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(auctionId, "No auction id provided");
|
||||
invariant(
|
||||
contract.englishAuctions.getWinningBid,
|
||||
"contract does not support englishAuctions.getWinningBid",
|
||||
);
|
||||
return contract.englishAuctions.getWinningBid(auctionId);
|
||||
},
|
||||
{
|
||||
enabled: !!contract && auctionId !== undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get the winner of an auction listing from your marketplace contract.
|
||||
*
|
||||
@@ -203,7 +512,7 @@ export function useWinningBid(
|
||||
* @beta
|
||||
*/
|
||||
export function useAuctionWinner(
|
||||
contract: RequiredParam<Marketplace>,
|
||||
contract: RequiredParam<Marketplace | MarketplaceV3>,
|
||||
listingId: RequiredParam<BigNumberish>,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
@@ -212,18 +521,27 @@ export function useAuctionWinner(
|
||||
contractAddress,
|
||||
listingId,
|
||||
),
|
||||
async () => {
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
let winner: string | undefined;
|
||||
try {
|
||||
winner = await contract.auction.getWinner(listingId);
|
||||
} catch (err) {
|
||||
if (!(err as Error)?.message?.includes("Could not find auction")) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const isV1 = isMarketplaceV1(contract);
|
||||
|
||||
if (isV1) {
|
||||
invariant(
|
||||
contract.auction.getWinner,
|
||||
"contract does not support auction.getWinner",
|
||||
);
|
||||
return contract.auction.getWinner(listingId);
|
||||
} else if (!isV1) {
|
||||
invariant(
|
||||
contract.englishAuctions.getWinner,
|
||||
"contract does not support englishAuctions.getWinner",
|
||||
);
|
||||
return contract.englishAuctions.getWinner(listingId);
|
||||
}
|
||||
return winner;
|
||||
invariant(false, "Contract is not a valid marketplace contract");
|
||||
|
||||
},
|
||||
{
|
||||
enabled: !!contract && listingId !== undefined,
|
||||
@@ -240,17 +558,35 @@ export function useAuctionWinner(
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace contract
|
||||
* @param listingId - the listing id to check (only necessary for marketplace v3)
|
||||
|
||||
* @returns a response object that includes an array of listings
|
||||
* @beta
|
||||
*/
|
||||
export function useBidBuffer(contract: RequiredParam<Marketplace>) {
|
||||
export function useBidBuffer(contract: RequiredParam<Marketplace | MarketplaceV3>, listingId?: RequiredParam<BigNumberish>) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.getBidBufferBps(contractAddress),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.getBidBufferBps();
|
||||
|
||||
const isV1 = isMarketplaceV1(contract);
|
||||
|
||||
if (isV1) {
|
||||
invariant(
|
||||
contract.getBidBufferBps,
|
||||
"contract does not support getBidBufferBps",
|
||||
);
|
||||
return contract.getBidBufferBps();
|
||||
} else if (!isV1) {
|
||||
invariant(
|
||||
contract.englishAuctions.getBidBufferBps,
|
||||
"contract does not support englishAuctions.getBidBufferBps",
|
||||
);
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
return BigNumber.from(contract.englishAuctions.getBidBufferBps(listingId));
|
||||
}
|
||||
invariant(false, "Contract is not a valid marketplace contract");
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
@@ -272,7 +608,7 @@ export function useBidBuffer(contract: RequiredParam<Marketplace>) {
|
||||
* @beta
|
||||
*/
|
||||
export function useMinimumNextBid(
|
||||
contract: RequiredParam<Marketplace>,
|
||||
contract: RequiredParam<Marketplace | MarketplaceV3>,
|
||||
listingId: RequiredParam<BigNumberish>,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
@@ -284,7 +620,24 @@ export function useMinimumNextBid(
|
||||
async () => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
return await contract.auction.getMinimumNextBid(listingId);
|
||||
|
||||
const isV1 = isMarketplaceV1(contract);
|
||||
|
||||
if (isV1) {
|
||||
invariant(
|
||||
contract.auction.getMinimumNextBid,
|
||||
"contract does not support auction.getMinimumNextBid",
|
||||
);
|
||||
return contract.auction.getMinimumNextBid(listingId);
|
||||
} else if (!isV1) {
|
||||
invariant(
|
||||
contract.englishAuctions.getMinimumNextBid,
|
||||
"contract does not support englishAuctions.getMinimumNextBid",
|
||||
);
|
||||
return contract.englishAuctions.getMinimumNextBid(listingId);
|
||||
}
|
||||
invariant(false, "Contract is not a valid marketplace contract");
|
||||
|
||||
},
|
||||
{
|
||||
enabled: !!contract && listingId !== undefined,
|
||||
@@ -327,20 +680,32 @@ export function useMinimumNextBid(
|
||||
* @returns a mutation object that can be used to create a new direct listing
|
||||
* @beta
|
||||
*/
|
||||
export function useCreateDirectListing(contract: RequiredParam<Marketplace>) {
|
||||
export function useCreateDirectListing<TMarketplace extends Marketplace | MarketplaceV3>(contract: RequiredParam<TMarketplace>) {
|
||||
const activeChainId = useSDKChainId();
|
||||
const contractAddress = contract?.getAddress();
|
||||
const queryClient = useQueryClient();
|
||||
const walletAddress = useAddress();
|
||||
return useMutation(
|
||||
async (data: NewDirectListing) => {
|
||||
invariant(walletAddress, "no wallet connected, cannot create listing");
|
||||
async (data: TMarketplace extends Marketplace ? NewDirectListing : DirectListingInputParams) => {
|
||||
invariant(walletAddress, "No wallet connected, cannot create listing");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
invariant(
|
||||
contract.direct.createListing,
|
||||
"contract does not support direct.createListing",
|
||||
);
|
||||
return await contract.direct.createListing(data);
|
||||
|
||||
const isV1 = isMarketplaceV1(contract);
|
||||
|
||||
if (isV1) {
|
||||
invariant(
|
||||
contract.direct.createListing,
|
||||
"contract does not support direct.createListing",
|
||||
);
|
||||
return await contract.direct.createListing(data as NewDirectListing);
|
||||
} else if (!isV1) {
|
||||
invariant(
|
||||
contract.directListings.createListing,
|
||||
"contract does not support directListings.createListing",
|
||||
);
|
||||
return await contract.directListings.createListing(data as DirectListingInputParams);
|
||||
}
|
||||
invariant(false, "Contract is not a valid marketplace contract");
|
||||
},
|
||||
{
|
||||
onSettled: () =>
|
||||
@@ -384,20 +749,32 @@ export function useCreateDirectListing(contract: RequiredParam<Marketplace>) {
|
||||
* @returns a mutation object that can be used to create a new auction listing
|
||||
* @beta
|
||||
*/
|
||||
export function useCreateAuctionListing(contract: RequiredParam<Marketplace>) {
|
||||
export function useCreateAuctionListing<TMarketplace extends Marketplace | MarketplaceV3>(contract: RequiredParam<TMarketplace>) {
|
||||
const activeChainId = useSDKChainId();
|
||||
const contractAddress = contract?.getAddress();
|
||||
const queryClient = useQueryClient();
|
||||
const walletAddress = useAddress();
|
||||
return useMutation(
|
||||
async (data: NewAuctionListing) => {
|
||||
async (data: TMarketplace extends Marketplace ? NewAuctionListing : EnglishAuctionInputParams) => {
|
||||
invariant(walletAddress, "no wallet connected, cannot create listing");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
invariant(
|
||||
contract.direct.createListing,
|
||||
"contract does not support auction.createListing",
|
||||
);
|
||||
return await contract.auction.createListing(data);
|
||||
|
||||
const isV1 = isMarketplaceV1(contract);
|
||||
|
||||
if (isV1) {
|
||||
invariant(
|
||||
contract.auction.createListing,
|
||||
"contract does not support auction.createListing",
|
||||
);
|
||||
return await contract.auction.createListing(data as NewAuctionListing);
|
||||
} else if (!isV1) {
|
||||
invariant(
|
||||
contract.englishAuctions.createAuction,
|
||||
"contract does not support englishAuctions.createAuction",
|
||||
);
|
||||
return await contract.englishAuctions.createAuction(data as EnglishAuctionInputParams);
|
||||
}
|
||||
invariant(false, "Contract is not a valid marketplace contract");
|
||||
},
|
||||
{
|
||||
onSettled: () =>
|
||||
@@ -431,26 +808,38 @@ export function useCreateAuctionListing(contract: RequiredParam<Marketplace>) {
|
||||
* disabled={isLoading}
|
||||
* onClick={() => cancelListing()}
|
||||
* >
|
||||
* Create Auction Listing!
|
||||
* Cancel Auction Listing!
|
||||
* </button>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a Marketplace contract
|
||||
* @returns a mutation object that can be used to create a new auction listing
|
||||
* @returns a mutation object that can be used to cancel a listing
|
||||
* @beta
|
||||
*/
|
||||
export function useCancelListing(contract: RequiredParam<Marketplace>) {
|
||||
const activeChainId = useSDKChainId();
|
||||
const contractAddress = contract?.getAddress();
|
||||
const queryClient = useQueryClient();
|
||||
const walletAddress = useAddress();
|
||||
return useMutation(
|
||||
async (data: Pick<AuctionListing | DirectListing, "type" | "id">) => {
|
||||
invariant(walletAddress, "no wallet connected, cannot create listing");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
|
||||
if (data.type === ListingType.Auction) {
|
||||
return await contract?.auction.cancelListing(data.id);
|
||||
invariant(
|
||||
contract.auction.cancelListing,
|
||||
"contract does not support auction.cancelListing",
|
||||
);
|
||||
return await contract.auction.cancelListing(data.id);
|
||||
} else {
|
||||
return await contract?.direct.cancelListing(data.id);
|
||||
invariant(
|
||||
contract.direct.cancelListing,
|
||||
"contract does not support direct.cancelListing",
|
||||
);
|
||||
return await contract.direct.cancelListing(data.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -464,6 +853,125 @@ export function useCancelListing(contract: RequiredParam<Marketplace>) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to cancel a direct listing on your marketplace v3 contract.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* const Component = () => {
|
||||
* const {
|
||||
* mutate: cancelDirectListing,
|
||||
* isLoading,
|
||||
* error,
|
||||
* } = useCancelDirectListing(">>YourMarketplaceV3ContractInstance<<");
|
||||
*
|
||||
* if (error) {
|
||||
* console.error("failed to cancel direct listing", error);
|
||||
* }
|
||||
*
|
||||
* return (
|
||||
* <button
|
||||
* disabled={isLoading}
|
||||
* onClick={() => cancelListing()}
|
||||
* >
|
||||
* Cancel Direct Listing
|
||||
* </button>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a Marketplace v3 contract
|
||||
* @returns a mutation object that can be used to cancel a direct listing
|
||||
* @internal
|
||||
*/
|
||||
export function useCancelDirectListing(contract: RequiredParam<MarketplaceV3>) {
|
||||
const activeChainId = useSDKChainId();
|
||||
const contractAddress = contract?.getAddress();
|
||||
const queryClient = useQueryClient();
|
||||
const walletAddress = useAddress();
|
||||
return useMutation(
|
||||
async (listingId: BigNumberish) => {
|
||||
invariant(walletAddress, "no wallet connected, cannot create listing");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
|
||||
invariant(
|
||||
contract.directListings.cancelListing,
|
||||
"contract does not support directListings.cancelListing",
|
||||
);
|
||||
return await contract.directListings.cancelListing(listingId);
|
||||
},
|
||||
{
|
||||
onSettled: () =>
|
||||
invalidateContractAndBalances(
|
||||
queryClient,
|
||||
contractAddress,
|
||||
activeChainId,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to cancel a direct listing on your marketplace v3 contract.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* const Component = () => {
|
||||
* const {
|
||||
* mutate: cancelEnglishAuction,
|
||||
* isLoading,
|
||||
* error,
|
||||
* } = useCancelEnglishAuction(">>YourMarketplaceV3ContractInstance<<");
|
||||
*
|
||||
* if (error) {
|
||||
* console.error("failed to cancel english auction", error);
|
||||
* }
|
||||
*
|
||||
* return (
|
||||
* <button
|
||||
* disabled={isLoading}
|
||||
* onClick={() => cancelEnglishAuction()}
|
||||
* >
|
||||
* Cancel English Auction
|
||||
* </button>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a Marketplace v3 contract
|
||||
* @returns a mutation object that can be used to cancel an english auction
|
||||
* @internal
|
||||
*/
|
||||
export function useCancelEnglishAuction(contract: RequiredParam<MarketplaceV3>) {
|
||||
const activeChainId = useSDKChainId();
|
||||
const contractAddress = contract?.getAddress();
|
||||
const queryClient = useQueryClient();
|
||||
const walletAddress = useAddress();
|
||||
return useMutation(
|
||||
async (auctionId: BigNumberish) => {
|
||||
invariant(walletAddress, "no wallet connected, cannot create listing");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(auctionId, "No auction id provided");
|
||||
|
||||
invariant(
|
||||
contract.englishAuctions.cancelAuction,
|
||||
"contract does not support englishAuctions.cancelAuction",
|
||||
);
|
||||
return await contract.englishAuctions.cancelAuction(auctionId);
|
||||
},
|
||||
{
|
||||
onSettled: () =>
|
||||
invalidateContractAndBalances(
|
||||
queryClient,
|
||||
contractAddress,
|
||||
activeChainId,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Use this to place a bid on an auction listing from your marketplace contract.
|
||||
*
|
||||
@@ -786,3 +1294,7 @@ export function useBuyNow(contract: RequiredParam<Marketplace>) {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function isMarketplaceV1(contract: RequiredParam<Marketplace | MarketplaceV3>): contract is Marketplace {
|
||||
return (contract as Marketplace).getAllListings !== undefined;
|
||||
}
|
||||
@@ -268,6 +268,72 @@ export const cacheKeys = {
|
||||
{ listingId },
|
||||
]),
|
||||
},
|
||||
|
||||
directListings: {
|
||||
getAll: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
params?: MarketplaceFilter,
|
||||
) =>
|
||||
createContractCacheKey(
|
||||
contractAddress,
|
||||
params ? ["getAllDirectListings", params] : ["getAllDirectListings"],
|
||||
),
|
||||
getAllValid: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
params?: MarketplaceFilter,
|
||||
) =>
|
||||
createContractCacheKey(
|
||||
contractAddress,
|
||||
params ? ["getAllValidDirectListings", params] : ["getAllValidDirectListings"],
|
||||
),
|
||||
getListing: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
listingId: RequiredParam<BigNumberish>,
|
||||
) =>
|
||||
createContractCacheKey(contractAddress, [
|
||||
"getListing",
|
||||
{ listingId },
|
||||
]),
|
||||
getTotalCount: (contractAddress: RequiredParam<ContractAddress>) =>
|
||||
createContractCacheKey(contractAddress, ["getTotalDirectListingsCount"]),
|
||||
},
|
||||
|
||||
englishAuctions: {
|
||||
getAll: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
params?: MarketplaceFilter,
|
||||
) =>
|
||||
createContractCacheKey(
|
||||
contractAddress,
|
||||
params ? ["getAllEnglishAuctions", params] : ["getAllEnglishAuctions"],
|
||||
),
|
||||
getAllValid: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
params?: MarketplaceFilter,
|
||||
) =>
|
||||
createContractCacheKey(
|
||||
contractAddress,
|
||||
params ? ["getAllEValidEnglishAuctions", params] : ["getAllEValidEnglishAuctions"],
|
||||
),
|
||||
getAuction: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
auctionId: RequiredParam<BigNumberish>,
|
||||
) =>
|
||||
createContractCacheKey(contractAddress, [
|
||||
"getAuction",
|
||||
{ auctionId },
|
||||
]),
|
||||
getWinningBid: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
auctionId: RequiredParam<BigNumberish>,
|
||||
) =>
|
||||
createContractCacheKey(contractAddress, [
|
||||
"getWinningBid",
|
||||
{ auctionId },
|
||||
]),
|
||||
getTotalCount: (contractAddress: RequiredParam<ContractAddress>) =>
|
||||
createContractCacheKey(contractAddress, ["getTotalEnglishAuctionsCount"]),
|
||||
},
|
||||
},
|
||||
},
|
||||
// extensions
|
||||
|
||||
@@ -9,4 +9,6 @@ etc/
|
||||
temp/
|
||||
temp-evm/
|
||||
temp-solana/
|
||||
temp-evm-gnosis-safe/
|
||||
temp-evm-magic/
|
||||
docs/
|
||||
@@ -1,5 +1,44 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 3.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/react-core@3.8.1
|
||||
|
||||
## 3.8.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`440a4ad`](https://github.com/thirdweb-dev/js/commit/440a4ade95874e696c589eaa7aae9f0fecc862be)]:
|
||||
- @thirdweb-dev/react-core@3.8.0
|
||||
|
||||
## 3.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#470](https://github.com/thirdweb-dev/js/pull/470) [`b59a31c`](https://github.com/thirdweb-dev/js/commit/b59a31c365380c4dc9aed8d7678d46b4b21a029d) Thanks [@JustinTime42](https://github.com/JustinTime42)! - Added support for displaying 3d model NFTs
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/react-core@3.7.4
|
||||
|
||||
## 3.7.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/react-core@3.7.3
|
||||
|
||||
## 3.7.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#527](https://github.com/thirdweb-dev/js/pull/527) [`bdd9ed2`](https://github.com/thirdweb-dev/js/commit/bdd9ed22989f5baa511bec70ef1ae9d88bfb957e) Thanks [@jnsdls](https://github.com/jnsdls)! - fix dependency issues with @zag-js/react
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/react-core@3.7.2
|
||||
|
||||
## 3.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -305,3 +305,111 @@ const KitchenSinkExample = () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Available hooks
|
||||
|
||||
### Main hooks
|
||||
| Hook | Description |
|
||||
| --- | --- |
|
||||
| [`useSDK`](https://portal.thirdweb.com/react/react.usesdk) | Access the instance of the thirdweb SDK created by the ThirdwebProvider. |
|
||||
| [`useContract`](https://portal.thirdweb.com/react/react.usecontract) | Use this resolve a contract address to a smart contract instance. |
|
||||
| [`useContractWrite`](https://portal.thirdweb.com/react/react.usecontractwrite) | Use this to get data from a contract read-function call. |
|
||||
| [`useContractRead`](https://portal.thirdweb.com/react/react.usecontractread) | Use this to get a function to make a write call to your contract. |
|
||||
| [`useContractEvents`](https://portal.thirdweb.com/react/react.usecontractevents) | Use this to get the contract metadata for a (built-in or custom) contract. |
|
||||
| [`useContractMetadata`](https://portal.thirdweb.com/react/react.usecontractmetadata) | Use this to get the contract metadata for a (built-in or custom) contract. |
|
||||
|
||||
### Wallet connection
|
||||
| Hook | Description |
|
||||
| --- | --- |
|
||||
| [`useAddress`](https://portal.thirdweb.com/react/react.useaddress) | Hook for accessing the address of the connected wallet. |
|
||||
| [`useMetamask`](https://portal.thirdweb.com/react/react.usemetamask) | Hook for connecting to a Metamask wallet. |
|
||||
| [`useWalletConnect`](https://portal.thirdweb.com/react/react.usewalletconnect) | Hook for connecting to a mobile wallet with Wallet Connect. |
|
||||
| [`useCoinbaseWallet`](https://portal.thirdweb.com/react/react.usecoinbasewallet) | Hook for connecting to a Coinbase wallet. |
|
||||
| [`useMagic`](https://portal.thirdweb.com/react/react.usemagic) | Hook for connecting to an email wallet using magic link. |
|
||||
| [`useGnosis`](https://portal.thirdweb.com/react/react.usegnosis) | Hook for connecting to a Gnosis Safe. |
|
||||
| [`useDisconnect`](https://portal.thirdweb.com/react/react.usedisconnect) | Hook for disconnecting the currently connected wallet. |
|
||||
|
||||
### Network connection
|
||||
| Hook | Description |
|
||||
| --- | --- |
|
||||
| [`useChainId`](https://portal.thirdweb.com/react/react.usechainid) | Hook for accessing the chain ID of the network the current wallet is connected to |
|
||||
| [`useNetwork`](https://portal.thirdweb.com/react/react.usenetwork) | Hook for getting metadata about the network the current wallet is connected to and switching networks. |
|
||||
| [`useNetworkMismatch`](https://portal.thirdweb.com/react/react.usenetworkmismatch) | Hook for checking whether the connected wallet is on the correct network specified by the desiredChainId passed to the <ThirdwebProvider />. |
|
||||
|
||||
### NFT
|
||||
| Hook | Description |
|
||||
| --- | --- |
|
||||
| [`useNFT`](https://portal.thirdweb.com/react/react.usenft) | Use this to get an individual NFT token of your NFTContract. |
|
||||
| [`useNFTs`](https://portal.thirdweb.com/react/react.usenfts) | Use this to get a list of NFT tokens of your NFTContract. |
|
||||
| [`useTotalCirculatingSupply`](https://portal.thirdweb.com/react/react.usetotalcirculatingsupply) | Use this to get a the total (minted) supply of your NFTContract. |
|
||||
| [`useOwnedNFTs`](https://portal.thirdweb.com/react/react.useownednfts) | Use this to get a the owned NFTs for a specific Erc721OrErc1155 and wallet address. |
|
||||
| [`useNFTBalance`](https://portal.thirdweb.com/react/react.usenftbalance) | Use this to get a the total balance of a NFTContract and wallet address. |
|
||||
| [`useTotalCount`](https://portal.thirdweb.com/react/react.usetotalcount) | Use this to get the total count of NFT tokens of your NFTContract. |
|
||||
| [`useMintNFT`](https://portal.thirdweb.com/react/react.usemintnft) | Use this to mint a new NFT on your Erc721OrErc1155. |
|
||||
| [`useMintNFTSupply`](https://portal.thirdweb.com/react/react.usemintnftsupply) | Use this mint extra supply on your Erc1155. |
|
||||
| [`useTransferNFT`](https://portal.thirdweb.com/react/react.usetransfernft) | Use this to transfer tokens on your NFTContract. |
|
||||
| [`useAirdropNFT`](https://portal.thirdweb.com/react/react.useairdropnft) | Use this to airdrop tokens on your Erc1155. |
|
||||
| [`useBurnNFT`](https://portal.thirdweb.com/react/react.useburnnft) | Use this to burn an NFT on your Erc721OrErc1155. |
|
||||
|
||||
|
||||
### Token
|
||||
| Hook | Description |
|
||||
| --- | --- |
|
||||
| [`useTokenSupply`](https://portal.thirdweb.com/react/react.usetokensupply) | Use this to get a the total supply of your contract. |
|
||||
| [`useTokenBalance`](https://portal.thirdweb.com/react/react.usetokenbalance) | Use this to get the balance of your contract for a given address. |
|
||||
| [`useTokenDecimals`](https://portal.thirdweb.com/react/react.usetokendecimals) | Use this to get the decimals of your contract for a given address. |
|
||||
| [`useMintToken`](https://portal.thirdweb.com/react/react.useminttoken) | Use this to mint new tokens on your contract. |
|
||||
| [`useClaimToken`](https://portal.thirdweb.com/react/react.useclaimtoken) | Use this to claim tokens from your contract. |
|
||||
| [`useTransferToken`](https://portal.thirdweb.com/react/react.usetransfertoken) | Use this to transfer tokens on your contract. |
|
||||
| [`useTransferBatchToken`](https://portal.thirdweb.com/react/react.usetransferbatchtoken) | Use this to transfer batch tokens on your contract. |
|
||||
| [`useBurnToken`](https://portal.thirdweb.com/react/react.useburntoken) | Use this to burn tokens on your contract. |
|
||||
|
||||
### Marketplace
|
||||
| Hook | Description |
|
||||
| --- | --- |
|
||||
| [`useListing`](https://portal.thirdweb.com/react/react.uselisting) | Use this to get a specific listing from the marketplace. |
|
||||
| [`useListings`](https://portal.thirdweb.com/react/react.uselistings) | Use this to get a list all listings from your marketplace contract. |
|
||||
| [`useListingsCount`](https://portal.thirdweb.com/react/react.uselistingscount) | Use this to get a count of all listings on your marketplace contract. |
|
||||
| [`useActiveListings`](https://portal.thirdweb.com/react/react.useactivelistings) | Use this to get a list active listings from your marketplace contract. |
|
||||
| [`useWinningBid`](https://portal.thirdweb.com/react/react.usewinningbid) | Use this to get a the winning bid for an auction listing from your marketplace contract. |
|
||||
| [`useAuctionWinner`](https://portal.thirdweb.com/react/react.useauctionwinner) | Use this to get the winner of an auction listing from your marketplace contract. |
|
||||
| [`useBidBuffer`](https://portal.thirdweb.com/react/react.usebidbuffer) | Use this to get the buffer in basis points between offers from your marketplace contract. |
|
||||
| [`useCreateDirectListing`](https://portal.thirdweb.com/react/react.usecreatedirectlisting) | Use this to create a new Direct Listing on your marketplace contract. |
|
||||
| [`useCreateAuctionListing`](https://portal.thirdweb.com/react/react.usecreateauctionlisting) | Use this to create a new Auction Listing on your marketplace contract. |
|
||||
| [`useCancelListing`](https://portal.thirdweb.com/react/react.usecancellisting) | Use this to cancel a listing on your marketplace contract. |
|
||||
| [`useMakeBid`](https://portal.thirdweb.com/react/react.usemakebid) | Use this to place a bid on an auction listing from your marketplace contract. |
|
||||
| [`useBuyNow`](https://portal.thirdweb.com/react/react.usebuynow) | Use this to buy out an auction listing from your marketplace contract. |
|
||||
|
||||
### Permissions Controls
|
||||
| Hook | Description |
|
||||
| --- | --- |
|
||||
| [`useAllRoleMembers`](https://portal.thirdweb.com/react/react.useallrolemembers) | Use this to get the roles of all members. |
|
||||
| [`useRoleMembers`](https://portal.thirdweb.com/react/react.userolemembers) | Use this to get the members of a role. |
|
||||
| [`useIsAddressRole`](https://portal.thirdweb.com/react/react.useisaddressrole) | Use this to check if a WalletAddress is a member of a role. |
|
||||
| [`useSetAllRoleMembers`](https://portal.thirdweb.com/react/react.usesetallrolemembers) | Use this to OVERWRITE the list of addresses that are members of specific roles. |
|
||||
| [`useGrantRole`](https://portal.thirdweb.com/react/react.usegrantrole) | Use this to grant a WalletAddress a specific role. |
|
||||
| [`useRevokeRole`](https://portal.thirdweb.com/react/react.userevokerole) | Use this to revoke a WalletAddress a specific role. |
|
||||
|
||||
### Drop
|
||||
| Hook | Description |
|
||||
| --- | --- |
|
||||
| [`useUnclaimedNFTs`](https://portal.thirdweb.com/react/react.useunclaimednfts) | Use this to get a list of unclaimed NFT tokens of your ERC721 Drop contract. |
|
||||
| [`useClaimedNFTs`](https://portal.thirdweb.com/react/react.useclaimednfts) | Use this to get a list of claimed (minted) NFT tokens of your ERC721 Drop contract.. |
|
||||
| [`useUnclaimedNFTSupply`](https://portal.thirdweb.com/react/react.useunclaimednftsupply) | Use this to get the total unclaimed NFT supply of your ERC721 Drop contract. |
|
||||
| [`useClaimedNFTSupply`](https://portal.thirdweb.com/react/react.useclaimednftsupply) | Use this to get the total claimed (minted) NFT supply of your ERC721 Drop contract. |
|
||||
| [`useBatchesToReveal`](https://portal.thirdweb.com/react/react.usebatchestoreveal) | Use this to get a list of batches that are ready to be revealed. |
|
||||
| [`useClaimNFT`](https://portal.thirdweb.com/react/react.useclaimnft) | Use this to claim a NFT on your DropContract |
|
||||
| [`useLazyMint`](https://portal.thirdweb.com/react/react.uselazymint) | Use this to lazy mint a batch of NFTs on your DropContract. |
|
||||
| [`useDelayedRevealLazyMint`](https://portal.thirdweb.com/react/react.usedelayedreveallazymint) | Use this to lazy mint a batch of delayed reveal NFTs on your DropContract. |
|
||||
| [`useRevealLazyMint`](https://portal.thirdweb.com/react/react.usereveallazymint) | Use this to reveal a batch of delayed reveal NFTs on your RevealableContract. |
|
||||
|
||||
### Claim Conditions
|
||||
| Hook | Description |
|
||||
| --- | --- |
|
||||
| [`useActiveClaimCondition`](https://portal.thirdweb.com/react/react.useactiveclaimcondition) | Use this to get the active claim condition for ERC20, ERC721 or ERC1155 based contracts. |
|
||||
| [`useClaimConditions`](https://portal.thirdweb.com/react/react.useclaimconditions) | Use this to get all claim conditions for ERC20, ERC721 or ERC1155 based contracts. |
|
||||
| [`useClaimIneligibilityReasons`](https://portal.thirdweb.com/react/react.useclaimineligibilityreasons) | Use this to check for reasons that prevent claiming for either ERC20, ERC721 or ERC1155 based contracts. |
|
||||
| [`useSetClaimConditions`](https://portal.thirdweb.com/react/react.usesetclaimconditions) | Use this to set claim conditions on your DropContract. |
|
||||
| [`useResetClaimConditions`](https://portal.thirdweb.com/react/react.useresetclaimconditions) | Use this to reset claim conditions on your DropContract. |
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
/**
|
||||
* Optionally specifies another JSON config file that this file extends from. This provides a way for
|
||||
* standard settings to be shared across multiple projects.
|
||||
*
|
||||
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
|
||||
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
|
||||
* resolved using NodeJS require().
|
||||
*
|
||||
* SUPPORTED TOKENS: none
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "extends": "./shared/api-extractor-base.json"
|
||||
// "extends": "my-package/include/api-extractor-base.json"
|
||||
/**
|
||||
* Determines the "<projectFolder>" token that can be used with other config file settings. The project folder
|
||||
* typically contains the tsconfig.json and package.json config files, but the path is user-defined.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting.
|
||||
*
|
||||
* The default value for "projectFolder" is the token "<lookup>", which means the folder is determined by traversing
|
||||
* parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
|
||||
* that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
|
||||
* will be reported.
|
||||
*
|
||||
* SUPPORTED TOKENS: <lookup>
|
||||
* DEFAULT VALUE: "<lookup>"
|
||||
*/
|
||||
// "projectFolder": "..",
|
||||
/**
|
||||
* (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
|
||||
* analyzes the symbols exported by this module.
|
||||
*
|
||||
* The file extension must be ".d.ts" and not ".ts".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
*/
|
||||
"mainEntryPointFilePath": "<projectFolder>/evm/connectors/gnosis-safe/dist/thirdweb-dev-react-evm-connectors-gnosis-safe.cjs.d.ts",
|
||||
/**
|
||||
* A list of NPM package names whose exports should be treated as part of this package.
|
||||
*
|
||||
* For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
|
||||
* and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
|
||||
* of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
|
||||
* imports library2. To avoid this, we can specify:
|
||||
*
|
||||
* "bundledPackages": [ "library2" ],
|
||||
*
|
||||
* This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
|
||||
* local files for library1.
|
||||
*/
|
||||
"bundledPackages": [
|
||||
"wagmi",
|
||||
"wagmi-core"
|
||||
],
|
||||
/**
|
||||
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
|
||||
*/
|
||||
"compiler": {
|
||||
/**
|
||||
* Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* Note: This setting will be ignored if "overrideTsconfig" is used.
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/tsconfig.json"
|
||||
*/
|
||||
// "tsconfigFilePath": "<projectFolder>/tsconfig.json",
|
||||
/**
|
||||
* Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
|
||||
* The object must conform to the TypeScript tsconfig schema:
|
||||
*
|
||||
* http://json.schemastore.org/tsconfig
|
||||
*
|
||||
* If omitted, then the tsconfig.json file will be read from the "projectFolder".
|
||||
*
|
||||
* DEFAULT VALUE: no overrideTsconfig section
|
||||
*/
|
||||
"overrideTsconfig": {
|
||||
"skipLibCheck": true
|
||||
}
|
||||
/**
|
||||
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
|
||||
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
|
||||
* dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
|
||||
* for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "skipLibCheck": true,
|
||||
},
|
||||
/**
|
||||
* Configures how the API report file (*.api.md) will be generated.
|
||||
*/
|
||||
"apiReport": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate an API report.
|
||||
*/
|
||||
"enabled": true
|
||||
/**
|
||||
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
|
||||
* a full file path.
|
||||
*
|
||||
* The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
|
||||
*
|
||||
* SUPPORTED TOKENS: <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<unscopedPackageName>.api.md"
|
||||
*/
|
||||
// "reportFileName": "<unscopedPackageName>.api.md",
|
||||
/**
|
||||
* Specifies the folder where the API report file is written. The file name portion is determined by
|
||||
* the "reportFileName" setting.
|
||||
*
|
||||
* The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
|
||||
* e.g. for an API review.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/etc/"
|
||||
*/
|
||||
// "reportFolder": "<projectFolder>/etc/",
|
||||
/**
|
||||
* Specifies the folder where the temporary report file is written. The file name portion is determined by
|
||||
* the "reportFileName" setting.
|
||||
*
|
||||
* After the temporary file is written to disk, it is compared with the file in the "reportFolder".
|
||||
* If they are different, a production build will fail.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/"
|
||||
*/
|
||||
// "reportTempFolder": "<projectFolder>/temp/"
|
||||
},
|
||||
/**
|
||||
* Configures how the doc model file (*.api.json) will be generated.
|
||||
*/
|
||||
"docModel": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate a doc model file.
|
||||
*/
|
||||
"enabled": true
|
||||
/**
|
||||
* The output path for the doc model file. The file extension should be ".api.json".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
|
||||
*/
|
||||
// "apiJsonFilePath": "<projectFolder>/temp/<unscopedPackageName>.api.json"
|
||||
},
|
||||
/**
|
||||
* Configures how the .d.ts rollup file will be generated.
|
||||
*/
|
||||
"dtsRollup": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate the .d.ts rollup file.
|
||||
*/
|
||||
"enabled": false,
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
|
||||
* This file will include all declarations that are exported by the main entry point.
|
||||
*
|
||||
* If the path is an empty string, then this file will not be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
|
||||
*/
|
||||
"untrimmedFilePath": "<projectFolder>/lib/<unscopedPackageName>.d.ts"
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
|
||||
* This file will include only declarations that are marked as "@public" or "@beta".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
|
||||
* This file will include only declarations that are marked as "@public".
|
||||
*
|
||||
* If the path is an empty string, then this file will not be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
|
||||
/**
|
||||
* When a declaration is trimmed, by default it will be replaced by a code comment such as
|
||||
* "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
|
||||
* declaration completely.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "omitTrimmingComments": true
|
||||
},
|
||||
/**
|
||||
* Configures how the tsdoc-metadata.json file will be generated.
|
||||
*/
|
||||
"tsdocMetadata": {
|
||||
/**
|
||||
* Whether to generate the tsdoc-metadata.json file.
|
||||
*
|
||||
* DEFAULT VALUE: true
|
||||
*/
|
||||
// "enabled": true,
|
||||
/**
|
||||
* Specifies where the TSDoc metadata file should be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* The default value is "<lookup>", which causes the path to be automatically inferred from the "tsdocMetadata",
|
||||
* "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
|
||||
* falls back to "tsdoc-metadata.json" in the package folder.
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<lookup>"
|
||||
*/
|
||||
// "tsdocMetadataFilePath": "<projectFolder>/dist/tsdoc-metadata.json"
|
||||
},
|
||||
/**
|
||||
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
|
||||
* will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
|
||||
* To use the OS's default newline kind, specify "os".
|
||||
*
|
||||
* DEFAULT VALUE: "crlf"
|
||||
*/
|
||||
// "newlineKind": "crlf",
|
||||
/**
|
||||
* Configures how API Extractor reports error and warning messages produced during analysis.
|
||||
*
|
||||
* There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
|
||||
*/
|
||||
"messages": {
|
||||
/**
|
||||
* Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
|
||||
* the input .d.ts files.
|
||||
*
|
||||
* TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
|
||||
*
|
||||
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||
*/
|
||||
"compilerMessageReporting": {
|
||||
/**
|
||||
* Configures the default routing for messages that don't match an explicit rule in this table.
|
||||
*/
|
||||
"default": {
|
||||
/**
|
||||
* Specifies whether the message should be written to the the tool's output log. Note that
|
||||
* the "addToApiReportFile" property may supersede this option.
|
||||
*
|
||||
* Possible values: "error", "warning", "none"
|
||||
*
|
||||
* Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
|
||||
* and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
|
||||
* the "--local" option), the warning is displayed but the build will not fail.
|
||||
*
|
||||
* DEFAULT VALUE: "warning"
|
||||
*/
|
||||
"logLevel": "warning"
|
||||
/**
|
||||
* When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
|
||||
* then the message will be written inside that file; otherwise, the message is instead logged according to
|
||||
* the "logLevel" option.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
// "TS2551": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
},
|
||||
/**
|
||||
* Configures handling of messages reported by API Extractor during its analysis.
|
||||
*
|
||||
* API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
|
||||
*
|
||||
* DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
|
||||
*/
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
// "ae-extra-release-tag": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
},
|
||||
/**
|
||||
* Configures handling of messages reported by the TSDoc parser when analyzing code comments.
|
||||
*
|
||||
* TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
|
||||
*
|
||||
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||
*/
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
// "tsdoc-link-tag-unescaped-text": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
/**
|
||||
* Optionally specifies another JSON config file that this file extends from. This provides a way for
|
||||
* standard settings to be shared across multiple projects.
|
||||
*
|
||||
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
|
||||
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
|
||||
* resolved using NodeJS require().
|
||||
*
|
||||
* SUPPORTED TOKENS: none
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "extends": "./shared/api-extractor-base.json"
|
||||
// "extends": "my-package/include/api-extractor-base.json"
|
||||
/**
|
||||
* Determines the "<projectFolder>" token that can be used with other config file settings. The project folder
|
||||
* typically contains the tsconfig.json and package.json config files, but the path is user-defined.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting.
|
||||
*
|
||||
* The default value for "projectFolder" is the token "<lookup>", which means the folder is determined by traversing
|
||||
* parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
|
||||
* that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
|
||||
* will be reported.
|
||||
*
|
||||
* SUPPORTED TOKENS: <lookup>
|
||||
* DEFAULT VALUE: "<lookup>"
|
||||
*/
|
||||
// "projectFolder": "..",
|
||||
/**
|
||||
* (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
|
||||
* analyzes the symbols exported by this module.
|
||||
*
|
||||
* The file extension must be ".d.ts" and not ".ts".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
*/
|
||||
"mainEntryPointFilePath": "<projectFolder>/evm/connectors/magic/dist/thirdweb-dev-react-evm-connectors-magic.cjs.d.ts",
|
||||
/**
|
||||
* A list of NPM package names whose exports should be treated as part of this package.
|
||||
*
|
||||
* For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
|
||||
* and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
|
||||
* of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
|
||||
* imports library2. To avoid this, we can specify:
|
||||
*
|
||||
* "bundledPackages": [ "library2" ],
|
||||
*
|
||||
* This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
|
||||
* local files for library1.
|
||||
*/
|
||||
"bundledPackages": [
|
||||
"wagmi",
|
||||
"wagmi-core"
|
||||
],
|
||||
/**
|
||||
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
|
||||
*/
|
||||
"compiler": {
|
||||
/**
|
||||
* Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* Note: This setting will be ignored if "overrideTsconfig" is used.
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/tsconfig.json"
|
||||
*/
|
||||
// "tsconfigFilePath": "<projectFolder>/tsconfig.json",
|
||||
/**
|
||||
* Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
|
||||
* The object must conform to the TypeScript tsconfig schema:
|
||||
*
|
||||
* http://json.schemastore.org/tsconfig
|
||||
*
|
||||
* If omitted, then the tsconfig.json file will be read from the "projectFolder".
|
||||
*
|
||||
* DEFAULT VALUE: no overrideTsconfig section
|
||||
*/
|
||||
"overrideTsconfig": {
|
||||
"skipLibCheck": true
|
||||
}
|
||||
/**
|
||||
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
|
||||
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
|
||||
* dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
|
||||
* for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "skipLibCheck": true,
|
||||
},
|
||||
/**
|
||||
* Configures how the API report file (*.api.md) will be generated.
|
||||
*/
|
||||
"apiReport": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate an API report.
|
||||
*/
|
||||
"enabled": true
|
||||
/**
|
||||
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
|
||||
* a full file path.
|
||||
*
|
||||
* The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
|
||||
*
|
||||
* SUPPORTED TOKENS: <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<unscopedPackageName>.api.md"
|
||||
*/
|
||||
// "reportFileName": "<unscopedPackageName>.api.md",
|
||||
/**
|
||||
* Specifies the folder where the API report file is written. The file name portion is determined by
|
||||
* the "reportFileName" setting.
|
||||
*
|
||||
* The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
|
||||
* e.g. for an API review.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/etc/"
|
||||
*/
|
||||
// "reportFolder": "<projectFolder>/etc/",
|
||||
/**
|
||||
* Specifies the folder where the temporary report file is written. The file name portion is determined by
|
||||
* the "reportFileName" setting.
|
||||
*
|
||||
* After the temporary file is written to disk, it is compared with the file in the "reportFolder".
|
||||
* If they are different, a production build will fail.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/"
|
||||
*/
|
||||
// "reportTempFolder": "<projectFolder>/temp/"
|
||||
},
|
||||
/**
|
||||
* Configures how the doc model file (*.api.json) will be generated.
|
||||
*/
|
||||
"docModel": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate a doc model file.
|
||||
*/
|
||||
"enabled": true
|
||||
/**
|
||||
* The output path for the doc model file. The file extension should be ".api.json".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
|
||||
*/
|
||||
// "apiJsonFilePath": "<projectFolder>/temp/<unscopedPackageName>.api.json"
|
||||
},
|
||||
/**
|
||||
* Configures how the .d.ts rollup file will be generated.
|
||||
*/
|
||||
"dtsRollup": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate the .d.ts rollup file.
|
||||
*/
|
||||
"enabled": false,
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
|
||||
* This file will include all declarations that are exported by the main entry point.
|
||||
*
|
||||
* If the path is an empty string, then this file will not be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
|
||||
*/
|
||||
"untrimmedFilePath": "<projectFolder>/lib/<unscopedPackageName>.d.ts"
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
|
||||
* This file will include only declarations that are marked as "@public" or "@beta".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
|
||||
* This file will include only declarations that are marked as "@public".
|
||||
*
|
||||
* If the path is an empty string, then this file will not be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
|
||||
/**
|
||||
* When a declaration is trimmed, by default it will be replaced by a code comment such as
|
||||
* "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
|
||||
* declaration completely.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "omitTrimmingComments": true
|
||||
},
|
||||
/**
|
||||
* Configures how the tsdoc-metadata.json file will be generated.
|
||||
*/
|
||||
"tsdocMetadata": {
|
||||
/**
|
||||
* Whether to generate the tsdoc-metadata.json file.
|
||||
*
|
||||
* DEFAULT VALUE: true
|
||||
*/
|
||||
// "enabled": true,
|
||||
/**
|
||||
* Specifies where the TSDoc metadata file should be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* The default value is "<lookup>", which causes the path to be automatically inferred from the "tsdocMetadata",
|
||||
* "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
|
||||
* falls back to "tsdoc-metadata.json" in the package folder.
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<lookup>"
|
||||
*/
|
||||
// "tsdocMetadataFilePath": "<projectFolder>/dist/tsdoc-metadata.json"
|
||||
},
|
||||
/**
|
||||
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
|
||||
* will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
|
||||
* To use the OS's default newline kind, specify "os".
|
||||
*
|
||||
* DEFAULT VALUE: "crlf"
|
||||
*/
|
||||
// "newlineKind": "crlf",
|
||||
/**
|
||||
* Configures how API Extractor reports error and warning messages produced during analysis.
|
||||
*
|
||||
* There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
|
||||
*/
|
||||
"messages": {
|
||||
/**
|
||||
* Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
|
||||
* the input .d.ts files.
|
||||
*
|
||||
* TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
|
||||
*
|
||||
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||
*/
|
||||
"compilerMessageReporting": {
|
||||
/**
|
||||
* Configures the default routing for messages that don't match an explicit rule in this table.
|
||||
*/
|
||||
"default": {
|
||||
/**
|
||||
* Specifies whether the message should be written to the the tool's output log. Note that
|
||||
* the "addToApiReportFile" property may supersede this option.
|
||||
*
|
||||
* Possible values: "error", "warning", "none"
|
||||
*
|
||||
* Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
|
||||
* and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
|
||||
* the "--local" option), the warning is displayed but the build will not fail.
|
||||
*
|
||||
* DEFAULT VALUE: "warning"
|
||||
*/
|
||||
"logLevel": "warning"
|
||||
/**
|
||||
* When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
|
||||
* then the message will be written inside that file; otherwise, the message is instead logged according to
|
||||
* the "logLevel" option.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
// "TS2551": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
},
|
||||
/**
|
||||
* Configures handling of messages reported by API Extractor during its analysis.
|
||||
*
|
||||
* API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
|
||||
*
|
||||
* DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
|
||||
*/
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
// "ae-extra-release-tag": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
},
|
||||
/**
|
||||
* Configures handling of messages reported by the TSDoc parser when analyzing code comments.
|
||||
*
|
||||
* TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
|
||||
*
|
||||
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||
*/
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
// "tsdoc-link-tag-unescaped-text": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "3.7.1",
|
||||
"version": "3.8.1",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -54,7 +54,10 @@
|
||||
"fix": "eslint src/ --fix",
|
||||
"clean": "rm -rf dist/",
|
||||
"generate-docs": "yarn generate-docs:evm && yarn generate-docs:solana && yarn generate-snippets",
|
||||
"generate-docs:evm": "api-extractor run --local --config ./config/api-extractor-evm.json && api-documenter markdown -i ./temp -o ./docs/evm && rm -rf ./temp-evm && mv ./temp ./temp-evm",
|
||||
"generate-docs:evm": "yarn generate-docs:evm:base && yarn generate-docs:evm:magic && yarn generate-docs:evm:gnosis-safe",
|
||||
"generate-docs:evm:base": "api-extractor run --local --config ./config/api-extractor-evm.json && api-documenter markdown -i ./temp -o ./docs/evm && rm -rf ./temp-evm && mv ./temp ./temp-evm",
|
||||
"generate-docs:evm:magic": "api-extractor run --local --config ./config/api-extractor-magic.json && api-documenter markdown -i ./temp -o ./docs/evm/magic && rm -rf ./temp-evm-magic && mv ./temp ./temp-evm-magic && mv ./docs/evm/magic/react.usemagic.md ./docs/evm/react.usemagic.md && rm -rf ./docs/evm/magic",
|
||||
"generate-docs:evm:gnosis-safe": "api-extractor run --local --config ./config/api-extractor-gnosis.json && api-documenter markdown -i ./temp -o ./docs/evm/gnosis-safe && rm -rf ./temp-evm-gnosis-safe && mv ./temp ./temp-evm-gnosis-safe && mv ./docs/evm/gnosis-safe/react.usegnosis.md ./docs/evm/react.usegnosis.md && rm -rf ./docs/evm/gnosis-safe",
|
||||
"generate-docs:solana": "api-extractor run --local --config ./config/api-extractor-solana.json && api-documenter markdown -i ./temp -o ./docs/solana && rm -rf ./temp-solana && mv ./temp ./temp-solana",
|
||||
"generate-snippets": "node ./scripts/generate-snippets-evm.mjs && node ./scripts/generate-snippets-solana.mjs && node ./scripts/generate-feature-snippets-evm.mjs",
|
||||
"build": "tsc && preconstruct build",
|
||||
@@ -72,9 +75,9 @@
|
||||
"@microsoft/tsdoc": "^0.14.1",
|
||||
"@preconstruct/cli": "^2.2.1",
|
||||
"@solana/wallet-adapter-base": "^0.9.17",
|
||||
"@solana/wallet-adapter-phantom": "^0.9.17",
|
||||
"@solana/wallet-adapter-react": "^0.15.19",
|
||||
"@solana/wallet-adapter-wallets": "^0.19.0",
|
||||
"@solana/wallet-adapter-phantom": "^0.9.17",
|
||||
"@solana/web3.js": "^1.62.0",
|
||||
"@thirdweb-dev/auth": "*",
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
@@ -97,16 +100,17 @@
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@thirdweb-dev/react-core": "*",
|
||||
"@emotion/react": "^11.10.0",
|
||||
"@emotion/styled": "^11.10.0",
|
||||
"@google/model-viewer": "^2.1.1",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@safe-global/safe-core-sdk": "^3.2.0",
|
||||
"@safe-global/safe-ethers-adapters": "0.1.0-alpha.13",
|
||||
"@safe-global/safe-ethers-lib": "^1.7.0",
|
||||
"@tanstack/react-query": "^4.0.10",
|
||||
"@zag-js/menu": "^0.3.0",
|
||||
"@zag-js/react": "^0.3.1",
|
||||
"@thirdweb-dev/react-core": "*",
|
||||
"@zag-js/menu": "0.3.4",
|
||||
"@zag-js/react": "0.3.4",
|
||||
"buffer": "^6.0.3",
|
||||
"color": "^4.2.3",
|
||||
"copy-to-clipboard": "^3.3.2",
|
||||
@@ -119,9 +123,9 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@solana/wallet-adapter-base": "^0.9.17",
|
||||
"@solana/wallet-adapter-phantom": "^0.9.17",
|
||||
"@solana/wallet-adapter-react": "^0.15.19",
|
||||
"@solana/wallet-adapter-wallets": "^0.19.0",
|
||||
"@solana/wallet-adapter-phantom": "^0.9.17",
|
||||
"@solana/web3.js": "^1.62.0",
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"ethers": ">=5.5.1",
|
||||
@@ -149,4 +153,4 @@
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,19 @@ import {
|
||||
CarbonPlayFilledAlt,
|
||||
} from "./Icons";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import React, {
|
||||
ReactNode,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
lazy,
|
||||
Suspense,
|
||||
} from "react";
|
||||
import useDimensions from "react-cool-dimensions";
|
||||
|
||||
const ModelViewer = lazy(() => import("./ModelViewer"));
|
||||
|
||||
export interface SharedMediaProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
@@ -25,6 +35,8 @@ export interface SharedMediaProps {
|
||||
* Show the media controls (where applicable) (default false)
|
||||
*/
|
||||
controls?: HTMLVideoElement["controls"];
|
||||
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,6 +57,8 @@ export interface MediaRendererProps extends SharedMediaProps {
|
||||
* The media poster image uri. (if applicable)
|
||||
*/
|
||||
poster?: string | null;
|
||||
width?: string;
|
||||
height?: string;
|
||||
}
|
||||
|
||||
interface PlayButtonProps {
|
||||
@@ -399,6 +413,7 @@ LinkPlayer.displayName = "LinkPlayer";
|
||||
* This component can be used to render any media type, including image, audio, video, and html files.
|
||||
* Its convenient for rendering NFT media files, as these can be a variety of different types.
|
||||
* The component falls back to a external link if the media type is not supported.
|
||||
* The default size is 300px by 300px, but this can be changed using the `width` and `height` props.
|
||||
*
|
||||
* Props: {@link MediaRendererProps}
|
||||
*
|
||||
@@ -420,10 +435,10 @@ export const MediaRenderer = React.forwardRef<
|
||||
MediaRendererProps
|
||||
>(
|
||||
(
|
||||
{ src, poster, alt, requireInteraction = false, style, ...restProps },
|
||||
{ src, poster, alt, requireInteraction = false, width = "300px", height = "300px", style, ...restProps },
|
||||
ref,
|
||||
) => {
|
||||
const mergedStyle: React.CSSProperties = { objectFit: "contain", ...style };
|
||||
const mergedStyle: React.CSSProperties = { objectFit: "contain", width, height, ...style };
|
||||
const videoOrImageSrc = useResolvedMediaType(src ?? undefined);
|
||||
const possiblePosterSrc = useResolvedMediaType(poster ?? undefined);
|
||||
if (!videoOrImageSrc.mimeType) {
|
||||
@@ -445,6 +460,30 @@ export const MediaRenderer = React.forwardRef<
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
} else if (videoOrImageSrc.mimeType.startsWith("model")) {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
poster ? (
|
||||
<img
|
||||
style={mergedStyle}
|
||||
src={poster}
|
||||
alt={alt}
|
||||
ref={ref as unknown as React.LegacyRef<HTMLImageElement>}
|
||||
{...restProps}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<ModelViewer
|
||||
style={mergedStyle}
|
||||
src={videoOrImageSrc.url || ""}
|
||||
poster={poster}
|
||||
alt={alt}
|
||||
{...restProps}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
} else if (shouldRenderVideoTag(videoOrImageSrc.mimeType)) {
|
||||
return (
|
||||
<VideoPlayer
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { MediaRendererProps } from "./MediaRenderer";
|
||||
import "@google/model-viewer";
|
||||
import type { ModelViewerElement } from "@google/model-viewer";
|
||||
import React from "react";
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"model-viewer": Partial<ModelViewerElement>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const ModelViewer = React.forwardRef<HTMLDivElement, MediaRendererProps>(
|
||||
({ src, alt, poster, style }, ref) => {
|
||||
return (
|
||||
<div style={{ ...style }} ref={ref}>
|
||||
{src ? (
|
||||
<model-viewer
|
||||
src={src}
|
||||
alt={alt || "3D Model"}
|
||||
camera-controls
|
||||
poster={poster ? poster : null}
|
||||
style={{ width: '100%', height: '100%'} as CSSStyleDeclaration}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ModelViewer.displayName = "ModelViewer";
|
||||
|
||||
export default ModelViewer;
|
||||
@@ -10,10 +10,15 @@ export interface ThirdwebNftMediaProps extends SharedMediaProps {
|
||||
* The NFT metadata of the NFT returned by the thirdweb sdk.
|
||||
*/
|
||||
metadata: NFTMetadata;
|
||||
width?: string;
|
||||
height?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This component can be used to render NFTs from the thirdweb SDK.
|
||||
* It will render the animation_url if it exists, otherwise it will render the image.
|
||||
* The default size is 300px by 300px, but this can be changed using the `width` and `height` props.
|
||||
*
|
||||
* Props: {@link ThirdwebNftMediaProps}
|
||||
*
|
||||
* @example
|
||||
@@ -38,13 +43,16 @@ export interface ThirdwebNftMediaProps extends SharedMediaProps {
|
||||
export const ThirdwebNftMedia = React.forwardRef<
|
||||
HTMLMediaElement,
|
||||
ThirdwebNftMediaProps
|
||||
>(({ metadata, ...props }, ref) => {
|
||||
>(({ metadata, width = "300px", height = "300px", style, ...props }, ref) => {
|
||||
return (
|
||||
<MediaRenderer
|
||||
src={metadata.animation_url || metadata.image}
|
||||
poster={metadata.image}
|
||||
alt={metadata.name?.toString() || ""}
|
||||
ref={ref}
|
||||
width={width}
|
||||
height={height}
|
||||
style={{ ...style }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,57 @@
|
||||
# @thirdweb-dev/sdk
|
||||
|
||||
## 3.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#550](https://github.com/thirdweb-dev/js/pull/550) [`448b357`](https://github.com/thirdweb-dev/js/commit/448b357ed4b864e05e531837ca3bc97fbcab3291) Thanks [@adam-maj](https://github.com/adam-maj)! - Add solidity stack trace to error messages
|
||||
|
||||
## 3.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#474](https://github.com/thirdweb-dev/js/pull/474) [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Introducing marketplace-v3 class
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#547](https://github.com/thirdweb-dev/js/pull/547) [`0b38dac`](https://github.com/thirdweb-dev/js/commit/0b38dac852e54d0bccd263631f4342aee39d7c29) Thanks [@adam-maj](https://github.com/adam-maj)! - Add proper error hanling for no funds error
|
||||
|
||||
- [#545](https://github.com/thirdweb-dev/js/pull/545) [`29d0c62`](https://github.com/thirdweb-dev/js/commit/29d0c62610cdb88f75d19574bddcb028b5c40393) Thanks [@adam-maj](https://github.com/adam-maj)! - Add error info fields to transaction error
|
||||
|
||||
- [#474](https://github.com/thirdweb-dev/js/pull/474) [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Support for Plugin Pattern based contracts
|
||||
|
||||
- [#539](https://github.com/thirdweb-dev/js/pull/539) [`f295ec6`](https://github.com/thirdweb-dev/js/commit/f295ec67b4cc422c51beda94cbc4d54a31ce9566) Thanks [@adam-maj](https://github.com/adam-maj)! - Upgrade error messages on failed transactions
|
||||
|
||||
- [#544](https://github.com/thirdweb-dev/js/pull/544) [`f1aecc3`](https://github.com/thirdweb-dev/js/commit/f1aecc30499e39fb23a205bade939bd939f0d0e4) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Contracts v3.4.0
|
||||
|
||||
- Updated dependencies [[`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c), [`912738b`](https://github.com/thirdweb-dev/js/commit/912738bd0afdf81e118c720811911ba3d1979ac0), [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c), [`f1aecc3`](https://github.com/thirdweb-dev/js/commit/f1aecc30499e39fb23a205bade939bd939f0d0e4)]:
|
||||
- @thirdweb-dev/contracts-js@1.3.0
|
||||
- @thirdweb-dev/storage@1.0.7
|
||||
|
||||
## 3.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#542](https://github.com/thirdweb-dev/js/pull/542) [`ec1f232`](https://github.com/thirdweb-dev/js/commit/ec1f2324c7a0e58c2d2a209cfe0d194807ba5b70) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Solana - Expose getTransaction pagination options + transactions for given wallet
|
||||
|
||||
- [#540](https://github.com/thirdweb-dev/js/pull/540) [`d29f7e7`](https://github.com/thirdweb-dev/js/commit/d29f7e78f09063630e812dd41164d8a41d4f97dd) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - expose `getMintTransaction()` for fine grain control on mint calls
|
||||
|
||||
## 3.7.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#410](https://github.com/thirdweb-dev/js/pull/410) [`b45d761`](https://github.com/thirdweb-dev/js/commit/b45d761f8f2695b48312ec6eff15e0549e6a8a41) Thanks [@adam-maj](https://github.com/adam-maj)! - Add creator and royalty settings to drop
|
||||
|
||||
- [#535](https://github.com/thirdweb-dev/js/pull/535) [`5f8ec45`](https://github.com/thirdweb-dev/js/commit/5f8ec45af7c81d075a84846098f38cad90d03fb9) Thanks [@jnsdls](https://github.com/jnsdls)! - [Solana] - add `burnBatch()` and `getTransactions()` for NFTCollection and NFTDrop
|
||||
|
||||
- [#471](https://github.com/thirdweb-dev/js/pull/471) [`04de5b2`](https://github.com/thirdweb-dev/js/commit/04de5b278362573991140c3a496de7451e5c770d) Thanks [@adam-maj](https://github.com/adam-maj)! - Expose getSnapshotEntryForAddress
|
||||
|
||||
## 3.7.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#525](https://github.com/thirdweb-dev/js/pull/525) [`19b8e18`](https://github.com/thirdweb-dev/js/commit/19b8e18b6bf68e374c817fefbf2fd29ac5573052) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix `getClaimTransaction().estimateGasCostInEther()` for claims costing native currency
|
||||
|
||||
## 3.7.1
|
||||
|
||||
## 3.7.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "3.7.1",
|
||||
"version": "3.8.1",
|
||||
"description": "The main thirdweb SDK.",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
|
||||
"license": "Apache-2.0",
|
||||
@@ -54,7 +54,7 @@
|
||||
"generate-docs:solana": "api-extractor run --local --config ./config/api-extractor-solana.json && api-documenter markdown -i ./temp -o ./docs/solana && rm -rf ./temp-solana && mv ./temp ./temp-solana",
|
||||
"generate-snippets": "node ./scripts/generate-snippets.mjs && node ./scripts/generate-feature-snippets-evm.mjs",
|
||||
"build": "tsc && preconstruct build",
|
||||
"test:evm:all": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 30000 --parallel './test/evm/**/*.test.ts'",
|
||||
"test:evm:all": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 90000 --parallel './test/evm/**/*.test.ts'",
|
||||
"test:evm": "make test-evm",
|
||||
"test:evm:single": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 90000",
|
||||
"node:solana:start": "DEBUG='amman:(info|error|debug)' amman start --forceClone",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Feature } from "../constants/contract-features";
|
||||
import { NATIVE_TOKENS, SUPPORTED_CHAIN_ID } from "../constants/index";
|
||||
import { ContractSource } from "../schema";
|
||||
import { BigNumber, BigNumberish, ethers, providers } from "ethers";
|
||||
|
||||
/**
|
||||
@@ -264,176 +265,137 @@ export type FunctionInfo = {
|
||||
value: BigNumber;
|
||||
};
|
||||
|
||||
export type TransactionErrorInfo = {
|
||||
reason: string;
|
||||
from: string;
|
||||
to: string;
|
||||
network: providers.Network;
|
||||
method?: string;
|
||||
data?: string;
|
||||
rpcUrl?: string;
|
||||
value?: BigNumber;
|
||||
hash?: string;
|
||||
contractName?: string;
|
||||
sources?: ContractSource[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class TransactionError extends Error {
|
||||
public reason: string;
|
||||
public from: string;
|
||||
public to: string;
|
||||
public data: string;
|
||||
public chain: providers.Network;
|
||||
public rpcUrl: string;
|
||||
public functionInfo: FunctionInfo | undefined;
|
||||
#reason: string;
|
||||
#info: TransactionErrorInfo;
|
||||
|
||||
constructor(
|
||||
reason: string,
|
||||
from: string,
|
||||
to: string,
|
||||
data: string,
|
||||
network: providers.Network,
|
||||
rpcUrl: string,
|
||||
raw: string,
|
||||
functionInfo: FunctionInfo | undefined,
|
||||
) {
|
||||
let builtErrorMsg = "\n| Contract transaction failed |\n\n";
|
||||
builtErrorMsg += `Message: ${reason}`;
|
||||
builtErrorMsg += "\n\n| Transaction info |\n";
|
||||
builtErrorMsg += withSpaces("from", from);
|
||||
builtErrorMsg += withSpaces("to", to);
|
||||
builtErrorMsg += withSpaces(
|
||||
constructor(info: TransactionErrorInfo) {
|
||||
let errorMessage = `\n\n\n╔═══════════════════╗\n║ TRANSACTION ERROR ║\n╚═══════════════════╝\n\n`;
|
||||
errorMessage += `Reason: ${info.reason}`;
|
||||
errorMessage += `\n\n\n╔═════════════════════════╗\n║ TRANSACTION INFORMATION ║\n╚═════════════════════════╝\n`;
|
||||
errorMessage += withSpaces("from", info.from);
|
||||
errorMessage += withSpaces(
|
||||
"to",
|
||||
info.contractName ? `${info.to} (${info.contractName})` : info.to,
|
||||
);
|
||||
errorMessage += withSpaces(
|
||||
`chain`,
|
||||
`${network.name} (${network.chainId})`,
|
||||
`${info.network.name} (${info.network.chainId})`,
|
||||
);
|
||||
|
||||
if (functionInfo) {
|
||||
builtErrorMsg += "\n\n| Failed contract call info |\n";
|
||||
builtErrorMsg += withSpaces("function", functionInfo.signature);
|
||||
builtErrorMsg += withSpaces(
|
||||
`arguments`,
|
||||
JSON.stringify(functionInfo.inputs, null, 2),
|
||||
if (info.rpcUrl) {
|
||||
try {
|
||||
const url = new URL(info.rpcUrl);
|
||||
errorMessage += withSpaces(`rpc`, url.hostname);
|
||||
} catch (e2) {
|
||||
// ignore if can't parse URL
|
||||
}
|
||||
}
|
||||
|
||||
if (info.hash) {
|
||||
errorMessage += withSpaces(`tx hash`, info.hash);
|
||||
}
|
||||
|
||||
if (info.value && info.value.gt(0)) {
|
||||
errorMessage += withSpaces(
|
||||
"value",
|
||||
`${ethers.utils.formatEther(info.value)} ${
|
||||
NATIVE_TOKENS[info.network.chainId as SUPPORTED_CHAIN_ID]?.symbol ||
|
||||
""
|
||||
}`,
|
||||
);
|
||||
if (functionInfo.value.gt(0)) {
|
||||
builtErrorMsg += withSpaces(
|
||||
"value",
|
||||
`${ethers.utils.formatEther(functionInfo.value)} ${
|
||||
NATIVE_TOKENS[network.chainId as SUPPORTED_CHAIN_ID]?.symbol
|
||||
}`,
|
||||
}
|
||||
|
||||
errorMessage += withSpaces(`data`, `${info.data}`);
|
||||
|
||||
if (info.method) {
|
||||
errorMessage += withSpaces("method", info.method);
|
||||
}
|
||||
|
||||
if (info.sources) {
|
||||
const revertFile = info.sources.find((file) =>
|
||||
file.source.includes(info.reason),
|
||||
);
|
||||
|
||||
if (revertFile) {
|
||||
const lines = revertFile.source
|
||||
.split("\n")
|
||||
.map((line, index) => `${index + 1} ${line}`);
|
||||
const revertLine = lines.findIndex((line) =>
|
||||
line.includes(info.reason),
|
||||
);
|
||||
lines[revertLine] += " <-- REVERT";
|
||||
const errorLines = lines.slice(revertLine - 8, revertLine + 4);
|
||||
|
||||
errorMessage += `\n\n\n╔══════════════════════╗\n║ SOLIDITY STACK TRACE ║\n╚══════════════════════╝\n\n`;
|
||||
errorMessage += `File: ${revertFile.filename.replace(
|
||||
"node_modules/",
|
||||
"",
|
||||
)}\n\n`;
|
||||
errorMessage += errorLines.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(rpcUrl);
|
||||
builtErrorMsg += withSpaces(`RPC`, url.hostname);
|
||||
} catch (e2) {
|
||||
// ignore if can't parse URL
|
||||
}
|
||||
builtErrorMsg += "\n\n";
|
||||
builtErrorMsg += "| Raw error |";
|
||||
builtErrorMsg += "\n\n";
|
||||
builtErrorMsg += raw;
|
||||
super(builtErrorMsg);
|
||||
this.reason = reason;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.data = data;
|
||||
this.chain = network;
|
||||
this.rpcUrl = rpcUrl;
|
||||
this.functionInfo = functionInfo;
|
||||
errorMessage += `\n\n\n╔═════════════════════╗\n║ DEBUGGING RESOURCES ║\n╚═════════════════════╝\n\n`;
|
||||
errorMessage += `Need helping debugging? Join our Discord: https://discord.gg/thirdweb`;
|
||||
errorMessage += `\n\n`;
|
||||
|
||||
super(errorMessage);
|
||||
|
||||
this.#reason = info.reason;
|
||||
this.#info = info;
|
||||
}
|
||||
|
||||
// Keep reason here for backwards compatibility
|
||||
get reason(): string {
|
||||
return this.#reason;
|
||||
}
|
||||
|
||||
get info(): TransactionErrorInfo {
|
||||
return this.#info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param data
|
||||
* @param contractInterface
|
||||
*/
|
||||
function parseFunctionInfo(
|
||||
data: string,
|
||||
contractInterface: ethers.utils.Interface,
|
||||
): FunctionInfo | undefined {
|
||||
try {
|
||||
const fnFragment = contractInterface.parseTransaction({
|
||||
data,
|
||||
});
|
||||
const results: Record<string, any> = {};
|
||||
const args = fnFragment.args;
|
||||
fnFragment.functionFragment.inputs.forEach((param, index) => {
|
||||
if (Array.isArray(args[index])) {
|
||||
const obj: Record<string, unknown> = {};
|
||||
const components = param.components;
|
||||
if (components) {
|
||||
const arr = args[index];
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
const name = components[i].name;
|
||||
obj[name] = arr[i];
|
||||
}
|
||||
results[param.name] = obj;
|
||||
}
|
||||
} else {
|
||||
results[param.name] = args[index];
|
||||
}
|
||||
});
|
||||
return {
|
||||
signature: fnFragment.signature,
|
||||
inputs: results,
|
||||
value: fnFragment.value,
|
||||
};
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
export function parseRevertReason(error: any): string {
|
||||
if (error.reason) {
|
||||
return error.reason as string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param error
|
||||
* @param network
|
||||
* @param signerAddress
|
||||
* @param contractAddress
|
||||
* @param contractInterface
|
||||
*/
|
||||
export async function convertToTWError(
|
||||
error: any,
|
||||
network: ethers.providers.Network,
|
||||
signerAddress: string,
|
||||
contractAddress: string,
|
||||
contractInterface: ethers.utils.Interface,
|
||||
): Promise<TransactionError> {
|
||||
let raw: string;
|
||||
// I think this code path should never be hit, but just in case
|
||||
|
||||
let errorString: string = error;
|
||||
if (typeof error === "object") {
|
||||
// metamask errors comes as objects, apply parsing on data object
|
||||
raw = JSON.stringify(error);
|
||||
} else {
|
||||
// not sure what this is, just throw it back
|
||||
raw = error.toString();
|
||||
// MetaMask errors come as objects so parse them first
|
||||
errorString = JSON.stringify(error);
|
||||
} else if (typeof error !== "string") {
|
||||
errorString = error.toString();
|
||||
}
|
||||
let reason =
|
||||
parseMessageParts(/.*?"message[^a-zA-Z0-9]*([^"\\]*).*?/, raw) ||
|
||||
parseMessageParts(/.*?"reason[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
if (reason && reason.toLowerCase().includes("cannot estimate gas")) {
|
||||
// the error might be in the next reason block
|
||||
const nextReason = parseMessageParts(
|
||||
/.*?"reason[^a-zA-Z0-9]*([^"\\]*).*?/,
|
||||
raw.slice(raw.indexOf(reason) + reason.length),
|
||||
);
|
||||
if (nextReason) {
|
||||
reason = nextReason;
|
||||
}
|
||||
}
|
||||
const data = parseMessageParts(/.*?"data[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
const rpcUrl = parseMessageParts(/.*?"url[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
let from = parseMessageParts(/.*?"from[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
let to = parseMessageParts(/.*?"to[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
if (to === "") {
|
||||
// fallback to contractAddress
|
||||
to = contractAddress;
|
||||
}
|
||||
if (from === "") {
|
||||
// fallback to signerAddress
|
||||
from = signerAddress;
|
||||
}
|
||||
const functionInfo =
|
||||
data.length > 0 ? parseFunctionInfo(data, contractInterface) : undefined;
|
||||
return new TransactionError(
|
||||
reason,
|
||||
from,
|
||||
to,
|
||||
data,
|
||||
network,
|
||||
rpcUrl,
|
||||
raw,
|
||||
functionInfo,
|
||||
|
||||
return (
|
||||
parseMessageParts(/.*?"message":"([^"\\]*).*?/, errorString) ||
|
||||
parseMessageParts(/.*?"reason":"([^"\\]*).*?/, errorString) ||
|
||||
error.message ||
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
@@ -441,10 +403,18 @@ function withSpaces(label: string, content: string) {
|
||||
if (content === "") {
|
||||
return content;
|
||||
}
|
||||
|
||||
const spaces = Array(10 - label.length)
|
||||
.fill(" ")
|
||||
.join("");
|
||||
return `\n${label}:${spaces}${content}`;
|
||||
|
||||
if (content.includes("\n")) {
|
||||
content = "\n\n " + content.split("\n").join(`\n `);
|
||||
} else {
|
||||
content = `${spaces}${content}`;
|
||||
}
|
||||
|
||||
return `\n${label}:${content}`;
|
||||
}
|
||||
|
||||
function parseMessageParts(regex: RegExp, raw: string): string {
|
||||
|
||||
@@ -10,4 +10,7 @@ export {
|
||||
fetchCurrencyMetadata,
|
||||
normalizePriceValue,
|
||||
} from "./currency";
|
||||
export { convertToReadableQuantity } from "./claim-conditions";
|
||||
export {
|
||||
convertToReadableQuantity,
|
||||
fetchSnapshotEntryForAddress,
|
||||
} from "./claim-conditions";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DEFAULT_QUERY_ALL_COUNT } from "../../core/schema/QueryParams";
|
||||
import { MAX_BPS } from "../../core/schema/shared";
|
||||
import {
|
||||
InterfaceId_IERC1155,
|
||||
@@ -15,7 +16,13 @@ import type { IERC1155, IERC165, IERC721 } from "@thirdweb-dev/contracts-js";
|
||||
import ERC165Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC165.json";
|
||||
import ERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC721.json";
|
||||
import ERC1155Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC1155.json";
|
||||
import { BigNumber, BigNumberish, Contract, providers } from "ethers";
|
||||
import {
|
||||
BigNumber,
|
||||
BigNumberish,
|
||||
Contract,
|
||||
ContractFunction,
|
||||
providers,
|
||||
} from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
@@ -235,3 +242,18 @@ export function isWinningBid(
|
||||
const buffer = newBidPrice.sub(winningPrice).mul(MAX_BPS).div(winningPrice);
|
||||
return buffer.gte(bidBuffer);
|
||||
}
|
||||
|
||||
export async function getAllInBatches(
|
||||
start: number,
|
||||
end: number,
|
||||
fn: ContractFunction,
|
||||
): Promise<any[]> {
|
||||
let batches: any[] = [];
|
||||
while (end - start > DEFAULT_QUERY_ALL_COUNT) {
|
||||
batches.push(fn(start, start + DEFAULT_QUERY_ALL_COUNT - 1));
|
||||
start += DEFAULT_QUERY_ALL_COUNT;
|
||||
}
|
||||
batches.push(fn(start, end - 1));
|
||||
|
||||
return await Promise.all(batches);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ export async function getCompositePluginABI(
|
||||
AbiSchema.parse(abi),
|
||||
"PluginRouter",
|
||||
);
|
||||
|
||||
if (isPluginRouter) {
|
||||
const contract = new ContractWrapper<IRouter>(
|
||||
provider,
|
||||
@@ -85,5 +84,7 @@ export function joinABIs(abis: Abi[]): Abi {
|
||||
);
|
||||
});
|
||||
|
||||
return AbiSchema.parse(filteredABIs);
|
||||
const finalABIs = filteredABIs.filter((item) => item.type !== "constructor");
|
||||
|
||||
return AbiSchema.parse(finalABIs);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ import {
|
||||
FEATURE_GASLESS,
|
||||
FEATURE_PACK_VRF,
|
||||
FEATURE_PLUGIN_ROUTER,
|
||||
FEATURE_DIRECT_LISTINGS,
|
||||
FEATURE_ENGLISH_AUCTIONS,
|
||||
FEATURE_OFFERS,
|
||||
} from "./thirdweb-features";
|
||||
|
||||
/**
|
||||
@@ -110,7 +113,10 @@ export type Feature =
|
||||
| typeof FEATURE_OWNER
|
||||
| typeof FEATURE_GASLESS
|
||||
| typeof FEATURE_PACK_VRF
|
||||
| typeof FEATURE_PLUGIN_ROUTER;
|
||||
| typeof FEATURE_PLUGIN_ROUTER
|
||||
| typeof FEATURE_DIRECT_LISTINGS
|
||||
| typeof FEATURE_ENGLISH_AUCTIONS
|
||||
| typeof FEATURE_OFFERS;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -141,4 +147,7 @@ export const SUPPORTED_FEATURES: Record<string, Feature> = {
|
||||
[FEATURE_GASLESS.name]: FEATURE_GASLESS,
|
||||
[FEATURE_PACK_VRF.name]: FEATURE_PACK_VRF,
|
||||
[FEATURE_PLUGIN_ROUTER.name]: FEATURE_PLUGIN_ROUTER,
|
||||
[FEATURE_DIRECT_LISTINGS.name]: FEATURE_DIRECT_LISTINGS,
|
||||
[FEATURE_ENGLISH_AUCTIONS.name]: FEATURE_ENGLISH_AUCTIONS,
|
||||
[FEATURE_OFFERS.name]: FEATURE_OFFERS,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import IERC2771ContextAbi from "@thirdweb-dev/contracts-js/dist/abis/ERC2771Context.json";
|
||||
import IAppURI from "@thirdweb-dev/contracts-js/dist/abis/IAppURI.json";
|
||||
import IContractMetadataAbi from "@thirdweb-dev/contracts-js/dist/abis/IContractMetadata.json";
|
||||
import IDirectListingsAbi from "@thirdweb-dev/contracts-js/dist/abis/IDirectListings.json";
|
||||
import IEnglishAuctionsAbi from "@thirdweb-dev/contracts-js/dist/abis/IEnglishAuctions.json";
|
||||
import IOffersAbi from "@thirdweb-dev/contracts-js/dist/abis/IOffers.json";
|
||||
import IPackVRFAbi from "@thirdweb-dev/contracts-js/dist/abis/IPackVRFDirect.json";
|
||||
import IPermissionsAbi from "@thirdweb-dev/contracts-js/dist/abis/IPermissions.json";
|
||||
import IPermissionsEnumerableAbi from "@thirdweb-dev/contracts-js/dist/abis/IPermissionsEnumerable.json";
|
||||
@@ -133,3 +136,39 @@ export const FEATURE_PLUGIN_ROUTER = {
|
||||
abis: [IRouterAbi],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
export const FEATURE_DIRECT_LISTINGS = {
|
||||
name: "DirectListings",
|
||||
namespace: "direct.listings",
|
||||
docLinks: {
|
||||
// TODO
|
||||
sdk: "",
|
||||
contracts: "",
|
||||
},
|
||||
abis: [IDirectListingsAbi],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
export const FEATURE_ENGLISH_AUCTIONS = {
|
||||
name: "EnglishAuctions",
|
||||
namespace: "english.auctions",
|
||||
docLinks: {
|
||||
// TODO
|
||||
sdk: "",
|
||||
contracts: "",
|
||||
},
|
||||
abis: [IEnglishAuctionsAbi],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
export const FEATURE_OFFERS = {
|
||||
name: "Offers",
|
||||
namespace: "offers",
|
||||
docLinks: {
|
||||
// TODO
|
||||
sdk: "",
|
||||
contracts: "",
|
||||
},
|
||||
abis: [IOffersAbi],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { getPrebuiltInfo } from "../common/legacy";
|
||||
import { fetchAbiFromAddress } from "../common/metadata-resolver";
|
||||
import { getCompositePluginABI } from "../common/plugin";
|
||||
import { ALL_ROLES } from "../common/role";
|
||||
import { getSignerAndProvider } from "../core/classes/rpc-connection-handler";
|
||||
import type {
|
||||
@@ -20,7 +21,11 @@ import {
|
||||
TokenErc721ContractSchema,
|
||||
VoteContractSchema,
|
||||
} from "../schema";
|
||||
import { Abi, CustomContractSchema } from "../schema/contracts/custom";
|
||||
import {
|
||||
Abi,
|
||||
AbiSchema,
|
||||
CustomContractSchema,
|
||||
} from "../schema/contracts/custom";
|
||||
import { DropErc20ContractSchema } from "../schema/contracts/drop-erc20";
|
||||
import { MultiwrapContractSchema } from "../schema/contracts/multiwrap";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
@@ -152,6 +157,7 @@ export const MarketplaceInitializer = {
|
||||
if (abi) {
|
||||
return abi;
|
||||
}
|
||||
|
||||
// Deprecated - only needed for backwards compatibility with non-released contracts - should remove in v4
|
||||
return (
|
||||
await import("@thirdweb-dev/contracts-js/dist/abis/Marketplace.json")
|
||||
@@ -159,6 +165,56 @@ export const MarketplaceInitializer = {
|
||||
},
|
||||
};
|
||||
|
||||
export const MarketplaceV3Initializer = {
|
||||
name: "MarketplaceRouter" as const,
|
||||
contractType: "marketplace-v3" as const,
|
||||
schema: MarketplaceContractSchema,
|
||||
roles: ["admin", "lister", "asset"] as const,
|
||||
initialize: async (
|
||||
...[network, address, storage, options]: InitalizeParams
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
MarketplaceV3Initializer.getAbi(address, provider, storage),
|
||||
import("./prebuilt-implementations/marketplacev3"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
|
||||
const parsedABI = typeof abi === "string" ? JSON.parse(abi) : abi;
|
||||
const compositeABI = await getCompositePluginABI(
|
||||
address,
|
||||
AbiSchema.parse(parsedABI),
|
||||
provider,
|
||||
options,
|
||||
storage,
|
||||
);
|
||||
|
||||
return new contract.MarketplaceV3(
|
||||
network,
|
||||
address,
|
||||
storage,
|
||||
options,
|
||||
compositeABI,
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async (
|
||||
address: string,
|
||||
provider: ethers.providers.Provider,
|
||||
storage: ThirdwebStorage,
|
||||
) => {
|
||||
const abi = await fetchAbiFromAddress(address, provider, storage);
|
||||
if (abi) {
|
||||
return abi;
|
||||
}
|
||||
|
||||
// Deprecated - only needed for backwards compatibility with non-released contracts - should remove in v4
|
||||
return (
|
||||
await import("@thirdweb-dev/contracts-js/dist/abis/MarketplaceV3.json")
|
||||
).default;
|
||||
},
|
||||
};
|
||||
|
||||
export const MultiwrapInitializer = {
|
||||
name: "Multiwrap" as const,
|
||||
contractType: "multiwrap" as const,
|
||||
@@ -558,6 +614,7 @@ export const PREBUILT_CONTRACTS_MAP = {
|
||||
[EditionDropInitializer.contractType]: EditionDropInitializer,
|
||||
[EditionInitializer.contractType]: EditionInitializer,
|
||||
[MarketplaceInitializer.contractType]: MarketplaceInitializer,
|
||||
[MarketplaceV3Initializer.contractType]: MarketplaceV3Initializer,
|
||||
[MultiwrapInitializer.contractType]: MultiwrapInitializer,
|
||||
[NFTCollectionInitializer.contractType]: NFTCollectionInitializer,
|
||||
[NFTDropInitializer.contractType]: NFTDropInitializer,
|
||||
|
||||
@@ -258,6 +258,19 @@ export class Edition extends StandardErc1155<TokenERC1155> {
|
||||
return this.erc1155.mintTo(to, metadataWithSupply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a mint transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a mint transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param receiver - Address you want to send the token to
|
||||
* @param metadataWithSupply - The metadata of the NFT you want to mint
|
||||
*/
|
||||
public async getMintTransaction(
|
||||
receiver: string,
|
||||
metadataWithSupply: EditionMetadataOrUri,
|
||||
) {
|
||||
return this.erc1155.getMintTransaction(receiver, metadataWithSupply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the supply of an existing NFT and mint it to the connected wallet
|
||||
*
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
import { assertEnabled, detectContractFeature } from "../../common";
|
||||
import {
|
||||
FEATURE_DIRECT_LISTINGS,
|
||||
FEATURE_ENGLISH_AUCTIONS,
|
||||
FEATURE_OFFERS,
|
||||
} from "../../constants/thirdweb-features";
|
||||
import { ContractEncoder } from "../../core/classes/contract-encoder";
|
||||
import { ContractEvents } from "../../core/classes/contract-events";
|
||||
import { ContractInterceptor } from "../../core/classes/contract-interceptor";
|
||||
import { ContractMetadata } from "../../core/classes/contract-metadata";
|
||||
import { ContractPlatformFee } from "../../core/classes/contract-platform-fee";
|
||||
import { ContractRoles } from "../../core/classes/contract-roles";
|
||||
import { ContractWrapper } from "../../core/classes/contract-wrapper";
|
||||
import { GasCostEstimator } from "../../core/classes/gas-cost-estimator";
|
||||
import { MarketplaceV3DirectListings } from "../../core/classes/marketplacev3-direct-listings";
|
||||
import { MarketplaceV3EnglishAuctions } from "../../core/classes/marketplacev3-english-auction";
|
||||
import { MarketplaceV3Offers } from "../../core/classes/marketplacev3-offers";
|
||||
import { UpdateableNetwork } from "../../core/interfaces/contract";
|
||||
import { NetworkOrSignerOrProvider } from "../../core/types";
|
||||
import { Abi } from "../../schema/contracts/custom";
|
||||
import { MarketplaceContractSchema } from "../../schema/contracts/marketplace";
|
||||
import { SDKOptions } from "../../schema/sdk-options";
|
||||
import type {
|
||||
MarketplaceV3 as MarketplaceV3Contract,
|
||||
DirectListingsLogic,
|
||||
EnglishAuctionsLogic,
|
||||
OffersLogic,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { CallOverrides } from "ethers";
|
||||
|
||||
/**
|
||||
* Create your own whitelabel marketplace that enables users to buy and sell any digital assets.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```javascript
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "marketplace");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class MarketplaceV3 implements UpdateableNetwork {
|
||||
static contractRoles = ["admin", "lister", "asset"] as const;
|
||||
|
||||
public abi: Abi;
|
||||
private contractWrapper: ContractWrapper<MarketplaceV3Contract>;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
public encoder: ContractEncoder<MarketplaceV3Contract>;
|
||||
public events: ContractEvents<MarketplaceV3Contract>;
|
||||
public estimator: GasCostEstimator<MarketplaceV3Contract>;
|
||||
public platformFees: ContractPlatformFee<MarketplaceV3Contract>;
|
||||
public metadata: ContractMetadata<
|
||||
MarketplaceV3Contract,
|
||||
typeof MarketplaceContractSchema
|
||||
>;
|
||||
public roles: ContractRoles<
|
||||
MarketplaceV3Contract,
|
||||
typeof MarketplaceV3.contractRoles[number]
|
||||
>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public interceptor: ContractInterceptor<MarketplaceV3Contract>;
|
||||
/**
|
||||
* Direct listings
|
||||
* @remarks Create and manage direct listings in your marketplace.
|
||||
* ```javascript
|
||||
* // Data of the listing you want to create
|
||||
* const listing = {
|
||||
* // address of the contract the asset you want to list is on
|
||||
* assetContractAddress: "0x...",
|
||||
* // token ID of the asset you want to list
|
||||
* tokenId: "0",
|
||||
* // how many of the asset you want to list
|
||||
* quantity: 1,
|
||||
* // address of the currency contract that will be used to pay for the listing
|
||||
* currencyContractAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* // The price to pay per unit of NFTs listed.
|
||||
* pricePerToken: 1.5,
|
||||
* // when should the listing open up for offers
|
||||
* startTimestamp: new Date(Date.now()),
|
||||
* // how long the listing will be open for
|
||||
* endTimestamp: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000),
|
||||
* // Whether the listing is reserved for a specific set of buyers.
|
||||
* isReservedListing: false
|
||||
* }
|
||||
*
|
||||
* const tx = await contract.directListings.createListing(listing);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const id = tx.id; // the id of the newly created listing
|
||||
*
|
||||
* // And on the buyers side:
|
||||
* // The ID of the listing you want to buy from
|
||||
* const listingId = 0;
|
||||
* // Quantity of the asset you want to buy
|
||||
* const quantityDesired = 1;
|
||||
*
|
||||
* await contract.directListings.buyFromListing(listingId, quantityDesired);
|
||||
* ```
|
||||
*/
|
||||
get directListings(): MarketplaceV3DirectListings<DirectListingsLogic> {
|
||||
return assertEnabled(this.detectDirectListings(), FEATURE_DIRECT_LISTINGS);
|
||||
}
|
||||
/**
|
||||
* Auctions
|
||||
* @remarks Create and manage auctions in your marketplace.
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Data of the auction you want to create
|
||||
* const auction = {
|
||||
* // address of the contract of the asset you want to auction
|
||||
* assetContractAddress: "0x...",
|
||||
* // token ID of the asset you want to auction
|
||||
* tokenId: "0",
|
||||
* // how many of the asset you want to auction
|
||||
* quantity: 1,
|
||||
* // address of the currency contract that will be used to pay for the auctioned tokens
|
||||
* currencyContractAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* // the minimum bid that will be accepted for the token
|
||||
* minimumBidAmount: "1.5",
|
||||
* // how much people would have to bid to instantly buy the asset
|
||||
* buyoutBidAmount: "10",
|
||||
* // If a bid is made less than these many seconds before expiration, the expiration time is increased by this.
|
||||
* timeBufferInSeconds: "1000",
|
||||
* // A bid must be at least this much bps greater than the current winning bid
|
||||
* bidBufferBps: "100", // 100 bps stands for 1%
|
||||
* // when should the auction open up for bidding
|
||||
* startTimestamp: new Date(Date.now()),
|
||||
* // end time of auction
|
||||
* endTimestamp: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000),
|
||||
* }
|
||||
*
|
||||
* const tx = await contract.englishAuctions.createAuction(auction);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const id = tx.id; // the id of the newly created auction
|
||||
*
|
||||
* // And on the buyers side:
|
||||
* // The auction ID of the asset you want to bid on
|
||||
* const auctionId = 0;
|
||||
* // The total amount you are willing to bid for auctioned tokens
|
||||
* const bidAmount = 1;
|
||||
*
|
||||
* await contract.englishAuctions.makeBid(auctionId, bidAmount);
|
||||
* ```
|
||||
*/
|
||||
get englishAuctions(): MarketplaceV3EnglishAuctions<EnglishAuctionsLogic> {
|
||||
return assertEnabled(
|
||||
this.detectEnglishAuctions(),
|
||||
FEATURE_ENGLISH_AUCTIONS,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offers
|
||||
* @remarks Make and manage offers.
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Data of the offer you want to make
|
||||
* const offer = {
|
||||
* // address of the contract the asset you want to make an offer for
|
||||
* assetContractAddress: "0x...",
|
||||
* // token ID of the asset you want to buy
|
||||
* tokenId: "0",
|
||||
* // how many of the asset you want to buy
|
||||
* quantity: 1,
|
||||
* // address of the currency contract that you offer to pay in
|
||||
* currencyContractAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* // Total price you offer to pay for the mentioned token(s)
|
||||
* totalPrice: "1.5",
|
||||
* // Offer valid until
|
||||
* endTimestamp: new Date(),
|
||||
* }
|
||||
*
|
||||
* const tx = await contract.offers.makeOffer(offer);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const id = tx.id; // the id of the newly created offer
|
||||
*
|
||||
* // And on the seller's side:
|
||||
* // The ID of the offer you want to accept
|
||||
* const offerId = 0;
|
||||
* await contract.offers.acceptOffer(offerId);
|
||||
* ```
|
||||
*/
|
||||
get offers(): MarketplaceV3Offers<OffersLogic> {
|
||||
return assertEnabled(this.detectOffers(), FEATURE_OFFERS);
|
||||
}
|
||||
|
||||
private _chainId;
|
||||
get chainId() {
|
||||
return this._chainId;
|
||||
}
|
||||
|
||||
constructor(
|
||||
network: NetworkOrSignerOrProvider,
|
||||
address: string,
|
||||
storage: ThirdwebStorage,
|
||||
options: SDKOptions = {},
|
||||
abi: Abi,
|
||||
chainId: number,
|
||||
contractWrapper = new ContractWrapper<MarketplaceV3Contract>(
|
||||
network,
|
||||
address,
|
||||
abi,
|
||||
options,
|
||||
),
|
||||
) {
|
||||
this._chainId = chainId;
|
||||
this.abi = abi;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
this.metadata = new ContractMetadata(
|
||||
this.contractWrapper,
|
||||
MarketplaceContractSchema,
|
||||
this.storage,
|
||||
);
|
||||
this.roles = new ContractRoles(
|
||||
this.contractWrapper,
|
||||
MarketplaceV3.contractRoles,
|
||||
);
|
||||
this.encoder = new ContractEncoder(this.contractWrapper);
|
||||
this.estimator = new GasCostEstimator(this.contractWrapper);
|
||||
this.events = new ContractEvents(this.contractWrapper);
|
||||
this.platformFees = new ContractPlatformFee(this.contractWrapper);
|
||||
this.interceptor = new ContractInterceptor(this.contractWrapper);
|
||||
}
|
||||
|
||||
onNetworkUpdated(network: NetworkOrSignerOrProvider) {
|
||||
this.contractWrapper.updateSignerOrProvider(network);
|
||||
}
|
||||
|
||||
getAddress(): string {
|
||||
return this.contractWrapper.readContract.address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public async call(
|
||||
functionName: string,
|
||||
...args: unknown[] | [...unknown[], CallOverrides]
|
||||
): Promise<any> {
|
||||
return this.contractWrapper.call(functionName, ...args);
|
||||
}
|
||||
|
||||
/** ********************
|
||||
* FEATURE DETECTION
|
||||
* ********************/
|
||||
|
||||
private detectDirectListings() {
|
||||
if (
|
||||
detectContractFeature<DirectListingsLogic>(
|
||||
this.contractWrapper,
|
||||
"DirectListings",
|
||||
)
|
||||
) {
|
||||
return new MarketplaceV3DirectListings(
|
||||
this.contractWrapper as unknown as ContractWrapper<DirectListingsLogic>,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private detectEnglishAuctions() {
|
||||
if (
|
||||
detectContractFeature<EnglishAuctionsLogic>(
|
||||
this.contractWrapper,
|
||||
"EnglishAuctions",
|
||||
)
|
||||
) {
|
||||
return new MarketplaceV3EnglishAuctions(
|
||||
this
|
||||
.contractWrapper as unknown as ContractWrapper<EnglishAuctionsLogic>,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private detectOffers() {
|
||||
if (detectContractFeature<OffersLogic>(this.contractWrapper, "Offers")) {
|
||||
return new MarketplaceV3Offers(
|
||||
this.contractWrapper as unknown as ContractWrapper<OffersLogic>,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -229,6 +229,19 @@ export class NFTCollection extends StandardErc721<TokenERC721> {
|
||||
return this.erc721.mintTo(walletAddress, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a mint transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a mint transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param receiver - Address you want to send the token to
|
||||
* @param metadata - The metadata of the NFT you want to mint
|
||||
*/
|
||||
public async getMintTransaction(
|
||||
receiver: string,
|
||||
metadata: NFTMetadataOrUri,
|
||||
) {
|
||||
return this.erc721.getMintTransaction(receiver, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Many unique NFTs
|
||||
*
|
||||
|
||||
@@ -186,6 +186,16 @@ export class Token extends StandardErc20<TokenERC20> {
|
||||
return this.erc20.mintTo(to, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a mint transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a mint transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param receiver - Address you want to send the token to
|
||||
* @param amount - The amount of tokens you want to mint
|
||||
*/
|
||||
public async getMintTransaction(to: string, amount: Amount) {
|
||||
return this.erc20.getMintTransaction(to, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Tokens To Many Wallets
|
||||
*
|
||||
|
||||
@@ -104,7 +104,10 @@ export class TransactionTask {
|
||||
* @returns the gas limit in gas units
|
||||
*/
|
||||
public async estimateGasLimit(): Promise<BigNumber> {
|
||||
return await this.estimator.gasLimitOf(this.functionName, this.args);
|
||||
return await this.estimator.gasLimitOf(this.functionName, [
|
||||
...this.args,
|
||||
this.overrides || {},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +115,10 @@ export class TransactionTask {
|
||||
* @returns the gas cost in ether
|
||||
*/
|
||||
public async estimateGasCostInEther(): Promise<string> {
|
||||
return await this.estimator.gasCostOf(this.functionName, this.args);
|
||||
return await this.estimator.gasCostOf(this.functionName, [
|
||||
...this.args,
|
||||
this.overrides || {},
|
||||
]);
|
||||
}
|
||||
|
||||
// ////////////// Actions ////////////////
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
EditionInitializer,
|
||||
getContractName,
|
||||
MarketplaceInitializer,
|
||||
MarketplaceV3Initializer,
|
||||
MultiwrapInitializer,
|
||||
NFTCollectionInitializer,
|
||||
NFTDropInitializer,
|
||||
@@ -27,6 +28,7 @@ import { SDKOptions } from "../../schema/sdk-options";
|
||||
import { DeployEvent, DeployEvents } from "../../types";
|
||||
import {
|
||||
MarketplaceContractDeployMetadata,
|
||||
MarketplaceV3ContractDeployMetadata,
|
||||
MultiwrapContractDeployMetadata,
|
||||
NFTContractDeployMetadata,
|
||||
SplitContractDeployMetadata,
|
||||
@@ -309,6 +311,30 @@ export class ContractDeployer extends RPCConnectionHandler {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploys a new Marketplace-V3 contract
|
||||
*
|
||||
* @remarks Deploys a Marketplace-V3 contract and returns the address of the deployed contract
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contractAddress = await sdk.deployer.deployMarketplaceV3({
|
||||
* name: "My Marketplace",
|
||||
* primary_sale_recipient: "your-address",
|
||||
* });
|
||||
* ```
|
||||
* @param metadata - the contract metadata
|
||||
* @returns the address of the deployed contract
|
||||
*/
|
||||
public async deployMarketplaceV3(
|
||||
metadata: MarketplaceV3ContractDeployMetadata,
|
||||
): Promise<string> {
|
||||
return await this.deployBuiltInContract(
|
||||
MarketplaceV3Initializer.contractType,
|
||||
metadata,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploys a new Pack contract
|
||||
*
|
||||
@@ -463,7 +489,6 @@ export class ContractDeployer extends RPCConnectionHandler {
|
||||
parsedMetadata,
|
||||
contractURI,
|
||||
);
|
||||
|
||||
return this.deployReleasedContract(
|
||||
// 0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024 === deployer.thirdweb.eth
|
||||
"0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024",
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { convertToTWError, extractFunctionsFromAbi } from "../../common";
|
||||
import {
|
||||
TransactionError,
|
||||
extractFunctionsFromAbi,
|
||||
fetchContractMetadataFromAddress,
|
||||
fetchSourceFilesFromMetadata,
|
||||
parseRevertReason,
|
||||
} from "../../common";
|
||||
import {
|
||||
BiconomyForwarderAbi,
|
||||
ChainAwareForwardRequest,
|
||||
@@ -13,7 +19,7 @@ import { CONTRACT_ADDRESSES, ChainId } from "../../constants";
|
||||
import { getContractAddressByChainId } from "../../constants/addresses";
|
||||
import { EventType } from "../../constants/events";
|
||||
import { CallOverrideSchema } from "../../schema";
|
||||
import { AbiSchema } from "../../schema/contracts/custom";
|
||||
import { AbiSchema, ContractSource } from "../../schema/contracts/custom";
|
||||
import { SDKOptions } from "../../schema/sdk-options";
|
||||
import {
|
||||
ForwardRequestMessage,
|
||||
@@ -23,6 +29,7 @@ import {
|
||||
} from "../types";
|
||||
import { RPCConnectionHandler } from "./rpc-connection-handler";
|
||||
import ForwarderABI from "@thirdweb-dev/contracts-js/dist/abis/Forwarder.json";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import fetch from "cross-fetch";
|
||||
import {
|
||||
BaseContract,
|
||||
@@ -35,6 +42,7 @@ import {
|
||||
ethers,
|
||||
providers,
|
||||
} from "ethers";
|
||||
import { ConnectionInfo } from "ethers/lib/utils.js";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
@@ -43,6 +51,9 @@ import invariant from "tiny-invariant";
|
||||
export class ContractWrapper<
|
||||
TContract extends BaseContract,
|
||||
> extends RPCConnectionHandler {
|
||||
// TOOO: In another PR, make this storage private, and have extending classes pass
|
||||
// down storage to be stored in contract wrapper.
|
||||
#storage: ThirdwebStorage;
|
||||
private isValidContract = false;
|
||||
private customOverrides: () => CallOverrides = () => ({});
|
||||
/**
|
||||
@@ -70,6 +81,7 @@ export class ContractWrapper<
|
||||
this.readContract = this.writeContract.connect(
|
||||
this.getProvider(),
|
||||
) as TContract;
|
||||
this.#storage = new ThirdwebStorage();
|
||||
}
|
||||
|
||||
public override updateSignerOrProvider(
|
||||
@@ -375,7 +387,26 @@ export class ContractWrapper<
|
||||
callOverrides,
|
||||
);
|
||||
this.emitTransactionEvent("submitted", tx.hash);
|
||||
const receipt = tx.wait();
|
||||
|
||||
// tx.wait() can fail so we need to wrap it with a catch
|
||||
let receipt;
|
||||
try {
|
||||
receipt = await tx.wait();
|
||||
} catch (err) {
|
||||
try {
|
||||
// If tx.wait() fails, it just gives us a generic "transaction failed"
|
||||
// error. So instead, we need to call static to get an informative error message
|
||||
await this.writeContract.callStatic[fn as string](
|
||||
...args,
|
||||
...(callOverrides.value ? [{ value: callOverrides.value }] : []),
|
||||
);
|
||||
} catch (staticErr: any) {
|
||||
throw await this.formatError(staticErr, fn, args, callOverrides);
|
||||
}
|
||||
|
||||
throw await this.formatError(err, fn, args, callOverrides);
|
||||
}
|
||||
|
||||
this.emitTransactionEvent("completed", tx.hash);
|
||||
return receipt;
|
||||
}
|
||||
@@ -395,22 +426,133 @@ export class ContractWrapper<
|
||||
if (!func) {
|
||||
throw new Error(`invalid function: "${fn.toString()}"`);
|
||||
}
|
||||
|
||||
// First, if no gasLimit is passed, call estimate gas ourselves
|
||||
if (!callOverrides.gasLimit) {
|
||||
try {
|
||||
callOverrides.gasLimit = await this.writeContract.estimateGas[
|
||||
fn as string
|
||||
](...args, callOverrides);
|
||||
} catch (e) {
|
||||
// If gas estimation fails, we'll call static to get a better error message
|
||||
try {
|
||||
await this.writeContract.callStatic[fn as string](
|
||||
...args,
|
||||
...(callOverrides.value ? [{ value: callOverrides.value }] : []),
|
||||
);
|
||||
} catch (err: any) {
|
||||
throw await this.formatError(err, fn, args, callOverrides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now there should be no gas estimate errors
|
||||
try {
|
||||
return await func(...args, callOverrides);
|
||||
} catch (e) {
|
||||
const network = await this.getProvider().getNetwork();
|
||||
const signerAddress = await this.getSignerAddress();
|
||||
const contractAddress = await this.readContract.address;
|
||||
throw await convertToTWError(
|
||||
e,
|
||||
network,
|
||||
signerAddress,
|
||||
contractAddress,
|
||||
this.readContract.interface,
|
||||
);
|
||||
} catch (err) {
|
||||
const from = await (callOverrides.from || this.getSignerAddress());
|
||||
const value = await (callOverrides.value ? callOverrides.value : 0);
|
||||
const balance = await this.getProvider().getBalance(from);
|
||||
|
||||
if (balance.eq(0) || (value && balance.lt(value))) {
|
||||
throw await this.formatError(
|
||||
new Error(
|
||||
"You have insufficient funds in your account to execute this transaction.",
|
||||
),
|
||||
fn,
|
||||
args,
|
||||
callOverrides,
|
||||
);
|
||||
}
|
||||
|
||||
throw await this.formatError(err, fn, args, callOverrides);
|
||||
}
|
||||
}
|
||||
|
||||
private async formatError(
|
||||
error: any,
|
||||
fn: keyof TContract["functions"],
|
||||
args: any[],
|
||||
callOverrides: CallOverrides,
|
||||
) {
|
||||
const provider = this.getProvider() as ethers.providers.Provider & {
|
||||
connection?: ConnectionInfo;
|
||||
};
|
||||
|
||||
// Get metadata for transaction to populate into error
|
||||
const network = await provider.getNetwork();
|
||||
const from = await (callOverrides.from || this.getSignerAddress());
|
||||
const to = this.readContract.address;
|
||||
const data = this.readContract.interface.encodeFunctionData(
|
||||
fn as string,
|
||||
args,
|
||||
);
|
||||
const value = BigNumber.from(callOverrides.value || 0);
|
||||
const rpcUrl = provider.connection?.url;
|
||||
|
||||
// Render function signature with arguments filled in
|
||||
const functionSignature = this.readContract.interface.getFunction(
|
||||
fn as string,
|
||||
);
|
||||
const methodArgs = args.map((arg) => {
|
||||
if (JSON.stringify(arg).length <= 80) {
|
||||
return JSON.stringify(arg);
|
||||
}
|
||||
return JSON.stringify(arg, undefined, 2);
|
||||
});
|
||||
const joinedArgs =
|
||||
methodArgs.join(", ").length <= 80
|
||||
? methodArgs.join(", ")
|
||||
: "\n" +
|
||||
methodArgs
|
||||
.map((arg) => " " + arg.split("\n").join("\n "))
|
||||
.join(",\n") +
|
||||
"\n";
|
||||
const method = `${functionSignature.name}(${joinedArgs})`;
|
||||
const hash =
|
||||
error.transactionHash ||
|
||||
error.transaction?.hash ||
|
||||
error.receipt?.transactionHash;
|
||||
|
||||
// Parse the revert reason from the error
|
||||
const reason = parseRevertReason(error);
|
||||
|
||||
// Get contract sources for stack trace
|
||||
let sources: ContractSource[] | undefined = undefined;
|
||||
let contractName: string | undefined = undefined;
|
||||
try {
|
||||
const metadata = await fetchContractMetadataFromAddress(
|
||||
this.readContract.address,
|
||||
this.getProvider(),
|
||||
this.#storage,
|
||||
);
|
||||
|
||||
if (metadata.name) {
|
||||
contractName = metadata.name;
|
||||
}
|
||||
|
||||
if (metadata.metadata.sources) {
|
||||
sources = await fetchSourceFilesFromMetadata(metadata, this.#storage);
|
||||
}
|
||||
} catch (err) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
return new TransactionError({
|
||||
reason,
|
||||
from,
|
||||
to,
|
||||
method,
|
||||
data,
|
||||
network,
|
||||
rpcUrl,
|
||||
value,
|
||||
hash,
|
||||
contractName,
|
||||
sources,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ import { FEATURE_EDITION_MINTABLE } from "../../constants/erc1155-features";
|
||||
import { EditionMetadataOrUri } from "../../schema";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { TransactionTask } from "./TransactionTask";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc1155 } from "./erc-1155";
|
||||
import { Erc1155BatchMintable } from "./erc-1155-batch-mintable";
|
||||
@@ -77,16 +78,8 @@ export class Erc1155Mintable implements DetectableFeature {
|
||||
to: string,
|
||||
metadataWithSupply: EditionMetadataOrUri,
|
||||
): Promise<TransactionResultWithId<NFT>> {
|
||||
const uri = await uploadOrExtractURI(
|
||||
metadataWithSupply.metadata,
|
||||
this.storage,
|
||||
);
|
||||
const receipt = await this.contractWrapper.sendTransaction("mintTo", [
|
||||
to,
|
||||
ethers.constants.MaxUint256,
|
||||
uri,
|
||||
metadataWithSupply.supply,
|
||||
]);
|
||||
const tx = await this.getMintTransaction(to, metadataWithSupply);
|
||||
const { receipt } = await tx.execute();
|
||||
const event = this.contractWrapper.parseLogs<TransferSingleEvent>(
|
||||
"TransferSingle",
|
||||
receipt?.logs,
|
||||
@@ -102,6 +95,21 @@ export class Erc1155Mintable implements DetectableFeature {
|
||||
};
|
||||
}
|
||||
|
||||
public async getMintTransaction(
|
||||
to: string,
|
||||
metadataWithSupply: EditionMetadataOrUri,
|
||||
): Promise<TransactionTask> {
|
||||
const uri = await uploadOrExtractURI(
|
||||
metadataWithSupply.metadata,
|
||||
this.storage,
|
||||
);
|
||||
return TransactionTask.make({
|
||||
contractWrapper: this.contractWrapper,
|
||||
functionName: "mintTo",
|
||||
args: [to, ethers.constants.MaxUint256, uri, metadataWithSupply.supply],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the supply of an existing NFT and mint it to a given wallet address
|
||||
*
|
||||
|
||||
@@ -473,6 +473,22 @@ export class Erc1155<
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a mint transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a mint transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param receiver - Address you want to send the token to
|
||||
* @param metadataWithSupply - The metadata of the NFT you want to mint
|
||||
*/
|
||||
public async getMintTransaction(
|
||||
receiver: string,
|
||||
metadataWithSupply: EditionMetadataOrUri,
|
||||
) {
|
||||
return assertEnabled(
|
||||
this.mintable,
|
||||
FEATURE_EDITION_MINTABLE,
|
||||
).getMintTransaction(receiver, metadataWithSupply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the supply of an existing NFT
|
||||
* @remarks Increase the supply of an existing NFT and mint it to the connected wallet address
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FEATURE_TOKEN_MINTABLE } from "../../constants/erc20-features";
|
||||
import { Amount } from "../../types";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResult } from "../types";
|
||||
import { TransactionTask } from "./TransactionTask";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc20 } from "./erc-20";
|
||||
import { Erc20BatchMintable } from "./erc-20-batch-mintable";
|
||||
@@ -47,12 +48,19 @@ export class Erc20Mintable implements DetectableFeature {
|
||||
* ```
|
||||
*/
|
||||
public async to(to: string, amount: Amount): Promise<TransactionResult> {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction("mintTo", [
|
||||
to,
|
||||
await this.erc20.normalizeAmount(amount),
|
||||
]),
|
||||
};
|
||||
const tx = await this.getMintTransaction(to, amount);
|
||||
return tx.execute();
|
||||
}
|
||||
|
||||
public async getMintTransaction(
|
||||
to: string,
|
||||
amount: Amount,
|
||||
): Promise<TransactionTask> {
|
||||
return TransactionTask.make({
|
||||
contractWrapper: this.contractWrapper,
|
||||
functionName: "mintTo",
|
||||
args: [to, await this.erc20.normalizeAmount(amount)],
|
||||
});
|
||||
}
|
||||
|
||||
private detectErc20BatchMintable() {
|
||||
|
||||
@@ -376,6 +376,19 @@ export class Erc20<
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a mint transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a mint transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param receiver - Address you want to send the token to
|
||||
* @param amount - The amount of tokens you want to mint
|
||||
*/
|
||||
public async getMintTransaction(receiver: string, amount: Amount) {
|
||||
return assertEnabled(
|
||||
this.mintable,
|
||||
FEATURE_TOKEN_MINTABLE,
|
||||
).getMintTransaction(receiver, amount);
|
||||
}
|
||||
|
||||
////// ERC20 BatchMintable Extension //////
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ import { uploadOrExtractURI } from "../../common/nft";
|
||||
import { FEATURE_NFT_MINTABLE } from "../../constants/erc721-features";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { TransactionTask } from "./TransactionTask";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import { Erc721BatchMintable } from "./erc-721-batch-mintable";
|
||||
@@ -67,11 +68,8 @@ export class Erc721Mintable implements DetectableFeature {
|
||||
to: string,
|
||||
metadata: NFTMetadataOrUri,
|
||||
): Promise<TransactionResultWithId<NFT>> {
|
||||
const uri = await uploadOrExtractURI(metadata, this.storage);
|
||||
const receipt = await this.contractWrapper.sendTransaction("mintTo", [
|
||||
to,
|
||||
uri,
|
||||
]);
|
||||
const tx = await this.getMintTransaction(to, metadata);
|
||||
const { receipt } = await tx.execute();
|
||||
const event = this.contractWrapper.parseLogs<TransferEvent>(
|
||||
"Transfer",
|
||||
receipt?.logs,
|
||||
@@ -87,6 +85,18 @@ export class Erc721Mintable implements DetectableFeature {
|
||||
};
|
||||
}
|
||||
|
||||
public async getMintTransaction(
|
||||
to: string,
|
||||
metadata: NFTMetadataOrUri,
|
||||
): Promise<TransactionTask> {
|
||||
const uri = await uploadOrExtractURI(metadata, this.storage);
|
||||
return TransactionTask.make({
|
||||
contractWrapper: this.contractWrapper,
|
||||
functionName: "mintTo",
|
||||
args: [to, uri],
|
||||
});
|
||||
}
|
||||
|
||||
private detectErc721BatchMintable(): Erc721BatchMintable | undefined {
|
||||
if (
|
||||
detectContractFeature<IMintableERC721 & IMulticall>(
|
||||
|
||||
@@ -409,6 +409,22 @@ export class Erc721<
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a mint transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a mint transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param receiver - Address you want to send the token to
|
||||
* @param metadata - The metadata of the NFT you want to mint
|
||||
*/
|
||||
public async getMintTransaction(
|
||||
receiver: string,
|
||||
metadata: NFTMetadataOrUri,
|
||||
) {
|
||||
return assertEnabled(
|
||||
this.mintable,
|
||||
FEATURE_NFT_MINTABLE,
|
||||
).getMintTransaction(receiver, metadata);
|
||||
}
|
||||
|
||||
////// ERC721 Batch Mintable Extension //////
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
EditionInitializer,
|
||||
getContractName,
|
||||
MarketplaceInitializer,
|
||||
MarketplaceV3Initializer,
|
||||
MultiwrapInitializer,
|
||||
NFTCollectionInitializer,
|
||||
NFTDropInitializer,
|
||||
@@ -59,6 +60,7 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
|
||||
[VoteInitializer.contractType]: 1,
|
||||
[SplitInitializer.contractType]: 1,
|
||||
[MarketplaceInitializer.contractType]: 2,
|
||||
[MarketplaceV3Initializer.contractType]: 1,
|
||||
[PackInitializer.contractType]: 2,
|
||||
};
|
||||
|
||||
@@ -296,6 +298,16 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
|
||||
marketplaceMetadata.platform_fee_recipient,
|
||||
marketplaceMetadata.platform_fee_basis_points,
|
||||
];
|
||||
case MarketplaceV3Initializer.contractType:
|
||||
const marketplaceV3Metadata =
|
||||
MarketplaceV3Initializer.schema.deploy.parse(metadata);
|
||||
return [
|
||||
await this.getSignerAddress(),
|
||||
contractURI,
|
||||
trustedForwarders,
|
||||
marketplaceV3Metadata.platform_fee_recipient,
|
||||
marketplaceV3Metadata.platform_fee_basis_points,
|
||||
];
|
||||
case PackInitializer.contractType:
|
||||
const packsMetadata = PackInitializer.schema.deploy.parse(metadata);
|
||||
return [
|
||||
|
||||
@@ -0,0 +1,873 @@
|
||||
import {
|
||||
cleanCurrencyAddress,
|
||||
fetchCurrencyValue,
|
||||
normalizePriceValue,
|
||||
setErc20Allowance,
|
||||
} from "../../common/currency";
|
||||
import {
|
||||
getAllInBatches,
|
||||
handleTokenApproval,
|
||||
isTokenApprovedForTransfer,
|
||||
} from "../../common/marketplace";
|
||||
import { fetchTokenMetadataForContract } from "../../common/nft";
|
||||
import {
|
||||
InterfaceId_IERC1155,
|
||||
InterfaceId_IERC721,
|
||||
} from "../../constants/contract";
|
||||
import { FEATURE_DIRECT_LISTINGS } from "../../constants/thirdweb-features";
|
||||
import { Status } from "../../enums";
|
||||
import {
|
||||
DirectListingInputParams,
|
||||
DirectListingInputParamsSchema,
|
||||
} from "../../schema/marketplacev3/direct-listings";
|
||||
import { DirectListingV3, MarketplaceFilter } from "../../types";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResult, TransactionResultWithId } from "../types";
|
||||
import { ContractEncoder } from "./contract-encoder";
|
||||
import { ContractEvents } from "./contract-events";
|
||||
import { ContractInterceptor } from "./contract-interceptor";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import type {
|
||||
IERC1155,
|
||||
IERC165,
|
||||
IERC721,
|
||||
IDirectListings,
|
||||
DirectListingsLogic,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import ERC165Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC165.json";
|
||||
import ERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC721.json";
|
||||
import ERC1155Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC1155.json";
|
||||
import {
|
||||
NewListingEvent,
|
||||
UpdatedListingEvent,
|
||||
} from "@thirdweb-dev/contracts-js/dist/declarations/src/DirectListingsLogic";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, Contract } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* Handles direct listings
|
||||
* @public
|
||||
*/
|
||||
export class MarketplaceV3DirectListings<TContract extends DirectListingsLogic>
|
||||
implements DetectableFeature
|
||||
{
|
||||
featureName = FEATURE_DIRECT_LISTINGS.name;
|
||||
private contractWrapper: ContractWrapper<DirectListingsLogic>;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
// utilities
|
||||
public events: ContractEvents<DirectListingsLogic>;
|
||||
public interceptor: ContractInterceptor<DirectListingsLogic>;
|
||||
public encoder: ContractEncoder<DirectListingsLogic>;
|
||||
|
||||
constructor(
|
||||
contractWrapper: ContractWrapper<TContract>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
|
||||
this.events = new ContractEvents<DirectListingsLogic>(this.contractWrapper);
|
||||
this.encoder = new ContractEncoder<DirectListingsLogic>(
|
||||
this.contractWrapper,
|
||||
);
|
||||
this.interceptor = new ContractInterceptor<DirectListingsLogic>(
|
||||
this.contractWrapper,
|
||||
);
|
||||
}
|
||||
|
||||
getAddress(): string {
|
||||
return this.contractWrapper.readContract.address;
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* READ FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Get the total number of direct listings.
|
||||
* @returns Returns the total number of direct listings created.
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const totalListings = await contract.directListings.getTotalCount();
|
||||
* ```
|
||||
*/
|
||||
public async getTotalCount(): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.totalListings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all direct listings.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const listings = await contract.directListings.getAll();
|
||||
* const priceOfFirstListing = listings[0].price;
|
||||
* ```
|
||||
*
|
||||
* @param filter - optional filter parameters
|
||||
* @returns the Direct listing object array
|
||||
*/
|
||||
public async getAll(filter?: MarketplaceFilter): Promise<DirectListingV3[]> {
|
||||
const totalListings = await this.getTotalCount();
|
||||
|
||||
let start = BigNumber.from(filter?.start || 0).toNumber();
|
||||
let end = totalListings.toNumber();
|
||||
|
||||
if (end === 0) {
|
||||
throw new Error(`No listings exist on the contract.`);
|
||||
}
|
||||
|
||||
let rawListings: IDirectListings.ListingStructOutput[] = [];
|
||||
let batches = await getAllInBatches(
|
||||
start,
|
||||
end,
|
||||
this.contractWrapper.readContract.getAllListings,
|
||||
);
|
||||
rawListings = batches.flat();
|
||||
|
||||
const filteredListings = this.applyFilter(rawListings, filter);
|
||||
|
||||
return await Promise.all(
|
||||
filteredListings.map((listing) => this.mapListing(listing)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all valid direct listings.
|
||||
*
|
||||
* A valid listing is where the listing is active,
|
||||
* and the creator still owns & has approved Marketplace to transfer the listed NFTs.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const listings = await contract.directListings.getAllValid();
|
||||
* const priceOfFirstListing = listings[0].price;
|
||||
* ```
|
||||
*
|
||||
* @param filter - optional filter parameters
|
||||
* @returns the Direct listing object array
|
||||
*/
|
||||
public async getAllValid(
|
||||
filter?: MarketplaceFilter,
|
||||
): Promise<DirectListingV3[]> {
|
||||
const totalListings = await this.getTotalCount();
|
||||
|
||||
let start = BigNumber.from(filter?.start || 0).toNumber();
|
||||
let end = totalListings.toNumber();
|
||||
|
||||
if (end === 0) {
|
||||
throw new Error(`No listings exist on the contract.`);
|
||||
}
|
||||
|
||||
let rawListings: IDirectListings.ListingStructOutput[] = [];
|
||||
let batches = await getAllInBatches(
|
||||
start,
|
||||
end,
|
||||
this.contractWrapper.readContract.getAllValidListings,
|
||||
);
|
||||
rawListings = batches.flat();
|
||||
|
||||
const filteredListings = this.applyFilter(rawListings, filter);
|
||||
|
||||
return await Promise.all(
|
||||
filteredListings.map((listing) => this.mapListing(listing)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a direct listing by id.
|
||||
*
|
||||
* @param listingId - the listing id
|
||||
* @returns the Direct listing object
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const listingId = 0;
|
||||
* const listing = await contract.directListings.getListing(listingId);
|
||||
* ```
|
||||
*/
|
||||
public async getListing(listingId: BigNumberish): Promise<DirectListingV3> {
|
||||
const listing = await this.contractWrapper.readContract.getListing(
|
||||
listingId,
|
||||
);
|
||||
|
||||
return await this.mapListing(listing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a buyer is approved for a reserved listing.
|
||||
*
|
||||
* @param listingId - the listing id
|
||||
* @param buyer - buyer address
|
||||
*/
|
||||
public async isBuyerApprovedForListing(
|
||||
listingId: BigNumberish,
|
||||
buyer: string,
|
||||
): Promise<boolean> {
|
||||
const listing = await this.validateListing(BigNumber.from(listingId));
|
||||
|
||||
if (!listing.isReservedListing) {
|
||||
throw new Error(`Listing ${listingId} is not a reserved listing.`);
|
||||
}
|
||||
|
||||
return await this.contractWrapper.readContract.isBuyerApprovedForListing(
|
||||
listingId,
|
||||
buyer,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a currency is approved for a listing.
|
||||
*
|
||||
* @param listingId - the listing id
|
||||
* @param currency - currency address
|
||||
*/
|
||||
public async isCurrencyApprovedForListing(
|
||||
listingId: BigNumberish,
|
||||
currency: string,
|
||||
): Promise<boolean> {
|
||||
await this.validateListing(BigNumber.from(listingId));
|
||||
|
||||
return await this.contractWrapper.readContract.isCurrencyApprovedForListing(
|
||||
listingId,
|
||||
currency,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check price per token for an approved currency.
|
||||
*
|
||||
* @param listingId - the listing id
|
||||
* @param currencyContractAddress - currency contract address
|
||||
*/
|
||||
public async currencyPriceForListing(
|
||||
listingId: BigNumberish,
|
||||
currencyContractAddress: string,
|
||||
): Promise<BigNumberish> {
|
||||
const listing = await this.validateListing(BigNumber.from(listingId));
|
||||
|
||||
if (currencyContractAddress === listing.currencyContractAddress) {
|
||||
return listing.pricePerToken;
|
||||
}
|
||||
|
||||
const isApprovedCurrency = await this.isCurrencyApprovedForListing(
|
||||
listingId,
|
||||
currencyContractAddress,
|
||||
);
|
||||
|
||||
if (!isApprovedCurrency) {
|
||||
throw new Error(
|
||||
`Currency ${currencyContractAddress} is not approved for Listing ${listingId}.`,
|
||||
);
|
||||
}
|
||||
|
||||
return await this.contractWrapper.readContract.currencyPriceForListing(
|
||||
listingId,
|
||||
currencyContractAddress,
|
||||
);
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* WRITE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Create Direct Listing
|
||||
*
|
||||
* @remarks Create a new listing on the marketplace where people can buy an asset directly.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Data of the listing you want to create
|
||||
* const listing = {
|
||||
* // address of the contract the asset you want to list is on
|
||||
* assetContractAddress: "0x...",
|
||||
* // token ID of the asset you want to list
|
||||
* tokenId: "0",
|
||||
* // how many of the asset you want to list
|
||||
* quantity: 1,
|
||||
* // address of the currency contract that will be used to pay for the listing
|
||||
* currencyContractAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* // The price to pay per unit of NFTs listed.
|
||||
* pricePerToken: 1.5,
|
||||
* // when should the listing open up for offers
|
||||
* startTimestamp: new Date(Date.now()),
|
||||
* // how long the listing will be open for
|
||||
* endTimestamp: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000),
|
||||
* // Whether the listing is reserved for a specific set of buyers.
|
||||
* isReservedListing: false
|
||||
* }
|
||||
*
|
||||
* const tx = await contract.directListings.createListing(listing);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const id = tx.id; // the id of the newly created listing
|
||||
* ```
|
||||
*/
|
||||
public async createListing(
|
||||
listing: DirectListingInputParams,
|
||||
): Promise<TransactionResultWithId> {
|
||||
const parsedListing = DirectListingInputParamsSchema.parse(listing);
|
||||
|
||||
await handleTokenApproval(
|
||||
this.contractWrapper,
|
||||
this.getAddress(),
|
||||
parsedListing.assetContractAddress,
|
||||
parsedListing.tokenId,
|
||||
await this.contractWrapper.getSignerAddress(),
|
||||
);
|
||||
|
||||
const normalizedPricePerToken = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
parsedListing.pricePerToken,
|
||||
parsedListing.currencyContractAddress,
|
||||
);
|
||||
|
||||
const block = await this.contractWrapper.getProvider().getBlock("latest");
|
||||
const blockTime = block.timestamp;
|
||||
if (parsedListing.startTimestamp.lt(blockTime)) {
|
||||
parsedListing.startTimestamp = BigNumber.from(blockTime);
|
||||
}
|
||||
|
||||
const receipt = await this.contractWrapper.sendTransaction(
|
||||
"createListing",
|
||||
[
|
||||
{
|
||||
assetContract: parsedListing.assetContractAddress,
|
||||
tokenId: parsedListing.tokenId,
|
||||
quantity: parsedListing.quantity,
|
||||
currency: cleanCurrencyAddress(parsedListing.currencyContractAddress),
|
||||
pricePerToken: normalizedPricePerToken,
|
||||
startTimestamp: parsedListing.startTimestamp,
|
||||
endTimestamp: parsedListing.endTimestamp,
|
||||
reserved: parsedListing.isReservedListing,
|
||||
} as IDirectListings.ListingParametersStruct,
|
||||
],
|
||||
{
|
||||
// Higher gas limit for create listing
|
||||
gasLimit: 500000,
|
||||
},
|
||||
);
|
||||
|
||||
const event = this.contractWrapper.parseLogs<NewListingEvent>(
|
||||
"NewListing",
|
||||
receipt?.logs,
|
||||
);
|
||||
return {
|
||||
id: event[0].args.listingId,
|
||||
receipt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a Direct listing with new metadata.
|
||||
*
|
||||
* Note: cannot update a listing with a new quantity of 0. Use `cancelDirectListing` to remove a listing instead.
|
||||
*
|
||||
* @param listing - the new listing information
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Data of the listing you want to update
|
||||
*
|
||||
* const listingId = 0; // ID of the listing you want to update
|
||||
*
|
||||
* const listing = {
|
||||
* // address of the contract the asset you want to list is on
|
||||
* assetContractAddress: "0x...", // should be same as original listing
|
||||
* // token ID of the asset you want to list
|
||||
* tokenId: "0", // should be same as original listing
|
||||
* // how many of the asset you want to list
|
||||
* quantity: 1,
|
||||
* // address of the currency contract that will be used to pay for the listing
|
||||
* currencyContractAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* // The price to pay per unit of NFTs listed.
|
||||
* pricePerToken: 1.5,
|
||||
* // when should the listing open up for offers
|
||||
* startTimestamp: new Date(Date.now()), // can't change this if listing already active
|
||||
* // how long the listing will be open for
|
||||
* endTimestamp: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000),
|
||||
* // Whether the listing is reserved for a specific set of buyers.
|
||||
* isReservedListing: false
|
||||
* }
|
||||
*
|
||||
* const tx = await contract.directListings.updateListing(listingId, listing);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const id = tx.id; // the id of the newly created listing
|
||||
* ```
|
||||
*/
|
||||
public async updateListing(
|
||||
listingId: BigNumberish,
|
||||
listing: DirectListingInputParams,
|
||||
): Promise<TransactionResultWithId> {
|
||||
const parsedListing = DirectListingInputParamsSchema.parse(listing);
|
||||
|
||||
await handleTokenApproval(
|
||||
this.contractWrapper,
|
||||
this.getAddress(),
|
||||
parsedListing.assetContractAddress,
|
||||
parsedListing.tokenId,
|
||||
await this.contractWrapper.getSignerAddress(),
|
||||
);
|
||||
|
||||
const normalizedPricePerToken = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
parsedListing.pricePerToken,
|
||||
parsedListing.currencyContractAddress,
|
||||
);
|
||||
|
||||
const receipt = await this.contractWrapper.sendTransaction(
|
||||
"updateListing",
|
||||
[
|
||||
listingId,
|
||||
{
|
||||
assetContract: parsedListing.assetContractAddress,
|
||||
tokenId: parsedListing.tokenId,
|
||||
quantity: parsedListing.quantity,
|
||||
currency: cleanCurrencyAddress(parsedListing.currencyContractAddress),
|
||||
pricePerToken: normalizedPricePerToken,
|
||||
startTimestamp: parsedListing.startTimestamp,
|
||||
endTimestamp: parsedListing.endTimestamp,
|
||||
reserved: parsedListing.isReservedListing,
|
||||
} as IDirectListings.ListingParametersStruct,
|
||||
],
|
||||
{
|
||||
// Higher gas limit for create listing
|
||||
gasLimit: 500000,
|
||||
},
|
||||
);
|
||||
|
||||
const event = this.contractWrapper.parseLogs<UpdatedListingEvent>(
|
||||
"UpdatedListing",
|
||||
receipt?.logs,
|
||||
);
|
||||
return {
|
||||
id: event[0].args.listingId,
|
||||
receipt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel Direct Listing
|
||||
*
|
||||
* @remarks Cancel a direct listing on the marketplace
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The listing ID of the direct listing you want to cancel
|
||||
* const listingId = 0;
|
||||
*
|
||||
* await contract.directListings.cancelListing(listingId);
|
||||
* ```
|
||||
*/
|
||||
public async cancelListing(
|
||||
listingId: BigNumberish,
|
||||
): Promise<TransactionResult> {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction("cancelListing", [
|
||||
listingId,
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Buy from a Listing
|
||||
*
|
||||
* @remarks Buy from a specific direct listing from the marketplace.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The ID of the listing you want to buy from
|
||||
* const listingId = 0;
|
||||
* // Quantity of the asset you want to buy
|
||||
* const quantityDesired = 1;
|
||||
*
|
||||
* await contract.directListings.buyFromListing(listingId, quantityDesired);
|
||||
* ```
|
||||
*
|
||||
* @param listingId - The listing id to buy
|
||||
* @param quantityDesired - the quantity to buy
|
||||
* @param receiver - optional receiver of the bought listing if different from the connected wallet
|
||||
*/
|
||||
public async buyFromListing(
|
||||
listingId: BigNumberish,
|
||||
quantityDesired: BigNumberish,
|
||||
receiver?: string,
|
||||
): Promise<TransactionResult> {
|
||||
const listing = await this.validateListing(BigNumber.from(listingId));
|
||||
const { valid, error } = await this.isStillValidListing(
|
||||
listing,
|
||||
quantityDesired,
|
||||
);
|
||||
if (!valid) {
|
||||
throw new Error(`Listing ${listingId} is no longer valid. ${error}`);
|
||||
}
|
||||
const buyFor = receiver
|
||||
? receiver
|
||||
: await this.contractWrapper.getSignerAddress();
|
||||
const quantity = BigNumber.from(quantityDesired);
|
||||
const value = BigNumber.from(listing.pricePerToken).mul(quantity);
|
||||
const overrides = (await this.contractWrapper.getCallOverrides()) || {};
|
||||
await setErc20Allowance(
|
||||
this.contractWrapper,
|
||||
value,
|
||||
listing.currencyContractAddress,
|
||||
overrides,
|
||||
);
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction(
|
||||
"buyFromListing",
|
||||
[listingId, buyFor, quantity, listing.currencyContractAddress, value],
|
||||
overrides,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve buyer for reserved listing.
|
||||
*
|
||||
* @remarks Approve a buyer to buy from a reserved listing.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The listing ID of the direct listing you want approve buyer for
|
||||
* const listingId = "0";
|
||||
*
|
||||
* await contract.directListings.approveBuyerForReservedListing(listingId, buyer);
|
||||
* ```
|
||||
*
|
||||
* @param listingId - The listing id to buy
|
||||
* @param buyer - Address of buyer being approved
|
||||
*/
|
||||
public async approveBuyerForReservedListing(
|
||||
listingId: BigNumberish,
|
||||
buyer: string,
|
||||
): Promise<TransactionResult> {
|
||||
const isApproved = await this.isBuyerApprovedForListing(listingId, buyer);
|
||||
|
||||
if (!isApproved) {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction(
|
||||
"approveBuyerForListing",
|
||||
[listingId, buyer, true],
|
||||
),
|
||||
};
|
||||
} else {
|
||||
throw new Error(
|
||||
`Buyer ${buyer} already approved for listing ${listingId}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke approval of a buyer for reserved listing.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The listing ID of the direct listing you want approve buyer for
|
||||
* const listingId = "0";
|
||||
*
|
||||
* await contract.directListings.revokeBuyerApprovalForReservedListing(listingId, buyer);
|
||||
* ```
|
||||
*
|
||||
* @param listingId - The listing id to buy
|
||||
* @param buyer - Address of buyer being approved
|
||||
*/
|
||||
public async revokeBuyerApprovalForReservedListing(
|
||||
listingId: BigNumberish,
|
||||
buyer: string,
|
||||
): Promise<TransactionResult> {
|
||||
const isApproved = await this.isBuyerApprovedForListing(listingId, buyer);
|
||||
|
||||
if (isApproved) {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction(
|
||||
"approveBuyerForListing",
|
||||
[listingId, buyer, false],
|
||||
),
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Buyer ${buyer} not approved for listing ${listingId}.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve a currency for a listing.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The listing ID of the direct listing you want approve currency for
|
||||
* const listingId = "0";
|
||||
*
|
||||
* await contract.directListings.approveCurrencyForListing(listingId, currencyContractAddress, pricePerTokenInCurrency);
|
||||
* ```
|
||||
*
|
||||
* @param listingId - The listing id to buy
|
||||
* @param currencyContractAddress - Address of currency being approved
|
||||
* @param pricePerTokenInCurrency - Price per token in the currency
|
||||
*/
|
||||
public async approveCurrencyForListing(
|
||||
listingId: BigNumberish,
|
||||
currencyContractAddress: string,
|
||||
pricePerTokenInCurrency: BigNumberish,
|
||||
): Promise<TransactionResult> {
|
||||
const listing = await this.validateListing(BigNumber.from(listingId));
|
||||
|
||||
if (currencyContractAddress === listing.currencyContractAddress) {
|
||||
invariant(
|
||||
pricePerTokenInCurrency === listing.pricePerToken,
|
||||
"Approving listing currency with a different price.",
|
||||
);
|
||||
}
|
||||
|
||||
const currencyPrice =
|
||||
await this.contractWrapper.readContract.currencyPriceForListing(
|
||||
listingId,
|
||||
currencyContractAddress,
|
||||
);
|
||||
invariant(
|
||||
pricePerTokenInCurrency === currencyPrice,
|
||||
"Currency already approved with this price.",
|
||||
);
|
||||
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction(
|
||||
"approveCurrencyForListing",
|
||||
[listingId, currencyContractAddress, pricePerTokenInCurrency],
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke approval of a currency from a listing.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The listing ID of the direct listing you want to revoke currency for
|
||||
* const listingId = "0";
|
||||
*
|
||||
* await contract.directListings.revokeCurrencyApprovalForListing(listingId, currencyContractAddress);
|
||||
* ```
|
||||
*
|
||||
* @param listingId - The listing id to buy
|
||||
* @param currencyContractAddress - Address of currency
|
||||
*/
|
||||
public async revokeCurrencyApprovalForListing(
|
||||
listingId: BigNumberish,
|
||||
currencyContractAddress: string,
|
||||
): Promise<TransactionResult> {
|
||||
const listing = await this.validateListing(BigNumber.from(listingId));
|
||||
|
||||
if (currencyContractAddress === listing.currencyContractAddress) {
|
||||
throw new Error(`Can't revoke approval for main listing currency.`);
|
||||
}
|
||||
|
||||
const currencyPrice =
|
||||
await this.contractWrapper.readContract.currencyPriceForListing(
|
||||
listingId,
|
||||
currencyContractAddress,
|
||||
);
|
||||
invariant(!currencyPrice.isZero(), "Currency not approved.");
|
||||
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction(
|
||||
"approveCurrencyForListing",
|
||||
[listingId, currencyContractAddress, BigNumber.from(0)],
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* PRIVATE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Throws error if listing could not be found
|
||||
*
|
||||
* @param listingId - Listing to check for
|
||||
*/
|
||||
private async validateListing(
|
||||
listingId: BigNumber,
|
||||
): Promise<DirectListingV3> {
|
||||
try {
|
||||
return await this.getListing(listingId);
|
||||
} catch (err) {
|
||||
console.error(`Error getting the listing with id ${listingId}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method maps the auction listing to the direct listing interface.
|
||||
*
|
||||
* @internal
|
||||
* @param listing - The listing to map, as returned from the contract.
|
||||
* @returns - The mapped interface.
|
||||
*/
|
||||
private async mapListing(
|
||||
listing: IDirectListings.ListingStruct,
|
||||
): Promise<DirectListingV3> {
|
||||
let status: Status = Status.UNSET;
|
||||
const block = await this.contractWrapper.getProvider().getBlock("latest");
|
||||
const blockTime = block.timestamp;
|
||||
switch (listing.status) {
|
||||
case 1:
|
||||
status = BigNumber.from(listing.startTimestamp).gt(blockTime)
|
||||
? Status.Created
|
||||
: BigNumber.from(listing.endTimestamp).lt(blockTime)
|
||||
? Status.Expired
|
||||
: Status.Active;
|
||||
break;
|
||||
case 2:
|
||||
status = Status.Completed;
|
||||
break;
|
||||
case 3:
|
||||
status = Status.Cancelled;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
assetContractAddress: listing.assetContract,
|
||||
currencyContractAddress: listing.currency,
|
||||
pricePerToken: listing.pricePerToken.toString(),
|
||||
currencyValuePerToken: await fetchCurrencyValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
listing.currency,
|
||||
listing.pricePerToken,
|
||||
),
|
||||
id: listing.listingId.toString(),
|
||||
tokenId: listing.tokenId.toString(),
|
||||
quantity: listing.quantity.toString(),
|
||||
startTimeInSeconds: BigNumber.from(listing.startTimestamp).toNumber(),
|
||||
asset: await fetchTokenMetadataForContract(
|
||||
listing.assetContract,
|
||||
this.contractWrapper.getProvider(),
|
||||
listing.tokenId,
|
||||
this.storage,
|
||||
),
|
||||
endTimeInSeconds: BigNumber.from(listing.endTimestamp).toNumber(),
|
||||
creatorAddress: listing.listingCreator,
|
||||
isReservedListing: listing.reserved,
|
||||
status: status,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to check if a direct listing is still valid.
|
||||
*
|
||||
* Ways a direct listing can become invalid:
|
||||
* 1. The asset holder transferred the asset to another wallet
|
||||
* 2. The asset holder burned the asset
|
||||
* 3. The asset holder removed the approval on the marketplace
|
||||
*
|
||||
* @internal
|
||||
* @param listing - The listing to check.
|
||||
* @returns - True if the listing is valid, false otherwise.
|
||||
*/
|
||||
private async isStillValidListing(
|
||||
listing: DirectListingV3,
|
||||
quantity?: BigNumberish,
|
||||
): Promise<{ valid: boolean; error?: string }> {
|
||||
const approved = await isTokenApprovedForTransfer(
|
||||
this.contractWrapper.getProvider(),
|
||||
this.getAddress(),
|
||||
listing.assetContractAddress,
|
||||
listing.tokenId,
|
||||
listing.creatorAddress,
|
||||
);
|
||||
|
||||
if (!approved) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Token '${listing.tokenId}' from contract '${listing.assetContractAddress}' is not approved for transfer`,
|
||||
};
|
||||
}
|
||||
|
||||
const provider = this.contractWrapper.getProvider();
|
||||
const erc165 = new Contract(
|
||||
listing.assetContractAddress,
|
||||
ERC165Abi,
|
||||
provider,
|
||||
) as IERC165;
|
||||
const isERC721 = await erc165.supportsInterface(InterfaceId_IERC721);
|
||||
const isERC1155 = await erc165.supportsInterface(InterfaceId_IERC1155);
|
||||
if (isERC721) {
|
||||
const asset = new Contract(
|
||||
listing.assetContractAddress,
|
||||
ERC721Abi,
|
||||
provider,
|
||||
) as IERC721;
|
||||
const valid =
|
||||
(await asset.ownerOf(listing.tokenId)).toLowerCase() ===
|
||||
listing.creatorAddress.toLowerCase();
|
||||
return {
|
||||
valid,
|
||||
error: valid
|
||||
? undefined
|
||||
: `Seller is not the owner of Token '${listing.tokenId}' from contract '${listing.assetContractAddress} anymore'`,
|
||||
};
|
||||
} else if (isERC1155) {
|
||||
const asset = new Contract(
|
||||
listing.assetContractAddress,
|
||||
ERC1155Abi,
|
||||
provider,
|
||||
) as IERC1155;
|
||||
const balance = await asset.balanceOf(
|
||||
listing.creatorAddress,
|
||||
listing.tokenId,
|
||||
);
|
||||
const valid = balance.gte(quantity || listing.quantity);
|
||||
return {
|
||||
valid,
|
||||
error: valid
|
||||
? undefined
|
||||
: `Seller does not have enough balance of Token '${listing.tokenId}' from contract '${listing.assetContractAddress} to fulfill the listing`,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Contract does not implement ERC 1155 or ERC 721.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private applyFilter(
|
||||
listings: IDirectListings.ListingStructOutput[],
|
||||
filter?: MarketplaceFilter,
|
||||
) {
|
||||
let rawListings = [...listings];
|
||||
|
||||
if (filter) {
|
||||
if (filter.seller) {
|
||||
rawListings = rawListings.filter(
|
||||
(seller) =>
|
||||
seller.listingCreator.toString().toLowerCase() ===
|
||||
filter?.seller?.toString().toLowerCase(),
|
||||
);
|
||||
}
|
||||
if (filter.tokenContract) {
|
||||
rawListings = rawListings.filter(
|
||||
(tokenContract) =>
|
||||
tokenContract.assetContract.toString().toLowerCase() ===
|
||||
filter?.tokenContract?.toString().toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
if (filter.tokenId !== undefined) {
|
||||
rawListings = rawListings.filter(
|
||||
(tokenContract) =>
|
||||
tokenContract.tokenId.toString() === filter?.tokenId?.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return filter?.count && filter.count < rawListings.length
|
||||
? rawListings.slice(0, filter.count)
|
||||
: rawListings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,816 @@
|
||||
import { AuctionHasNotEndedError } from "../../common";
|
||||
import {
|
||||
cleanCurrencyAddress,
|
||||
fetchCurrencyMetadata,
|
||||
fetchCurrencyValue,
|
||||
normalizePriceValue,
|
||||
setErc20Allowance,
|
||||
} from "../../common/currency";
|
||||
import { getAllInBatches, handleTokenApproval } from "../../common/marketplace";
|
||||
import { fetchTokenMetadataForContract } from "../../common/nft";
|
||||
import { FEATURE_ENGLISH_AUCTIONS } from "../../constants/thirdweb-features";
|
||||
import { Status } from "../../enums";
|
||||
import {
|
||||
EnglishAuctionInputParams,
|
||||
EnglishAuctionInputParamsSchema,
|
||||
} from "../../schema/marketplacev3/english-auctions";
|
||||
import { MarketplaceFilter } from "../../types";
|
||||
import { CurrencyValue, Price } from "../../types/currency";
|
||||
import { EnglishAuction, Bid } from "../../types/marketplacev3";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResult, TransactionResultWithId } from "../types";
|
||||
import { ContractEncoder } from "./contract-encoder";
|
||||
import { ContractEvents } from "./contract-events";
|
||||
import { ContractInterceptor } from "./contract-interceptor";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import type {
|
||||
IEnglishAuctions,
|
||||
EnglishAuctionsLogic,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import { NewAuctionEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/EnglishAuctionsLogic";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, ethers, constants } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* Handles auctions
|
||||
* @public
|
||||
*/
|
||||
export class MarketplaceV3EnglishAuctions<
|
||||
TContract extends EnglishAuctionsLogic,
|
||||
> implements DetectableFeature
|
||||
{
|
||||
featureName = FEATURE_ENGLISH_AUCTIONS.name;
|
||||
private contractWrapper: ContractWrapper<EnglishAuctionsLogic>;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
// utilities
|
||||
public events: ContractEvents<EnglishAuctionsLogic>;
|
||||
public interceptor: ContractInterceptor<EnglishAuctionsLogic>;
|
||||
public encoder: ContractEncoder<EnglishAuctionsLogic>;
|
||||
|
||||
constructor(
|
||||
contractWrapper: ContractWrapper<TContract>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
this.events = new ContractEvents<EnglishAuctionsLogic>(
|
||||
this.contractWrapper,
|
||||
);
|
||||
this.encoder = new ContractEncoder<EnglishAuctionsLogic>(contractWrapper);
|
||||
this.interceptor = new ContractInterceptor<EnglishAuctionsLogic>(
|
||||
this.contractWrapper,
|
||||
);
|
||||
}
|
||||
|
||||
getAddress(): string {
|
||||
return this.contractWrapper.readContract.address;
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* READ FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Get the total number of english auctions.
|
||||
* @returns Returns the total number of auctions created.
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const totalAuctions = await contract.englishAuctions.getTotalCount();
|
||||
* ```
|
||||
*/
|
||||
public async getTotalCount(): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.totalAuctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all auctions.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const auctions = await contract.englishAuctions.getAll();
|
||||
* ```
|
||||
*
|
||||
* @param filter - optional filter parameters
|
||||
* @returns the Auction object array
|
||||
*/
|
||||
public async getAll(filter?: MarketplaceFilter): Promise<EnglishAuction[]> {
|
||||
const totalAuctions = await this.getTotalCount();
|
||||
|
||||
let start = BigNumber.from(filter?.start || 0).toNumber();
|
||||
let end = totalAuctions.toNumber();
|
||||
|
||||
if (end === 0) {
|
||||
throw new Error(`No auctions exist on the contract.`);
|
||||
}
|
||||
|
||||
let rawAuctions: IEnglishAuctions.AuctionStructOutput[] = [];
|
||||
let batches = await getAllInBatches(
|
||||
start,
|
||||
end,
|
||||
this.contractWrapper.readContract.getAllAuctions,
|
||||
);
|
||||
rawAuctions = batches.flat();
|
||||
|
||||
const filteredAuctions = this.applyFilter(rawAuctions, filter);
|
||||
|
||||
return await Promise.all(
|
||||
filteredAuctions.map((auction) => this.mapAuction(auction)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all valid auctions.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const auctions = await contract.englishAuctions.getAllValid();
|
||||
* ```
|
||||
*
|
||||
* @param filter - optional filter parameters
|
||||
* @returns the Auction object array
|
||||
*/
|
||||
public async getAllValid(
|
||||
filter?: MarketplaceFilter,
|
||||
): Promise<EnglishAuction[]> {
|
||||
const totalAuctions = await this.getTotalCount();
|
||||
|
||||
let start = BigNumber.from(filter?.start || 0).toNumber();
|
||||
let end = totalAuctions.toNumber();
|
||||
|
||||
if (end === 0) {
|
||||
throw new Error(`No auctions exist on the contract.`);
|
||||
}
|
||||
|
||||
let rawAuctions: IEnglishAuctions.AuctionStructOutput[] = [];
|
||||
let batches = await getAllInBatches(
|
||||
start,
|
||||
end,
|
||||
this.contractWrapper.readContract.getAllValidAuctions,
|
||||
);
|
||||
rawAuctions = batches.flat();
|
||||
|
||||
const filteredAuctions = this.applyFilter(rawAuctions, filter);
|
||||
|
||||
return await Promise.all(
|
||||
filteredAuctions.map((auction) => this.mapAuction(auction)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Auction by id.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const auctionId = 0;
|
||||
* const auction = await contract.englishAuctions.getAuction(auctionId);
|
||||
* ```
|
||||
*
|
||||
* @param auctionId - the auction Id
|
||||
* @returns the Auction object
|
||||
*/
|
||||
public async getAuction(auctionId: BigNumberish): Promise<EnglishAuction> {
|
||||
const auction = await this.contractWrapper.readContract.getAuction(
|
||||
auctionId,
|
||||
);
|
||||
|
||||
return await this.mapAuction(auction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Highest Bid
|
||||
*
|
||||
* @remarks Get the current highest bid of an active auction.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The ID of the auction
|
||||
* const auctionId = 0;
|
||||
*
|
||||
* contract.englishAuctions.
|
||||
* .getWinningBid(auctionId)
|
||||
* .then((bid) => console.log(bid))
|
||||
* .catch((err) => console.error(err));
|
||||
* ```
|
||||
*/
|
||||
public async getWinningBid(
|
||||
auctionId: BigNumberish,
|
||||
): Promise<Bid | undefined> {
|
||||
await this.validateAuction(BigNumber.from(auctionId));
|
||||
const bid = await this.contractWrapper.readContract.getWinningBid(
|
||||
auctionId,
|
||||
);
|
||||
if (bid._bidder === constants.AddressZero) {
|
||||
return undefined;
|
||||
}
|
||||
return await this.mapBid(
|
||||
auctionId.toString(),
|
||||
bid._bidder,
|
||||
bid._currency,
|
||||
bid._bidAmount.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a bid-amount is/will be a winning bid.
|
||||
*
|
||||
* @param auctionId - Auction Id
|
||||
* @param bidAmount - Amount to bid
|
||||
* @returns the Auction object
|
||||
*/
|
||||
public async isWinningBid(
|
||||
auctionId: BigNumberish,
|
||||
bidAmount: BigNumberish,
|
||||
): Promise<boolean> {
|
||||
return await this.contractWrapper.readContract.isNewWinningBid(
|
||||
auctionId,
|
||||
bidAmount,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Auction Winner
|
||||
*
|
||||
* @remarks Get the winner of the auction after an auction ends.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The auction ID of the auction that closed
|
||||
* const auctionId = 0;
|
||||
*
|
||||
* contract.englishAuctions.
|
||||
* .getWinner(auctionId)
|
||||
* .then((auctionWinner) => console.log(auctionWinner))
|
||||
* .catch((err) => console.error(err));
|
||||
* ```
|
||||
*/
|
||||
public async getWinner(auctionId: BigNumberish): Promise<string> {
|
||||
const auction = await this.validateAuction(BigNumber.from(auctionId));
|
||||
const bid = await this.contractWrapper.readContract.getWinningBid(
|
||||
auctionId,
|
||||
);
|
||||
const now = BigNumber.from(Math.floor(Date.now() / 1000));
|
||||
const endTime = BigNumber.from(auction.endTimeInSeconds);
|
||||
|
||||
// if we have a winner in the map and the current time is past the endtime of the auction return the address of the winner
|
||||
if (now.gt(endTime) && bid._bidder !== constants.AddressZero) {
|
||||
return bid._bidder;
|
||||
}
|
||||
// otherwise fall back to query filter things
|
||||
|
||||
// TODO this should be via indexer or direct contract call
|
||||
const closedAuctions = await this.contractWrapper.readContract.queryFilter(
|
||||
this.contractWrapper.readContract.filters.AuctionClosed(),
|
||||
);
|
||||
const closed = closedAuctions.find((a) =>
|
||||
a.args.auctionId.eq(BigNumber.from(auctionId)),
|
||||
);
|
||||
if (!closed) {
|
||||
throw new Error(
|
||||
`Could not find auction with ID ${auctionId} in closed auctions`,
|
||||
);
|
||||
}
|
||||
return closed.args.winningBidder;
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* WRITE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Create Auction
|
||||
*
|
||||
* @remarks Create a new auction where people can bid on an asset.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Data of the auction you want to create
|
||||
* const auction = {
|
||||
* // address of the contract of the asset you want to auction
|
||||
* assetContractAddress: "0x...",
|
||||
* // token ID of the asset you want to auction
|
||||
* tokenId: "0",
|
||||
* // how many of the asset you want to auction
|
||||
* quantity: 1,
|
||||
* // address of the currency contract that will be used to pay for the auctioned tokens
|
||||
* currencyContractAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* // the minimum bid that will be accepted for the token
|
||||
* minimumBidAmount: "1.5",
|
||||
* // how much people would have to bid to instantly buy the asset
|
||||
* buyoutBidAmount: "10",
|
||||
* // If a bid is made less than these many seconds before expiration, the expiration time is increased by this.
|
||||
* timeBufferInSeconds: "900", // 15 minutes by default
|
||||
* // A bid must be at least this much bps greater than the current winning bid
|
||||
* bidBufferBps: "500", // 5% by default
|
||||
* // when should the auction open up for bidding
|
||||
* startTimestamp: new Date(Date.now()),
|
||||
* // end time of auction
|
||||
* endTimestamp: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000),
|
||||
* }
|
||||
*
|
||||
* const tx = await contract.englishAuctions.createAuction(auction);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const id = tx.id; // the id of the newly created auction
|
||||
* ```
|
||||
*/
|
||||
public async createAuction(
|
||||
auction: EnglishAuctionInputParams,
|
||||
): Promise<TransactionResultWithId> {
|
||||
const parsedAuction = EnglishAuctionInputParamsSchema.parse(auction);
|
||||
|
||||
await handleTokenApproval(
|
||||
this.contractWrapper,
|
||||
this.getAddress(),
|
||||
parsedAuction.assetContractAddress,
|
||||
parsedAuction.tokenId,
|
||||
await this.contractWrapper.getSignerAddress(),
|
||||
);
|
||||
|
||||
const normalizedBuyoutAmount = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
parsedAuction.buyoutBidAmount,
|
||||
parsedAuction.currencyContractAddress,
|
||||
);
|
||||
|
||||
const normalizedMinBidAmount = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
parsedAuction.minimumBidAmount,
|
||||
parsedAuction.currencyContractAddress,
|
||||
);
|
||||
|
||||
const block = await this.contractWrapper.getProvider().getBlock("latest");
|
||||
const blockTime = block.timestamp;
|
||||
if (parsedAuction.startTimestamp.lt(blockTime)) {
|
||||
parsedAuction.startTimestamp = BigNumber.from(blockTime);
|
||||
}
|
||||
|
||||
const receipt = await this.contractWrapper.sendTransaction(
|
||||
"createAuction",
|
||||
[
|
||||
{
|
||||
assetContract: parsedAuction.assetContractAddress,
|
||||
tokenId: parsedAuction.tokenId,
|
||||
quantity: parsedAuction.quantity,
|
||||
currency: cleanCurrencyAddress(parsedAuction.currencyContractAddress),
|
||||
minimumBidAmount: normalizedMinBidAmount,
|
||||
buyoutBidAmount: normalizedBuyoutAmount,
|
||||
timeBufferInSeconds: parsedAuction.timeBufferInSeconds,
|
||||
bidBufferBps: parsedAuction.bidBufferBps,
|
||||
startTimestamp: parsedAuction.startTimestamp,
|
||||
endTimestamp: parsedAuction.endTimestamp,
|
||||
} as IEnglishAuctions.AuctionParametersStruct,
|
||||
],
|
||||
{
|
||||
// Higher gas limit for create auction
|
||||
gasLimit: 500000,
|
||||
},
|
||||
);
|
||||
|
||||
const event = this.contractWrapper.parseLogs<NewAuctionEvent>(
|
||||
"NewAuction",
|
||||
receipt?.logs,
|
||||
);
|
||||
return {
|
||||
id: event[0].args.auctionId,
|
||||
receipt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Buyout Auction
|
||||
*
|
||||
* @remarks Buy a specific auction from the marketplace.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The auction ID of the asset you want to buy
|
||||
* const auctionId = 0;
|
||||
*
|
||||
* await contract.englishAuctions.buyoutAuction(auctionId);
|
||||
* ```
|
||||
*/
|
||||
public async buyoutAuction(
|
||||
auctionId: BigNumberish,
|
||||
): Promise<TransactionResult> {
|
||||
const auction = await this.validateAuction(BigNumber.from(auctionId));
|
||||
|
||||
const currencyMetadata = await fetchCurrencyMetadata(
|
||||
this.contractWrapper.getProvider(),
|
||||
auction.currencyContractAddress,
|
||||
);
|
||||
|
||||
return this.makeBid(
|
||||
auctionId,
|
||||
ethers.utils.formatUnits(
|
||||
auction.buyoutBidAmount,
|
||||
currencyMetadata.decimals,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bid On Auction
|
||||
*
|
||||
* @remarks Make a bid on an auction
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The auction ID of the asset you want to bid on
|
||||
* const auctionId = 0;
|
||||
* // The total amount you are willing to bid for auctioned tokens
|
||||
* const bidAmount = 1;
|
||||
*
|
||||
* await contract.englishAuctions.makeBid(auctionId, bidAmount);
|
||||
* ```
|
||||
*/
|
||||
public async makeBid(
|
||||
auctionId: BigNumberish,
|
||||
bidAmount: Price,
|
||||
): Promise<TransactionResult> {
|
||||
const auction = await this.validateAuction(BigNumber.from(auctionId));
|
||||
|
||||
const normalizedBidAmount = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
bidAmount,
|
||||
auction.currencyContractAddress,
|
||||
);
|
||||
if (normalizedBidAmount.eq(BigNumber.from(0))) {
|
||||
throw new Error("Cannot make a bid with 0 value");
|
||||
}
|
||||
|
||||
if (normalizedBidAmount.gt(auction.buyoutBidAmount)) {
|
||||
throw new Error(
|
||||
"Bid amount must be less than or equal to buyoutBidAmount",
|
||||
);
|
||||
}
|
||||
|
||||
const winningBid = await this.getWinningBid(auctionId);
|
||||
if (winningBid) {
|
||||
const isWinnner = await this.isWinningBid(auctionId, normalizedBidAmount);
|
||||
|
||||
invariant(
|
||||
isWinnner,
|
||||
"Bid price is too low based on the current winning bid and the bid buffer",
|
||||
);
|
||||
} else {
|
||||
const tokenPrice = normalizedBidAmount;
|
||||
const minimumBidAmount = BigNumber.from(auction.minimumBidAmount);
|
||||
invariant(
|
||||
tokenPrice.gte(minimumBidAmount),
|
||||
"Bid price is too low based on minimum bid amount",
|
||||
);
|
||||
}
|
||||
|
||||
const overrides = (await this.contractWrapper.getCallOverrides()) || {};
|
||||
await setErc20Allowance(
|
||||
this.contractWrapper,
|
||||
normalizedBidAmount,
|
||||
auction.currencyContractAddress,
|
||||
overrides,
|
||||
);
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction(
|
||||
"bidInAuction",
|
||||
[auctionId, normalizedBidAmount],
|
||||
overrides,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel Auction
|
||||
*
|
||||
* @remarks Cancel an auction on the marketplace
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The ID of the auction you want to cancel
|
||||
* const auctionId = "0";
|
||||
*
|
||||
* await contract.englishAuctions.cancelAuction(auctionId);
|
||||
* ```
|
||||
*/
|
||||
public async cancelAuction(
|
||||
auctionId: BigNumberish,
|
||||
): Promise<TransactionResult> {
|
||||
const winningBid = await this.getWinningBid(auctionId);
|
||||
if (winningBid) {
|
||||
throw new Error(`Bids already made.`);
|
||||
}
|
||||
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction("cancelAuction", [
|
||||
auctionId,
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Auction for the buyer
|
||||
*
|
||||
* @remarks Closes the Auction and executes the sale for the buyer.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The ID of the auction you want to close
|
||||
* const auction = "0";
|
||||
* await contract.englishAuctions.closeAuctionForBidder(auctionId);
|
||||
* ```
|
||||
*
|
||||
* @param auctionId - the auction id to close
|
||||
* @param closeFor - optionally pass the winning bid offeror address to close the auction on their behalf
|
||||
*/
|
||||
public async closeAuctionForBidder(
|
||||
auctionId: BigNumberish,
|
||||
closeFor?: string,
|
||||
): Promise<TransactionResult> {
|
||||
if (!closeFor) {
|
||||
closeFor = await this.contractWrapper.getSignerAddress();
|
||||
}
|
||||
const auction = await this.validateAuction(BigNumber.from(auctionId));
|
||||
try {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction(
|
||||
"collectAuctionTokens",
|
||||
[BigNumber.from(auctionId)],
|
||||
),
|
||||
};
|
||||
} catch (err: any) {
|
||||
if (err.message.includes("Marketplace: auction still active.")) {
|
||||
throw new AuctionHasNotEndedError(
|
||||
auctionId.toString(),
|
||||
auction.endTimeInSeconds.toString(),
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Auction for the seller, i.e. the auction creator
|
||||
*
|
||||
* @remarks Closes the Auction and executes the sale for the seller.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The ID of the auction you want to close
|
||||
* const auctionId = "0";
|
||||
* await contract.englishAuctions.closeAuctionForSeller(auctionId);
|
||||
* ```
|
||||
*
|
||||
* @param auctionId - the auction id to close
|
||||
*/
|
||||
public async closeAuctionForSeller(
|
||||
auctionId: BigNumberish,
|
||||
): Promise<TransactionResult> {
|
||||
const auction = await this.validateAuction(BigNumber.from(auctionId));
|
||||
try {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction(
|
||||
"collectAuctionPayout",
|
||||
[BigNumber.from(auctionId)],
|
||||
),
|
||||
};
|
||||
} catch (err: any) {
|
||||
if (err.message.includes("Marketplace: auction still active.")) {
|
||||
throw new AuctionHasNotEndedError(
|
||||
auctionId.toString(),
|
||||
auction.endTimeInSeconds.toString(),
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the Auction Sale
|
||||
*
|
||||
* @remarks Closes the Auction and executes the sale for both parties.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The ID of the auction you want to close
|
||||
* const auction = "0";
|
||||
* await contract.englishAuctions.executeSale(auctionId);
|
||||
* ```
|
||||
*
|
||||
* @param auctionId - the auction to close
|
||||
*/
|
||||
public async executeSale(auctionId: BigNumberish) {
|
||||
const auction = await this.validateAuction(BigNumber.from(auctionId));
|
||||
try {
|
||||
const winningBid = await this.getWinningBid(auctionId);
|
||||
invariant(winningBid, "No winning bid found");
|
||||
const closeForSeller = this.encoder.encode("collectAuctionPayout", [
|
||||
auctionId,
|
||||
]);
|
||||
const closeForBuyer = this.encoder.encode("collectAuctionTokens", [
|
||||
auctionId,
|
||||
]);
|
||||
return await this.contractWrapper.multiCall([
|
||||
closeForSeller,
|
||||
closeForBuyer,
|
||||
]);
|
||||
} catch (err: any) {
|
||||
if (err.message.includes("Marketplace: auction still active.")) {
|
||||
throw new AuctionHasNotEndedError(
|
||||
auctionId.toString(),
|
||||
auction.endTimeInSeconds.toString(),
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the buffer in basis points between bids for an auction.
|
||||
*
|
||||
* @param auctionId - id of the auction
|
||||
*/
|
||||
public async getBidBufferBps(auctionId: BigNumberish): Promise<number> {
|
||||
return (await this.getAuction(auctionId)).bidBufferBps;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the minimum bid a user can place to outbid the previous highest bid
|
||||
* @param auctionId - id of the auction
|
||||
*/
|
||||
public async getMinimumNextBid(
|
||||
auctionId: BigNumberish,
|
||||
): Promise<CurrencyValue> {
|
||||
// we can fetch all of these at the same time using promise.all
|
||||
const [currentBidBufferBps, winningBid, auction] = await Promise.all([
|
||||
this.getBidBufferBps(auctionId),
|
||||
this.getWinningBid(auctionId),
|
||||
await this.validateAuction(BigNumber.from(auctionId)),
|
||||
]);
|
||||
|
||||
const currentBidOrReservePrice = winningBid
|
||||
? // if there is a winning bid use the value of it
|
||||
BigNumber.from(winningBid.bidAmount)
|
||||
: // if there is no winning bid use the reserve price
|
||||
BigNumber.from(auction.minimumBidAmount);
|
||||
|
||||
const minimumNextBid = currentBidOrReservePrice.add(
|
||||
// the addition of the current bid and the buffer
|
||||
// (have to divide by 10000 to get the fraction of the buffer (since it's in basis points))
|
||||
currentBidOrReservePrice.mul(currentBidBufferBps).div(10000),
|
||||
);
|
||||
|
||||
// it's more useful to return a currency value here
|
||||
return fetchCurrencyValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
auction.currencyContractAddress,
|
||||
minimumNextBid,
|
||||
);
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* PRIVATE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Throws error if auction could not be found
|
||||
*
|
||||
* @param auctionId - Auction to check for
|
||||
*/
|
||||
private async validateAuction(auctionId: BigNumber): Promise<EnglishAuction> {
|
||||
try {
|
||||
return await this.getAuction(auctionId);
|
||||
} catch (err) {
|
||||
console.error(`Error getting the auction with id ${auctionId}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method maps the auction to the auction interface.
|
||||
*
|
||||
* @internal
|
||||
* @param auction - The auction to map, as returned from the contract.
|
||||
* @returns - The mapped interface.
|
||||
*/
|
||||
private async mapAuction(
|
||||
auction: IEnglishAuctions.AuctionStruct,
|
||||
): Promise<EnglishAuction> {
|
||||
let status: Status = Status.UNSET;
|
||||
const block = await this.contractWrapper.getProvider().getBlock("latest");
|
||||
const blockTime = block.timestamp;
|
||||
switch (auction.status) {
|
||||
case 1:
|
||||
status = BigNumber.from(auction.startTimestamp).gt(blockTime)
|
||||
? Status.Created
|
||||
: BigNumber.from(auction.endTimestamp).lt(blockTime)
|
||||
? Status.Expired
|
||||
: Status.Active;
|
||||
break;
|
||||
case 2:
|
||||
status = Status.Completed;
|
||||
break;
|
||||
case 3:
|
||||
status = Status.Cancelled;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
id: auction.auctionId.toString(),
|
||||
creatorAddress: auction.auctionCreator,
|
||||
assetContractAddress: auction.assetContract,
|
||||
tokenId: auction.tokenId.toString(),
|
||||
quantity: auction.quantity.toString(),
|
||||
currencyContractAddress: auction.currency,
|
||||
minimumBidAmount: auction.minimumBidAmount.toString(),
|
||||
minimumBidCurrencyValue: await fetchCurrencyValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
auction.currency,
|
||||
auction.minimumBidAmount,
|
||||
),
|
||||
buyoutBidAmount: auction.buyoutBidAmount.toString(),
|
||||
buyoutCurrencyValue: await fetchCurrencyValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
auction.currency,
|
||||
auction.buyoutBidAmount,
|
||||
),
|
||||
timeBufferInSeconds: BigNumber.from(
|
||||
auction.timeBufferInSeconds,
|
||||
).toNumber(),
|
||||
bidBufferBps: BigNumber.from(auction.bidBufferBps).toNumber(),
|
||||
startTimeInSeconds: BigNumber.from(auction.startTimestamp).toNumber(),
|
||||
endTimeInSeconds: BigNumber.from(auction.endTimestamp).toNumber(),
|
||||
asset: await fetchTokenMetadataForContract(
|
||||
auction.assetContract,
|
||||
this.contractWrapper.getProvider(),
|
||||
auction.tokenId,
|
||||
this.storage,
|
||||
),
|
||||
status: status,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an auction-bid to the strict interface
|
||||
*
|
||||
* @internal
|
||||
* @param bid
|
||||
* @returns - A `Bid` object
|
||||
*/
|
||||
private async mapBid(
|
||||
auctionId: string,
|
||||
bidderAddress: string,
|
||||
currencyContractAddress: string,
|
||||
bidAmount: string,
|
||||
): Promise<Bid> {
|
||||
return {
|
||||
auctionId,
|
||||
bidderAddress,
|
||||
currencyContractAddress,
|
||||
bidAmount,
|
||||
bidAmountCurrencyValue: await fetchCurrencyValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
currencyContractAddress,
|
||||
bidAmount,
|
||||
),
|
||||
} as Bid;
|
||||
}
|
||||
|
||||
private applyFilter(
|
||||
auctions: IEnglishAuctions.AuctionStructOutput[],
|
||||
filter?: MarketplaceFilter,
|
||||
) {
|
||||
let rawAuctions = [...auctions];
|
||||
|
||||
if (filter) {
|
||||
if (filter.seller) {
|
||||
rawAuctions = rawAuctions.filter(
|
||||
(seller) =>
|
||||
seller.auctionCreator.toString().toLowerCase() ===
|
||||
filter?.seller?.toString().toLowerCase(),
|
||||
);
|
||||
}
|
||||
if (filter.tokenContract) {
|
||||
rawAuctions = rawAuctions.filter(
|
||||
(tokenContract) =>
|
||||
tokenContract.assetContract.toString().toLowerCase() ===
|
||||
filter?.tokenContract?.toString().toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
if (filter.tokenId !== undefined) {
|
||||
rawAuctions = rawAuctions.filter(
|
||||
(tokenContract) =>
|
||||
tokenContract.tokenId.toString() === filter?.tokenId?.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return filter?.count && filter.count < rawAuctions.length
|
||||
? rawAuctions.slice(0, filter.count)
|
||||
: rawAuctions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
import {
|
||||
fetchCurrencyValue,
|
||||
isNativeToken,
|
||||
normalizePriceValue,
|
||||
setErc20Allowance,
|
||||
} from "../../common/currency";
|
||||
import { getAllInBatches, handleTokenApproval } from "../../common/marketplace";
|
||||
import { fetchTokenMetadataForContract } from "../../common/nft";
|
||||
import { NATIVE_TOKENS, SUPPORTED_CHAIN_ID } from "../../constants";
|
||||
import { FEATURE_OFFERS } from "../../constants/thirdweb-features";
|
||||
import { Status } from "../../enums";
|
||||
import {
|
||||
OfferInputParams,
|
||||
OfferInputParamsSchema,
|
||||
} from "../../schema/marketplacev3/offer";
|
||||
import { MarketplaceFilter } from "../../types";
|
||||
import { OfferV3 } from "../../types/marketplacev3";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResult, TransactionResultWithId } from "../types";
|
||||
import { ContractEncoder } from "./contract-encoder";
|
||||
import { ContractEvents } from "./contract-events";
|
||||
import { ContractInterceptor } from "./contract-interceptor";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import type { IERC20, IOffers, OffersLogic } from "@thirdweb-dev/contracts-js";
|
||||
import ERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC20.json";
|
||||
import { NewOfferEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/OffersLogic";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish } from "ethers";
|
||||
|
||||
/**
|
||||
* Handles marketplace offers
|
||||
* @public
|
||||
*/
|
||||
export class MarketplaceV3Offers<TContract extends OffersLogic>
|
||||
implements DetectableFeature
|
||||
{
|
||||
featureName = FEATURE_OFFERS.name;
|
||||
private contractWrapper: ContractWrapper<OffersLogic>;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
// utilities
|
||||
public events: ContractEvents<OffersLogic>;
|
||||
public interceptor: ContractInterceptor<OffersLogic>;
|
||||
public encoder: ContractEncoder<OffersLogic>;
|
||||
|
||||
constructor(
|
||||
contractWrapper: ContractWrapper<TContract>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
|
||||
this.events = new ContractEvents<OffersLogic>(this.contractWrapper);
|
||||
this.encoder = new ContractEncoder<OffersLogic>(contractWrapper);
|
||||
this.interceptor = new ContractInterceptor<OffersLogic>(
|
||||
this.contractWrapper,
|
||||
);
|
||||
}
|
||||
|
||||
getAddress(): string {
|
||||
return this.contractWrapper.readContract.address;
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* READ FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Get the total number of offers
|
||||
* @returns Returns the total number of offers created.
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const totalOffers = await contract.offers.getTotalCount();
|
||||
* ```
|
||||
*/
|
||||
public async getTotalCount(): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.totalOffers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all offers.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const offers = await contract.offers.getAll();
|
||||
* ```
|
||||
*
|
||||
* @param filter - optional filter parameters
|
||||
* @returns the Offer object array
|
||||
*/
|
||||
public async getAll(filter?: MarketplaceFilter): Promise<OfferV3[]> {
|
||||
const totalOffers = await this.getTotalCount();
|
||||
|
||||
let start = BigNumber.from(filter?.start || 0).toNumber();
|
||||
let end = totalOffers.toNumber();
|
||||
|
||||
if (end === 0) {
|
||||
throw new Error(`No offers exist on the contract.`);
|
||||
}
|
||||
|
||||
let rawOffers: IOffers.OfferStructOutput[] = [];
|
||||
let batches = await getAllInBatches(
|
||||
start,
|
||||
end,
|
||||
this.contractWrapper.readContract.getAllOffers,
|
||||
);
|
||||
rawOffers = batches.flat();
|
||||
|
||||
const filteredOffers = this.applyFilter(rawOffers, filter);
|
||||
|
||||
return await Promise.all(
|
||||
filteredOffers.map((offer) => this.mapOffer(offer)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all valid offers.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const offers = await contract.offers.getAllValid();
|
||||
* ```
|
||||
*
|
||||
* @param filter - optional filter parameters
|
||||
* @returns the Offer object array
|
||||
*/
|
||||
public async getAllValid(filter?: MarketplaceFilter): Promise<OfferV3[]> {
|
||||
const totalOffers = await this.getTotalCount();
|
||||
|
||||
let start = BigNumber.from(filter?.start || 0).toNumber();
|
||||
let end = totalOffers.toNumber();
|
||||
|
||||
if (end === 0) {
|
||||
throw new Error(`No offers exist on the contract.`);
|
||||
}
|
||||
|
||||
let rawOffers: IOffers.OfferStructOutput[] = [];
|
||||
let batches = await getAllInBatches(
|
||||
start,
|
||||
end,
|
||||
this.contractWrapper.readContract.getAllValidOffers,
|
||||
);
|
||||
rawOffers = batches.flat();
|
||||
|
||||
const filteredOffers = this.applyFilter(rawOffers, filter);
|
||||
|
||||
return await Promise.all(
|
||||
filteredOffers.map((offer) => this.mapOffer(offer)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a offer by id
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const offerId = 0;
|
||||
* const offer = await contract.offers.getOffer(offerId);
|
||||
* ```
|
||||
*
|
||||
* @param offerId - the listing id
|
||||
* @returns the Direct listing object
|
||||
*/
|
||||
public async getOffer(offerId: BigNumberish): Promise<OfferV3> {
|
||||
const offer = await this.contractWrapper.readContract.getOffer(offerId);
|
||||
|
||||
return await this.mapOffer(offer);
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* WRITE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Make Offer
|
||||
*
|
||||
* @remarks Make an offer on the marketplace for an asset.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Data of the offer you want to make
|
||||
* const offer = {
|
||||
* // address of the contract the asset you want to make an offer for
|
||||
* assetContractAddress: "0x...",
|
||||
* // token ID of the asset you want to buy
|
||||
* tokenId: "0",
|
||||
* // how many of the asset you want to buy
|
||||
* quantity: 1,
|
||||
* // address of the currency contract that you offer to pay in
|
||||
* currencyContractAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* // Total price you offer to pay for the mentioned token(s)
|
||||
* totalPrice: "1.5",
|
||||
* // Offer valid until
|
||||
* endTimestamp: new Date(),
|
||||
* }
|
||||
*
|
||||
* const tx = await contract.offers.makeOffer(offer);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const id = tx.id; // the id of the newly created offer
|
||||
* ```
|
||||
*/
|
||||
public async makeOffer(
|
||||
offer: OfferInputParams,
|
||||
): Promise<TransactionResultWithId> {
|
||||
const parsedOffer = OfferInputParamsSchema.parse(offer);
|
||||
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const currency = isNativeToken(parsedOffer.currencyContractAddress)
|
||||
? NATIVE_TOKENS[chainId as SUPPORTED_CHAIN_ID].wrapped.address
|
||||
: parsedOffer.currencyContractAddress;
|
||||
|
||||
const normalizedTotalPrice = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
parsedOffer.totalPrice,
|
||||
currency,
|
||||
);
|
||||
|
||||
const overrides = await this.contractWrapper.getCallOverrides();
|
||||
await setErc20Allowance(
|
||||
this.contractWrapper,
|
||||
normalizedTotalPrice,
|
||||
currency,
|
||||
overrides,
|
||||
);
|
||||
|
||||
const receipt = await this.contractWrapper.sendTransaction(
|
||||
"makeOffer",
|
||||
[
|
||||
{
|
||||
assetContract: parsedOffer.assetContractAddress,
|
||||
tokenId: parsedOffer.tokenId,
|
||||
quantity: parsedOffer.quantity,
|
||||
currency: currency,
|
||||
totalPrice: normalizedTotalPrice,
|
||||
expirationTimestamp: parsedOffer.endTimestamp,
|
||||
} as IOffers.OfferParamsStruct,
|
||||
],
|
||||
{
|
||||
// Higher gas limit for create listing
|
||||
gasLimit: 500000,
|
||||
},
|
||||
);
|
||||
|
||||
const event = this.contractWrapper.parseLogs<NewOfferEvent>(
|
||||
"NewOffer",
|
||||
receipt?.logs,
|
||||
);
|
||||
return {
|
||||
id: event[0].args.offerId,
|
||||
receipt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel Offer
|
||||
*
|
||||
* @remarks Cancel an offer on the marketplace
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The ID of the offer you want to cancel
|
||||
* const offerId = "0";
|
||||
*
|
||||
* await contract.offers.cancelOffer(offerId);
|
||||
* ```
|
||||
*/
|
||||
public async cancelOffer(offerId: BigNumberish): Promise<TransactionResult> {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction("cancelOffer", [
|
||||
offerId,
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept an offer
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The ID of the offer you want to accept
|
||||
* const offerId = 0;
|
||||
*
|
||||
* await contract.offers.acceptOffer(offerId);
|
||||
* ```
|
||||
*
|
||||
* @param offerId - The offer id
|
||||
*/
|
||||
public async acceptOffer(offerId: BigNumberish): Promise<TransactionResult> {
|
||||
const offer = await this.validateOffer(BigNumber.from(offerId));
|
||||
const { valid, error } = await this.isStillValidOffer(offer);
|
||||
if (!valid) {
|
||||
throw new Error(`Offer ${offerId} is no longer valid. ${error}`);
|
||||
}
|
||||
const overrides = (await this.contractWrapper.getCallOverrides()) || {};
|
||||
|
||||
await handleTokenApproval(
|
||||
this.contractWrapper,
|
||||
this.getAddress(),
|
||||
offer.assetContractAddress,
|
||||
offer.tokenId,
|
||||
await this.contractWrapper.getSignerAddress(),
|
||||
);
|
||||
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction(
|
||||
"acceptOffer",
|
||||
[offerId],
|
||||
overrides,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* PRIVATE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Throws error if offer could not be found
|
||||
*
|
||||
* @param offerId - offer to check for
|
||||
*/
|
||||
private async validateOffer(offerId: BigNumber): Promise<OfferV3> {
|
||||
try {
|
||||
return await this.getOffer(offerId);
|
||||
} catch (err) {
|
||||
console.error(`Error getting the offer with id ${offerId}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method maps the offer to the offer interface.
|
||||
*
|
||||
* @internal
|
||||
* @param offer - The offer to map, as returned from the contract.
|
||||
* @returns - The mapped interface.
|
||||
*/
|
||||
private async mapOffer(offer: IOffers.OfferStruct): Promise<OfferV3> {
|
||||
let status: Status = Status.UNSET;
|
||||
const block = await this.contractWrapper.getProvider().getBlock("latest");
|
||||
const blockTime = block.timestamp;
|
||||
switch (offer.status) {
|
||||
case 1:
|
||||
status = BigNumber.from(offer.expirationTimestamp).lt(blockTime)
|
||||
? Status.Expired
|
||||
: Status.Active;
|
||||
break;
|
||||
case 2:
|
||||
status = Status.Completed;
|
||||
break;
|
||||
case 3:
|
||||
status = Status.Cancelled;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
id: offer.offerId.toString(),
|
||||
offerorAddress: offer.offeror,
|
||||
assetContractAddress: offer.assetContract,
|
||||
currencyContractAddress: offer.currency,
|
||||
tokenId: offer.tokenId.toString(),
|
||||
quantity: offer.quantity.toString(),
|
||||
totalPrice: offer.totalPrice.toString(),
|
||||
currencyValue: await fetchCurrencyValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
offer.currency,
|
||||
offer.totalPrice,
|
||||
),
|
||||
asset: await fetchTokenMetadataForContract(
|
||||
offer.assetContract,
|
||||
this.contractWrapper.getProvider(),
|
||||
offer.tokenId,
|
||||
this.storage,
|
||||
),
|
||||
endTimeInSeconds: BigNumber.from(offer.expirationTimestamp).toNumber(),
|
||||
status: status,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to check if an offer is still valid.
|
||||
*
|
||||
* Ways an offer can become invalid:
|
||||
* 1. The offer has expired
|
||||
* 2. The offeror doesn't have enough balance of currency tokens
|
||||
* 3. The offeror removed the approval of currency tokens on the marketplace
|
||||
*
|
||||
* @internal
|
||||
* @param offer - The offer to check.
|
||||
* @returns - True if the offer is valid, false otherwise.
|
||||
*/
|
||||
private async isStillValidOffer(
|
||||
offer: OfferV3,
|
||||
): Promise<{ valid: boolean; error?: string }> {
|
||||
const now = BigNumber.from(Math.floor(Date.now() / 1000));
|
||||
if (now.gt(offer.endTimeInSeconds)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Offer with ID ${offer.id} has expired`,
|
||||
};
|
||||
}
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const currency = isNativeToken(offer.currencyContractAddress)
|
||||
? NATIVE_TOKENS[chainId as SUPPORTED_CHAIN_ID].wrapped.address
|
||||
: offer.currencyContractAddress;
|
||||
|
||||
const provider = this.contractWrapper.getProvider();
|
||||
const erc20 = new ContractWrapper<IERC20>(provider, currency, ERC20Abi, {});
|
||||
|
||||
const offerorBalance = await erc20.readContract.balanceOf(
|
||||
offer.offerorAddress,
|
||||
);
|
||||
if (offerorBalance.lt(offer.totalPrice)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Offeror ${offer.offerorAddress} doesn't have enough balance of token ${currency}`,
|
||||
};
|
||||
}
|
||||
|
||||
const offerorAllowance = await erc20.readContract.allowance(
|
||||
offer.offerorAddress,
|
||||
this.getAddress(),
|
||||
);
|
||||
if (offerorAllowance.lt(offer.totalPrice)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Offeror ${offer.offerorAddress} hasn't approved enough amount of token ${currency}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
error: "",
|
||||
};
|
||||
}
|
||||
|
||||
private applyFilter(
|
||||
offers: IOffers.OfferStructOutput[],
|
||||
filter?: MarketplaceFilter,
|
||||
) {
|
||||
let rawOffers = [...offers];
|
||||
|
||||
if (filter) {
|
||||
if (filter.offeror) {
|
||||
rawOffers = rawOffers.filter(
|
||||
(offeror) =>
|
||||
offeror.offeror.toString().toLowerCase() ===
|
||||
filter?.offeror?.toString().toLowerCase(),
|
||||
);
|
||||
}
|
||||
if (filter.tokenContract) {
|
||||
rawOffers = rawOffers.filter(
|
||||
(tokenContract) =>
|
||||
tokenContract.assetContract.toString().toLowerCase() ===
|
||||
filter?.tokenContract?.toString().toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
if (filter.tokenId !== undefined) {
|
||||
rawOffers = rawOffers.filter(
|
||||
(tokenContract) =>
|
||||
tokenContract.tokenId.toString() === filter?.tokenId?.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return filter?.count && filter.count < rawOffers.length
|
||||
? rawOffers.slice(0, filter.count)
|
||||
: rawOffers;
|
||||
}
|
||||
}
|
||||
@@ -353,6 +353,20 @@ export class ThirdwebSDK extends RPCConnectionHandler {
|
||||
return await this.getContract(contractAddress, "marketplace");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of a Marketplace contract
|
||||
* @param contractAddress - the address of the deployed contract
|
||||
* @deprecated
|
||||
* This method is deprecated and will be removed in a future major version. You should use {@link getContract} instead.
|
||||
* ```diff
|
||||
* - const marketplace = await sdk.getMarketplaceV3("0x1234...");
|
||||
* + const marketplace = await sdk.getContract("0x1234...", "marketplace-v3");
|
||||
* ```
|
||||
*/
|
||||
public async getMarketplaceV3(contractAddress: string) {
|
||||
return await this.getContract(contractAddress, "marketplace-v3");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of a Pack contract
|
||||
* @param contractAddress - the address of the deployed contract
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export enum Status {
|
||||
UNSET = 0,
|
||||
Created = 1,
|
||||
Completed = 2,
|
||||
Cancelled = 3,
|
||||
Active = 4,
|
||||
Expired = 5,
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./ListingType";
|
||||
export * from "./Status";
|
||||
|
||||
@@ -33,6 +33,7 @@ export * from "./integrations/thirdweb-checkout";
|
||||
export type { Edition } from "./contracts/prebuilt-implementations/edition";
|
||||
export type { EditionDrop } from "./contracts/prebuilt-implementations/edition-drop";
|
||||
export type { Marketplace } from "./contracts/prebuilt-implementations/marketplace";
|
||||
export type { MarketplaceV3 } from "./contracts/prebuilt-implementations/marketplacev3";
|
||||
export type { Multiwrap } from "./contracts/prebuilt-implementations/multiwrap";
|
||||
export type { NFTCollection } from "./contracts/prebuilt-implementations/nft-collection";
|
||||
export type { NFTDrop } from "./contracts/prebuilt-implementations/nft-drop";
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { AmountSchema } from "../../../core/schema/shared";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../constants";
|
||||
import {
|
||||
AddressSchema,
|
||||
BigNumberishSchema,
|
||||
EndDateSchema,
|
||||
RawDateSchema,
|
||||
} from "../shared";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const DirectListingInputParamsSchema = z.object({
|
||||
/**
|
||||
* The address of the asset being listed.
|
||||
*/
|
||||
assetContractAddress: AddressSchema,
|
||||
|
||||
/**
|
||||
* The ID of the token to list.
|
||||
*/
|
||||
tokenId: BigNumberishSchema,
|
||||
|
||||
/**
|
||||
* The quantity of tokens to include in the listing.
|
||||
*
|
||||
* For ERC721s, this value should always be 1 (and will be forced internally regardless of what is passed here).
|
||||
*/
|
||||
quantity: BigNumberishSchema.default(1),
|
||||
|
||||
/**
|
||||
* The address of the currency to accept for the listing.
|
||||
*/
|
||||
currencyContractAddress: AddressSchema.default(NATIVE_TOKEN_ADDRESS),
|
||||
|
||||
/**
|
||||
* The price to pay per unit of NFTs listed.
|
||||
*/
|
||||
pricePerToken: AmountSchema,
|
||||
|
||||
/**
|
||||
* The start time of the listing.
|
||||
*/
|
||||
startTimestamp: RawDateSchema.default(new Date()),
|
||||
|
||||
/**
|
||||
* The end time of the listing.
|
||||
*/
|
||||
endTimestamp: EndDateSchema,
|
||||
|
||||
/**
|
||||
* Whether the listing is reserved to be bought from a specific set of buyers.
|
||||
*/
|
||||
isReservedListing: z.boolean().default(false),
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type DirectListingInputParams = z.input<
|
||||
typeof DirectListingInputParamsSchema
|
||||
>;
|
||||
@@ -0,0 +1,79 @@
|
||||
import { AmountSchema } from "../../../core/schema/shared";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../constants";
|
||||
import {
|
||||
AddressSchema,
|
||||
BigNumberishSchema,
|
||||
EndDateSchema,
|
||||
RawDateSchema,
|
||||
} from "../shared";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const EnglishAuctionInputParamsSchema = z.object({
|
||||
/**
|
||||
* The address of the asset being auctioned.
|
||||
*/
|
||||
assetContractAddress: AddressSchema,
|
||||
|
||||
/**
|
||||
* The ID of the token to auction.
|
||||
*/
|
||||
tokenId: BigNumberishSchema,
|
||||
|
||||
/**
|
||||
* The quantity of tokens to include in the listing.
|
||||
*
|
||||
* For ERC721s, this value should always be 1 (and will be forced internally regardless of what is passed here).
|
||||
*/
|
||||
quantity: BigNumberishSchema.default(1),
|
||||
|
||||
/**
|
||||
* The address of the currency to accept for the listing.
|
||||
*/
|
||||
currencyContractAddress: AddressSchema.default(NATIVE_TOKEN_ADDRESS),
|
||||
|
||||
/**
|
||||
* The minimum price that a bid must be in order to be accepted.
|
||||
*/
|
||||
minimumBidAmount: AmountSchema,
|
||||
|
||||
/**
|
||||
* The buyout price of the auction.
|
||||
*/
|
||||
buyoutBidAmount: AmountSchema,
|
||||
|
||||
/**
|
||||
* This is a buffer e.g. x seconds.
|
||||
*
|
||||
* If a new winning bid is made less than x seconds before expirationTimestamp, the
|
||||
* expirationTimestamp is increased by x seconds.
|
||||
*/
|
||||
timeBufferInSeconds: BigNumberishSchema.default(900), // 15 minutes by default
|
||||
|
||||
/**
|
||||
* This is a buffer in basis points e.g. x%.
|
||||
*
|
||||
* To be considered as a new winning bid, a bid must be at least x% greater than
|
||||
* the current winning bid.
|
||||
*/
|
||||
bidBufferBps: BigNumberishSchema.default(500), // 5% by default
|
||||
|
||||
/**
|
||||
* The start time of the auction.
|
||||
*/
|
||||
startTimestamp: RawDateSchema.default(new Date()),
|
||||
|
||||
/**
|
||||
* The end time of the auction.
|
||||
*/
|
||||
endTimestamp: EndDateSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type EnglishAuctionInputParams = z.input<
|
||||
typeof EnglishAuctionInputParamsSchema
|
||||
>;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { AmountSchema } from "../../../core/schema/shared";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../constants";
|
||||
import { AddressSchema, BigNumberishSchema, EndDateSchema } from "../shared";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const OfferInputParamsSchema = z.object({
|
||||
/**
|
||||
* The address of the asset being sought.
|
||||
*/
|
||||
assetContractAddress: AddressSchema,
|
||||
|
||||
/**
|
||||
* The ID of the token.
|
||||
*/
|
||||
tokenId: BigNumberishSchema,
|
||||
|
||||
/**
|
||||
* The quantity of tokens to buy.
|
||||
*
|
||||
* For ERC721s, this value should always be 1 (and will be forced internally regardless of what is passed here).
|
||||
*/
|
||||
quantity: BigNumberishSchema.default(1),
|
||||
|
||||
/**
|
||||
* The address of the currency offered for the NFTs.
|
||||
*/
|
||||
currencyContractAddress: AddressSchema.default(NATIVE_TOKEN_ADDRESS),
|
||||
|
||||
/**
|
||||
* The total offer amount for the NFTs.
|
||||
*/
|
||||
totalPrice: AmountSchema,
|
||||
|
||||
/**
|
||||
* The end time of the offer.
|
||||
*/
|
||||
endTimestamp: EndDateSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type OfferInputParams = z.input<typeof OfferInputParamsSchema>;
|
||||
@@ -130,6 +130,41 @@ export interface MarketplaceContractDeployMetadata {
|
||||
platform_fee_basis_points?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for deploying a Marketplace-V3 contract
|
||||
* @public
|
||||
*/
|
||||
export interface MarketplaceV3ContractDeployMetadata {
|
||||
/**
|
||||
* name of the contract
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Optional description of the contract
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* Optional image for the contract
|
||||
*/
|
||||
image?: FileOrBufferOrString;
|
||||
/**
|
||||
* Optional url for the contract
|
||||
*/
|
||||
external_link?: string;
|
||||
/**
|
||||
* Custom gasless trusted forwarder addresses
|
||||
*/
|
||||
trusted_forwarders?: string[];
|
||||
/**
|
||||
* The address that will receive the proceeds from platform fees
|
||||
*/
|
||||
platform_fee_recipient?: string;
|
||||
/**
|
||||
* The percentage (in basis points) of platform fees
|
||||
*/
|
||||
platform_fee_basis_points?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for deploying a Vote contract
|
||||
* @public
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./claim-conditions/claim-conditions";
|
||||
export * from "./airdrop/airdrop";
|
||||
export * from "./marketplace";
|
||||
export * from "./marketplacev3";
|
||||
export * from "./currency";
|
||||
export * from "./delayed-reveal";
|
||||
export * from "./vote";
|
||||
|
||||
@@ -8,4 +8,5 @@ export interface MarketplaceFilter extends QueryAllParams {
|
||||
seller?: string;
|
||||
tokenContract?: string;
|
||||
tokenId?: BigNumberish;
|
||||
offeror?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { CurrencyValue } from "../currency";
|
||||
|
||||
export interface Bid {
|
||||
/**
|
||||
* The id of the auction.
|
||||
*/
|
||||
auctionId: string;
|
||||
|
||||
/**
|
||||
* The address of the buyer who made the offer.
|
||||
*/
|
||||
bidderAddress: string;
|
||||
|
||||
/**
|
||||
* The currency contract address of the offer token.
|
||||
*/
|
||||
currencyContractAddress: string;
|
||||
|
||||
/**
|
||||
* The amount of coins offered per token.
|
||||
*/
|
||||
bidAmount: string;
|
||||
|
||||
/**
|
||||
* The `CurrencyValue` of the listing. Useful for displaying the price information.
|
||||
*/
|
||||
bidAmountCurrencyValue: CurrencyValue;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { NFTMetadata } from "../../../core/schema/nft";
|
||||
import { Status } from "../../enums";
|
||||
import { CurrencyValue } from "../currency";
|
||||
|
||||
/**
|
||||
* Represents a marketplace direct listing.
|
||||
*/
|
||||
export interface DirectListingV3 {
|
||||
/**
|
||||
* The id of the listing.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The address of the creator of listing.
|
||||
*/
|
||||
creatorAddress: string;
|
||||
|
||||
/**
|
||||
* The address of the asset being listed.
|
||||
*/
|
||||
assetContractAddress: string;
|
||||
|
||||
/**
|
||||
* The ID of the token to list.
|
||||
*/
|
||||
tokenId: string;
|
||||
|
||||
/**
|
||||
* The quantity of tokens to include in the listing.
|
||||
*
|
||||
* For ERC721s, this value should always be 1 (and will be forced internally regardless of what is passed here).
|
||||
*/
|
||||
quantity: string;
|
||||
|
||||
/**
|
||||
* The address of the currency to accept for the listing.
|
||||
*/
|
||||
currencyContractAddress: string;
|
||||
|
||||
/**
|
||||
* The `CurrencyValue` of the listing. Useful for displaying the price information.
|
||||
*/
|
||||
currencyValuePerToken: CurrencyValue;
|
||||
|
||||
/**
|
||||
* The price to pay per unit of NFTs listed.
|
||||
*/
|
||||
pricePerToken: string;
|
||||
|
||||
/**
|
||||
* The asset being listed.
|
||||
*/
|
||||
asset: NFTMetadata;
|
||||
|
||||
/**
|
||||
* The start time of the listing.
|
||||
*/
|
||||
startTimeInSeconds: number;
|
||||
|
||||
/**
|
||||
* The end time of the listing.
|
||||
*/
|
||||
endTimeInSeconds: number;
|
||||
|
||||
/**
|
||||
* Whether the listing is reserved to be bought from a specific set of buyers.
|
||||
*/
|
||||
isReservedListing: boolean;
|
||||
|
||||
/**
|
||||
* Whether the listing is CREATED, COMPLETED, or CANCELLED.
|
||||
*/
|
||||
status: Status;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { NFTMetadata } from "../../../core/schema/nft";
|
||||
import { Status } from "../../enums";
|
||||
import { CurrencyValue } from "../currency";
|
||||
|
||||
/**
|
||||
* Represents a new marketplace english-auction.
|
||||
*/
|
||||
export interface EnglishAuction {
|
||||
/**
|
||||
* The id of the auction
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The address of the creator of auction.
|
||||
*/
|
||||
creatorAddress: string;
|
||||
|
||||
/**
|
||||
* The address of the asset being auctioned.
|
||||
*/
|
||||
assetContractAddress: string;
|
||||
|
||||
/**
|
||||
* The ID of the token to auction.
|
||||
*/
|
||||
tokenId: string;
|
||||
|
||||
/**
|
||||
* The quantity of tokens to include in the auction.
|
||||
*
|
||||
* For ERC721s, this value should always be 1 (and will be forced internally regardless of what is passed here).
|
||||
*/
|
||||
quantity: string;
|
||||
|
||||
/**
|
||||
* The address of the currency to accept for the auction.
|
||||
*/
|
||||
currencyContractAddress: string;
|
||||
|
||||
/**
|
||||
* The minimum price that a bid must be in order to be accepted.
|
||||
*/
|
||||
minimumBidAmount: string;
|
||||
|
||||
/**
|
||||
* The `CurrencyValue` of the minimum bid amount.
|
||||
* Useful for displaying the price information.
|
||||
*/
|
||||
minimumBidCurrencyValue: CurrencyValue;
|
||||
|
||||
/**
|
||||
* The buyout price of the auction.
|
||||
*/
|
||||
buyoutBidAmount: string;
|
||||
|
||||
/**
|
||||
* The `CurrencyValue` of the buyout price.
|
||||
* Useful for displaying the price information.
|
||||
*/
|
||||
buyoutCurrencyValue: CurrencyValue;
|
||||
|
||||
/**
|
||||
* This is a buffer e.g. x seconds.
|
||||
*
|
||||
* If a new winning bid is made less than x seconds before expirationTimestamp, the
|
||||
* expirationTimestamp is increased by x seconds.
|
||||
*/
|
||||
timeBufferInSeconds: number;
|
||||
|
||||
/**
|
||||
* This is a buffer in basis points e.g. x%.
|
||||
*
|
||||
* To be considered as a new winning bid, a bid must be at least x% greater than
|
||||
* the current winning bid.
|
||||
*/
|
||||
bidBufferBps: number;
|
||||
|
||||
/**
|
||||
* The start time of the auction.
|
||||
*/
|
||||
startTimeInSeconds: number;
|
||||
|
||||
/**
|
||||
* The end time of the auction.
|
||||
*/
|
||||
endTimeInSeconds: number;
|
||||
|
||||
/**
|
||||
* The asset being auctioned.
|
||||
*/
|
||||
asset: NFTMetadata;
|
||||
|
||||
/**
|
||||
* Whether the listing is CREATED, COMPLETED, or CANCELLED.
|
||||
*/
|
||||
status: Status;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { NFTMetadata } from "../../../core/schema/nft";
|
||||
import { Status } from "../../enums";
|
||||
import { CurrencyValue } from "../currency";
|
||||
|
||||
/**
|
||||
* Represents a marketplace offer.
|
||||
*/
|
||||
export interface OfferV3 {
|
||||
/**
|
||||
* The id of the offer.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The address of the creator of offer.
|
||||
*/
|
||||
offerorAddress: string;
|
||||
|
||||
/**
|
||||
* The address of the asset being sought.
|
||||
*/
|
||||
assetContractAddress: string;
|
||||
|
||||
/**
|
||||
* The ID of the token.
|
||||
*/
|
||||
tokenId: string;
|
||||
|
||||
/**
|
||||
* The quantity of tokens to buy.
|
||||
*
|
||||
* For ERC721s, this value should always be 1 (and will be forced internally regardless of what is passed here).
|
||||
*/
|
||||
quantity: string;
|
||||
|
||||
/**
|
||||
* The address of the currency offered for the NFTs.
|
||||
*/
|
||||
currencyContractAddress: string;
|
||||
|
||||
/**
|
||||
* The `CurrencyValue` of the listing. Useful for displaying the price information.
|
||||
*/
|
||||
currencyValue: CurrencyValue;
|
||||
|
||||
/**
|
||||
* The total offer amount for the NFTs.
|
||||
*/
|
||||
totalPrice: string;
|
||||
|
||||
/**
|
||||
* The asset to buy.
|
||||
*/
|
||||
asset: NFTMetadata;
|
||||
|
||||
/**
|
||||
* The end time of the offer.
|
||||
*/
|
||||
endTimeInSeconds: number;
|
||||
|
||||
/**
|
||||
* Whether the listing is CREATED, COMPLETED, or CANCELLED.
|
||||
*/
|
||||
status: Status;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./DirectListingV3";
|
||||
export * from "./EnglishAuction";
|
||||
export * from "./Bid";
|
||||
export * from "./OfferV3";
|
||||
@@ -33,6 +33,8 @@ import {
|
||||
Connection,
|
||||
ParsedAccountData,
|
||||
PublicKey,
|
||||
SignaturesForAddressOptions,
|
||||
TransactionResponse,
|
||||
} from "@solana/web3.js";
|
||||
|
||||
/**
|
||||
@@ -174,30 +176,42 @@ export class NFTHelper {
|
||||
return nfts;
|
||||
}
|
||||
|
||||
private async getAllMetadataAddresses(collectionAddress: string) {
|
||||
async getTransactions(
|
||||
collectionAddress: string,
|
||||
options?: SignaturesForAddressOptions,
|
||||
): Promise<TransactionResponse[]> {
|
||||
const collectionKey = new PublicKey(collectionAddress);
|
||||
|
||||
// TODO cache signatures <> transactions mapping in memory so pagination doesn't re-request this everytime
|
||||
const allSignatures: ConfirmedSignatureInfo[] = [];
|
||||
// This returns the first 1000, so we need to loop through until we run out of signatures to get.
|
||||
let signatures = await this.metaplex.connection.getSignaturesForAddress(
|
||||
collectionKey,
|
||||
);
|
||||
|
||||
allSignatures.push(...signatures);
|
||||
do {
|
||||
const options = {
|
||||
before: signatures[signatures.length - 1]?.signature,
|
||||
};
|
||||
signatures = await this.metaplex.connection.getSignaturesForAddress(
|
||||
collectionKey,
|
||||
options,
|
||||
if (options) {
|
||||
// only fetch the specified options
|
||||
allSignatures.push(
|
||||
...(await this.metaplex.connection.getSignaturesForAddress(
|
||||
collectionKey,
|
||||
options,
|
||||
)),
|
||||
);
|
||||
} else {
|
||||
// fetch everything
|
||||
// This returns the first 1000, so we need to loop through until we run out of signatures to get.
|
||||
let signatures = await this.metaplex.connection.getSignaturesForAddress(
|
||||
collectionKey,
|
||||
);
|
||||
|
||||
allSignatures.push(...signatures);
|
||||
} while (signatures.length > 0);
|
||||
|
||||
const metadataAddresses: PublicKey[] = [];
|
||||
do {
|
||||
signatures = await this.metaplex.connection.getSignaturesForAddress(
|
||||
collectionKey,
|
||||
{
|
||||
before: signatures[signatures.length - 1]?.signature,
|
||||
},
|
||||
);
|
||||
allSignatures.push(...signatures);
|
||||
} while (signatures.length > 0);
|
||||
}
|
||||
|
||||
let txns: TransactionResponse[] = [];
|
||||
// TODO RPC's will throttle this, need to do some optimizations here
|
||||
const batchSize = 1000; // alchemy RPC batch limit
|
||||
for (let i = 0; i < allSignatures.length; i += batchSize) {
|
||||
@@ -206,13 +220,25 @@ export class NFTHelper {
|
||||
Math.min(allSignatures.length, i + batchSize),
|
||||
);
|
||||
|
||||
const transactions = (
|
||||
await this.metaplex.connection.getTransactions(
|
||||
batch.map((s) => s.signature),
|
||||
)
|
||||
).reverse();
|
||||
txns = [
|
||||
...txns,
|
||||
...((
|
||||
await this.metaplex.connection.getTransactions(
|
||||
batch.map((s) => s.signature),
|
||||
)
|
||||
).filter((tx) => !!tx) as TransactionResponse[]),
|
||||
];
|
||||
}
|
||||
return txns;
|
||||
}
|
||||
|
||||
for (const tx of transactions) {
|
||||
async getAllMetadataAddresses(
|
||||
collectionAddress: string,
|
||||
): Promise<PublicKey[]> {
|
||||
const txns = (await this.getTransactions(collectionAddress)).reverse();
|
||||
|
||||
return txns
|
||||
.map((tx) => {
|
||||
if (tx) {
|
||||
const programIds = tx.transaction.message
|
||||
.programIds()
|
||||
@@ -230,9 +256,8 @@ export class NFTHelper {
|
||||
accountKeys[ix.programIdIndex] === METAPLEX_PROGRAM_ID
|
||||
) {
|
||||
const metadataAddressIndex = ix.accounts[0];
|
||||
const metadata_address =
|
||||
tx.transaction.message.accountKeys[metadataAddressIndex];
|
||||
metadataAddresses.push(metadata_address);
|
||||
|
||||
return tx.transaction.message.accountKeys[metadataAddressIndex];
|
||||
}
|
||||
}
|
||||
} else if (programIds.includes(CANDYMACHINE_PROGRAM_ID)) {
|
||||
@@ -243,17 +268,14 @@ export class NFTHelper {
|
||||
ix.data === "JEuNFGs7wrU"
|
||||
) {
|
||||
const metadataAddressIndex = ix.accounts[1];
|
||||
const metadata_address =
|
||||
tx.transaction.message.accountKeys[metadataAddressIndex];
|
||||
metadataAddresses.push(metadata_address);
|
||||
|
||||
return tx.transaction.message.accountKeys[metadataAddressIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metadataAddresses;
|
||||
})
|
||||
.filter((a) => !!a) as PublicKey[];
|
||||
}
|
||||
|
||||
async toNFTMetadata(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CurrencyValue } from "../../core/schema/token";
|
||||
import { WalletSigner } from "../types/common";
|
||||
import { toCurrencyValue } from "../utils/token";
|
||||
import { NFTHelper } from "./helpers/nft-helper";
|
||||
import {
|
||||
amount,
|
||||
guestIdentity,
|
||||
@@ -12,7 +13,12 @@ import {
|
||||
SOL,
|
||||
walletAdapterIdentity,
|
||||
} from "@metaplex-foundation/js";
|
||||
import { Keypair, PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
SignaturesForAddressOptions,
|
||||
TransactionResponse,
|
||||
} from "@solana/web3.js";
|
||||
import bs58 from "bs58";
|
||||
import EventEmitter from "eventemitter3";
|
||||
import invariant from "tiny-invariant";
|
||||
@@ -172,6 +178,20 @@ export class UserWallet {
|
||||
return toCurrencyValue(amount(value, SOL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the all transactions for this program
|
||||
* @beta
|
||||
*/
|
||||
async getTransactions(
|
||||
options?: SignaturesForAddressOptions,
|
||||
): Promise<TransactionResponse[]> {
|
||||
const helper = new NFTHelper(this.metaplex);
|
||||
return helper.getTransactions(
|
||||
this.metaplex.identity().publicKey.toBase58(),
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
private connectToMetaplex(signer: WalletSigner) {
|
||||
invariant(signer, "Wallet is not connected");
|
||||
const plugin = isKeypairSigner(signer)
|
||||
|
||||
@@ -18,7 +18,11 @@ import {
|
||||
toOriginalEditionAccount,
|
||||
TransactionBuilder,
|
||||
} from "@metaplex-foundation/js";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
PublicKey,
|
||||
SignaturesForAddressOptions,
|
||||
TransactionResponse,
|
||||
} from "@solana/web3.js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
@@ -140,6 +144,16 @@ export class NFTCollection {
|
||||
return this.nft.getAll(this.publicKey.toBase58(), queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the all transactions for this program
|
||||
* @beta
|
||||
*/
|
||||
async getTransactions(
|
||||
options?: SignaturesForAddressOptions,
|
||||
): Promise<TransactionResponse[]> {
|
||||
return this.nft.getTransactions(this.publicKey.toBase58(), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of nfts minted on this program
|
||||
* @returns the total number of NFTs minted on this program
|
||||
@@ -420,14 +434,39 @@ export class NFTCollection {
|
||||
* ```
|
||||
*/
|
||||
async burn(nftAddress: string): Promise<TransactionResult> {
|
||||
const tx = await this.metaplex.nfts().delete({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
collection: this.publicKey,
|
||||
});
|
||||
return this.burnBatch([nftAddress]).then((txs) => txs[0]);
|
||||
}
|
||||
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
/**
|
||||
* Burn multiple NFTs
|
||||
* @param nftAddresses - the mint addresses of the NFT to burn
|
||||
* @returns the transaction signature
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* // Specify the address of the NFT to burn
|
||||
* const nftAddress1 = "..."
|
||||
* const nftAddress2 = "..."
|
||||
* // And send the actual burn transaction
|
||||
* const tx = await program.burnBatch([nftAddress1, nftAddress2]);
|
||||
* ```
|
||||
*/
|
||||
async burnBatch(nftAddresses: string[]): Promise<TransactionResult[]> {
|
||||
const txs: TransactionBuilder[] = [];
|
||||
|
||||
for (let nftAddress of nftAddresses) {
|
||||
txs.push(
|
||||
this.metaplex
|
||||
.nfts()
|
||||
.builders()
|
||||
.delete({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
collection: this.publicKey,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return await sendMultipartTransaction(txs, this.metaplex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -472,7 +511,6 @@ export class NFTCollection {
|
||||
.nfts()
|
||||
.builders()
|
||||
.update({
|
||||
collection: this.publicKey,
|
||||
nftOrSft: metaplexNft,
|
||||
creators: enforceCreator(
|
||||
creators,
|
||||
@@ -530,7 +568,6 @@ export class NFTCollection {
|
||||
|
||||
txs.push(
|
||||
this.metaplex.nfts().builders().update({
|
||||
collection: this.publicKey,
|
||||
nftOrSft: metaplexNft,
|
||||
sellerFeeBasisPoints,
|
||||
}),
|
||||
|
||||
@@ -21,10 +21,16 @@ import { sendMultipartTransaction } from "../utils/transactions";
|
||||
import { getNework } from "../utils/urls";
|
||||
import {
|
||||
CandyMachineV2Item,
|
||||
findCandyMachineV2CreatorPda,
|
||||
Metaplex,
|
||||
toBigNumber,
|
||||
TransactionBuilder,
|
||||
} from "@metaplex-foundation/js";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
PublicKey,
|
||||
SignaturesForAddressOptions,
|
||||
TransactionResponse,
|
||||
} from "@solana/web3.js";
|
||||
import { ThirdwebStorage, UploadProgressEvent } from "@thirdweb-dev/storage";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
@@ -218,6 +224,16 @@ export class NFTDrop {
|
||||
return [...claimedNfts, ...unclaimedNfts];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the all transactions for this program
|
||||
* @beta
|
||||
*/
|
||||
async getTransactions(
|
||||
options?: SignaturesForAddressOptions,
|
||||
): Promise<TransactionResponse[]> {
|
||||
return this.nft.getTransactions(this.publicKey.toBase58(), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metadata for all the claimed NFTs on this drop
|
||||
* @returns metadata for all claimed NFTs
|
||||
@@ -493,47 +509,176 @@ export class NFTDrop {
|
||||
* ```
|
||||
*/
|
||||
async burn(nftAddress: string): Promise<TransactionResult> {
|
||||
return this.burnBatch([nftAddress]).then((txs) => txs[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Burn multiple NFTs
|
||||
* @param nftAddresses - the mint addresses of the NFT to burn
|
||||
* @returns the transaction signature
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* // Specify the address of the NFT to burn
|
||||
* const nftAddress1 = "..."
|
||||
* const nftAddress2 = "..."
|
||||
* // And send the actual burn transaction
|
||||
* const tx = await program.burnBatch([nftAddress1, nftAddress2]);
|
||||
* ```
|
||||
*/
|
||||
async burnBatch(nftAddresses: string[]): Promise<TransactionResult[]> {
|
||||
const candyMachine = await this.getCandyMachine();
|
||||
const collection = candyMachine.collectionMintAddress
|
||||
? candyMachine.collectionMintAddress
|
||||
: undefined;
|
||||
const tx = await this.metaplex.nfts().delete({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
collection,
|
||||
});
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
const txs: TransactionBuilder[] = [];
|
||||
|
||||
for (let nftAddress of nftAddresses) {
|
||||
txs.push(
|
||||
this.metaplex
|
||||
.nfts()
|
||||
.builders()
|
||||
.delete({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
collection,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return await sendMultipartTransaction(txs, this.metaplex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the creators of the collection
|
||||
* @param creators - the creators to update
|
||||
* @param updateAll - whether or not to retroactively update the creators of all past NFTs
|
||||
*/
|
||||
async updateCreators(creators: CreatorInput[]) {
|
||||
const tx = await this.metaplex.candyMachinesV2().update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
|
||||
});
|
||||
async updateCreators(
|
||||
creators: CreatorInput[],
|
||||
updateAll: boolean = false,
|
||||
): Promise<TransactionResult[]> {
|
||||
if (updateAll) {
|
||||
const txs: TransactionBuilder[] = [];
|
||||
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
const candyMachine = await this.getCandyMachine();
|
||||
const collectionAddress = candyMachine.collectionMintAddress;
|
||||
invariant(collectionAddress, "Collection mint address not found");
|
||||
|
||||
// We need to find the candy machine creator PDA which is auto populated onto NFTs
|
||||
// minted with the candy machine, and add it to the list of creators for each NFT
|
||||
const candyMachineCreatorPda = findCandyMachineV2CreatorPda(
|
||||
candyMachine.address,
|
||||
);
|
||||
const nftCreators = [
|
||||
{
|
||||
address: candyMachineCreatorPda.toBase58(),
|
||||
share: 0,
|
||||
},
|
||||
...creators,
|
||||
];
|
||||
|
||||
const allNfts = await this.nft.getAll(collectionAddress.toBase58());
|
||||
await Promise.all(
|
||||
allNfts.map(async (nft) => {
|
||||
const metaplexNft = await this.metaplex
|
||||
.nfts()
|
||||
.findByMint({ mintAddress: new PublicKey(nft.metadata.id) });
|
||||
|
||||
txs.push(
|
||||
this.metaplex
|
||||
.nfts()
|
||||
.builders()
|
||||
.update({
|
||||
nftOrSft: metaplexNft,
|
||||
creators: enforceCreator(
|
||||
nftCreators,
|
||||
this.metaplex.identity().publicKey,
|
||||
),
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const results = await sendMultipartTransaction(txs, this.metaplex);
|
||||
const tx = await this.metaplex.candyMachinesV2().update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
signature: tx.response.signature,
|
||||
},
|
||||
...results,
|
||||
];
|
||||
} else {
|
||||
const tx = await this.metaplex.candyMachinesV2().update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
signature: tx.response.signature,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the royalty basis points of the collection
|
||||
* @param sellerFeeBasisPoints - the royalty basis points of the collection
|
||||
* @param updateAll - whether or not to retroactively update the royalty basis points of all past NFTs
|
||||
*/
|
||||
async updateRoyalty(sellerFeeBasisPoints: number) {
|
||||
const tx = await this.metaplex.candyMachinesV2().update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
sellerFeeBasisPoints,
|
||||
});
|
||||
async updateRoyalty(
|
||||
sellerFeeBasisPoints: number,
|
||||
updateAll: boolean = false,
|
||||
) {
|
||||
if (updateAll) {
|
||||
const txs: TransactionBuilder[] = [];
|
||||
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
const candyMachine = await this.getCandyMachine();
|
||||
const collectionAddress = candyMachine.collectionMintAddress;
|
||||
invariant(collectionAddress, "Collection mint address not found");
|
||||
|
||||
const allNfts = await this.nft.getAll(collectionAddress.toBase58());
|
||||
await Promise.all(
|
||||
allNfts.map(async (nft) => {
|
||||
const metaplexNft = await this.metaplex
|
||||
.nfts()
|
||||
.findByMint({ mintAddress: new PublicKey(nft.metadata.id) });
|
||||
|
||||
txs.push(
|
||||
this.metaplex.nfts().builders().update({
|
||||
nftOrSft: metaplexNft,
|
||||
sellerFeeBasisPoints,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const results = await sendMultipartTransaction(txs, this.metaplex);
|
||||
const tx = await this.metaplex.candyMachinesV2().update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
sellerFeeBasisPoints,
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
signature: tx.response.signature,
|
||||
},
|
||||
...results,
|
||||
];
|
||||
} else {
|
||||
const tx = await this.metaplex.candyMachinesV2().update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
sellerFeeBasisPoints,
|
||||
});
|
||||
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async getCandyMachine() {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
NFTCollectionInitializer,
|
||||
NFTDropInitializer,
|
||||
PackInitializer,
|
||||
resolveContractUriFromAddress,
|
||||
SignatureDropInitializer,
|
||||
SplitInitializer,
|
||||
ThirdwebSDK,
|
||||
@@ -20,7 +21,8 @@ import {
|
||||
VoteInitializer,
|
||||
} from "../../src/evm";
|
||||
import { Plugin } from "../../src/evm/types/plugins";
|
||||
import { MockStorage } from "./mock/MockStorage";
|
||||
import { MockStorage, mockUploadWithCID } from "./mock/MockStorage";
|
||||
import weth from "./mock/WETH9.json";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
import {
|
||||
ContractPublisher,
|
||||
@@ -53,10 +55,17 @@ import {
|
||||
PluginMap,
|
||||
PluginMap__factory,
|
||||
VoteERC20__factory,
|
||||
MarketplaceV3__factory,
|
||||
DirectListingsLogic__factory,
|
||||
DirectListingsLogic,
|
||||
MarketplaceV3,
|
||||
EnglishAuctionsLogic__factory,
|
||||
EnglishAuctionsLogic,
|
||||
OffersLogic__factory,
|
||||
OffersLogic,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { ethers } from "ethers";
|
||||
import { FormatTypes, FunctionFragment, Interface } from "ethers/lib/utils";
|
||||
import hardhat from "hardhat";
|
||||
|
||||
// it's there, trust me bro
|
||||
@@ -74,6 +83,7 @@ let signer: SignerWithAddress;
|
||||
let signers: SignerWithAddress[];
|
||||
let storage: ThirdwebStorage;
|
||||
let implementations: { [key in ContractType]?: string };
|
||||
let mock_weth_address: string;
|
||||
|
||||
const fastForwardTime = async (timeInSeconds: number): Promise<void> => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
@@ -101,6 +111,15 @@ export const mochaHooks = {
|
||||
"0xc82BbE41f2cF04e3a8efA18F7032BDD7f6d98a81";
|
||||
await jsonProvider.send("hardhat_reset", []);
|
||||
|
||||
const mock_weth_deployer = new ethers.ContractFactory(
|
||||
weth.abi,
|
||||
weth.bytecode,
|
||||
)
|
||||
.connect(signer)
|
||||
.deploy();
|
||||
const mock_weth = await (await mock_weth_deployer).deployed();
|
||||
mock_weth_address = mock_weth.address;
|
||||
|
||||
const registry = (await new ethers.ContractFactory(
|
||||
TWRegistry__factory.abi,
|
||||
TWRegistry__factory.bytecode,
|
||||
@@ -164,7 +183,7 @@ export const mochaHooks = {
|
||||
}
|
||||
|
||||
for (const contractType in CONTRACTS_MAP) {
|
||||
if (contractType === "custom") {
|
||||
if (contractType === "custom" || contractType === "marketplace-v3") {
|
||||
continue;
|
||||
}
|
||||
let factories: any[] = [];
|
||||
@@ -230,6 +249,13 @@ export const mochaHooks = {
|
||||
}
|
||||
}
|
||||
|
||||
// setup marketplace-v3 and add implementation to factory
|
||||
const marketplaceEntrypointAddress = await setupMarketplaceV3();
|
||||
const tx = await thirdwebFactoryDeployer.addImplementation(
|
||||
marketplaceEntrypointAddress,
|
||||
);
|
||||
await tx.wait();
|
||||
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
process.env.registryAddress = thirdwebRegistryAddress;
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
@@ -254,6 +280,18 @@ export const mochaHooks = {
|
||||
},
|
||||
};
|
||||
|
||||
const getFunctionSignature = (fnInputs: any): string => {
|
||||
return (
|
||||
"(" +
|
||||
fnInputs
|
||||
.map((i) => {
|
||||
return i.type === "tuple" ? getFunctionSignature(i.components) : i.type;
|
||||
})
|
||||
.join(",") +
|
||||
")"
|
||||
);
|
||||
};
|
||||
|
||||
const generatePluginFunctions = (
|
||||
pluginAddress: string,
|
||||
pluginAbi: Abi,
|
||||
@@ -263,16 +301,46 @@ const generatePluginFunctions = (
|
||||
// TODO - filter out common functions like _msgSender(), contractType(), etc.
|
||||
for (const fnFragment of Object.values(pluginInterface.functions)) {
|
||||
const fn = pluginInterface.getFunction(fnFragment.name);
|
||||
if (fn.name.includes("_")) {
|
||||
continue;
|
||||
}
|
||||
pluginFunctions.push({
|
||||
functionSelector: pluginInterface.getSighash(fn),
|
||||
functionSignature:
|
||||
fn.name + "(" + fn.inputs.map((i) => i.type).join(",") + ")",
|
||||
functionSignature: fn.name + getFunctionSignature(fn.inputs),
|
||||
pluginAddress,
|
||||
});
|
||||
}
|
||||
return pluginFunctions;
|
||||
};
|
||||
|
||||
const mockUploadContractMetadata = async (address: string, abi: any) => {
|
||||
const ipfsHash = (await resolveContractUriFromAddress(
|
||||
address,
|
||||
defaultProvider,
|
||||
)) as string;
|
||||
|
||||
const metadata = {
|
||||
compiler: {},
|
||||
output: {
|
||||
abi,
|
||||
devdoc: {},
|
||||
userdoc: {},
|
||||
},
|
||||
settings: {
|
||||
compilationTarget: {},
|
||||
evmVersion: {},
|
||||
metadata: {},
|
||||
optimizer: {},
|
||||
remappings: [],
|
||||
},
|
||||
sources: {},
|
||||
version: 1,
|
||||
};
|
||||
const cid = ipfsHash.replace("ipfs://", "");
|
||||
|
||||
await mockUploadWithCID(cid, JSON.stringify(metadata));
|
||||
};
|
||||
|
||||
// Setup multichain registry for tests
|
||||
async function setupMultichainRegistry(
|
||||
trustedForwarderAddress: string,
|
||||
@@ -312,6 +380,93 @@ async function setupMultichainRegistry(
|
||||
return multichainRegistryRouter.address;
|
||||
}
|
||||
|
||||
// Setup marketplace-v3 for tests
|
||||
async function setupMarketplaceV3(): Promise<string> {
|
||||
const nativeTokenWrapperAddress = getNativeTokenByChainId(ChainId.Hardhat)
|
||||
.wrapped.address;
|
||||
// Direct Listings
|
||||
const directListingsLogicDeployer = (await new ethers.ContractFactory(
|
||||
DirectListingsLogic__factory.abi,
|
||||
DirectListingsLogic__factory.bytecode,
|
||||
)
|
||||
.connect(signer)
|
||||
.deploy(mock_weth_address)) as DirectListingsLogic;
|
||||
const directListingsLogic = await directListingsLogicDeployer.deployed();
|
||||
await mockUploadContractMetadata(
|
||||
directListingsLogic.address,
|
||||
DirectListingsLogic__factory.abi,
|
||||
);
|
||||
|
||||
const pluginsDirectListings: Plugin[] = generatePluginFunctions(
|
||||
directListingsLogic.address,
|
||||
DirectListingsLogic__factory.abi,
|
||||
);
|
||||
|
||||
// English Auctions
|
||||
const englishAuctionsLogicDeployer = (await new ethers.ContractFactory(
|
||||
EnglishAuctionsLogic__factory.abi,
|
||||
EnglishAuctionsLogic__factory.bytecode,
|
||||
)
|
||||
.connect(signer)
|
||||
.deploy(mock_weth_address)) as EnglishAuctionsLogic;
|
||||
const englishAuctionsLogic = await englishAuctionsLogicDeployer.deployed();
|
||||
await mockUploadContractMetadata(
|
||||
englishAuctionsLogic.address,
|
||||
EnglishAuctionsLogic__factory.abi,
|
||||
);
|
||||
|
||||
const pluginsEnglishAuctions: Plugin[] = generatePluginFunctions(
|
||||
englishAuctionsLogic.address,
|
||||
EnglishAuctionsLogic__factory.abi,
|
||||
);
|
||||
|
||||
// Offers
|
||||
const offersLogicDeployer = (await new ethers.ContractFactory(
|
||||
OffersLogic__factory.abi,
|
||||
OffersLogic__factory.bytecode,
|
||||
)
|
||||
.connect(signer)
|
||||
.deploy()) as OffersLogic;
|
||||
const offersLogic = await offersLogicDeployer.deployed();
|
||||
await mockUploadContractMetadata(
|
||||
offersLogic.address,
|
||||
OffersLogic__factory.abi,
|
||||
);
|
||||
|
||||
const pluginsOffers: Plugin[] = generatePluginFunctions(
|
||||
offersLogic.address,
|
||||
OffersLogic__factory.abi,
|
||||
);
|
||||
|
||||
// console.log([
|
||||
// ...pluginsDirectListings,
|
||||
// ...pluginsEnglishAuctions,
|
||||
// ...pluginsOffers,
|
||||
// ]);
|
||||
|
||||
const pluginMapDeployer = (await new ethers.ContractFactory(
|
||||
PluginMap__factory.abi,
|
||||
PluginMap__factory.bytecode,
|
||||
)
|
||||
.connect(signer)
|
||||
.deploy([
|
||||
...pluginsDirectListings,
|
||||
...pluginsEnglishAuctions,
|
||||
...pluginsOffers,
|
||||
])) as PluginMap;
|
||||
const pluginMap = await pluginMapDeployer.deployed();
|
||||
|
||||
const marketplaceV3Deployer = (await new ethers.ContractFactory(
|
||||
MarketplaceV3__factory.abi,
|
||||
MarketplaceV3__factory.bytecode,
|
||||
)
|
||||
.connect(signer)
|
||||
.deploy(pluginMap.address)) as MarketplaceV3;
|
||||
const marketplaceV3 = await marketplaceV3Deployer.deployed();
|
||||
|
||||
return marketplaceV3.address;
|
||||
}
|
||||
|
||||
export {
|
||||
ipfsGatewayUrl,
|
||||
sdk,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NFTMetadata, NFTMetadataInput } from "../../src/core/schema/nft";
|
||||
import {
|
||||
ClaimEligibility,
|
||||
EditionDrop,
|
||||
@@ -49,7 +50,7 @@ describe("Edition Drop Contract (V2)", async () => {
|
||||
platform_fee_basis_points: 10,
|
||||
platform_fee_recipient: adminWallet.address,
|
||||
},
|
||||
2,
|
||||
"2",
|
||||
);
|
||||
bdContract = await sdk.getEditionDrop(address);
|
||||
});
|
||||
@@ -60,10 +61,25 @@ describe("Edition Drop Contract (V2)", async () => {
|
||||
"mock://12398172398172389/0",
|
||||
]);
|
||||
expect(parseFloat(cost)).gt(0);
|
||||
|
||||
await bdContract.createBatch([{ name: `test$`, description: `desc` }]);
|
||||
await bdContract.claimConditions.set(0, [
|
||||
{
|
||||
price: 0.1,
|
||||
},
|
||||
]);
|
||||
const claimTx = await bdContract.getClaimTransaction(
|
||||
adminWallet.address,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
const cost2 = await claimTx.estimateGasCostInEther();
|
||||
console.log("cost2", cost2);
|
||||
expect(parseFloat(cost2)).gt(0);
|
||||
});
|
||||
|
||||
it("comprehensive test", async () => {
|
||||
const metadata = [];
|
||||
const metadata: NFTMetadataInput[] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
metadata.push({ name: `test${i}`, description: `desc${i}` });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { ThirdwebSDK } from "../../src/evm";
|
||||
import { expectError, sdk } from "./before-setup";
|
||||
import { expect } from "chai";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
describe("Error Handling", async () => {
|
||||
it("should throw proper error on account with no balance", async () => {
|
||||
const newSdk = ThirdwebSDK.fromSigner(
|
||||
ethers.Wallet.createRandom().connect(sdk.getProvider()),
|
||||
);
|
||||
|
||||
try {
|
||||
await newSdk.deployer.deployNFTCollection({
|
||||
name: "Should Fail Collection",
|
||||
primary_sale_recipient: "0x0000000000000000000000000000000000000000",
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err) {
|
||||
expectError(
|
||||
err,
|
||||
"You have insufficient funds in your account to execute this transaction.",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw !BALNFT revert message without gas limit", async () => {
|
||||
const collectionAddress = await sdk.deployer.deployNFTCollection({
|
||||
name: "Test Collection",
|
||||
primary_sale_recipient: "0x0000000000000000000000000000000000000000",
|
||||
});
|
||||
const marketplaceAddress = await sdk.deployer.deployMarketplace({
|
||||
name: "Test Marketplace",
|
||||
});
|
||||
|
||||
const collection = await sdk.getContract(collectionAddress);
|
||||
await collection.erc721.mintTo(marketplaceAddress, { name: "Test NFT" });
|
||||
|
||||
const marketplace = await sdk.getContract(marketplaceAddress);
|
||||
try {
|
||||
await marketplace.call("createListing", {
|
||||
assetContract: collectionAddress,
|
||||
tokenId: 0,
|
||||
startTime: 1000000000000,
|
||||
secondsUntilEndTime: 10000000000000,
|
||||
quantityToList: 1,
|
||||
currencyToAccept: "0x0000000000000000000000000000000000000000",
|
||||
reservePricePerToken: 0,
|
||||
buyoutPricePerToken: 0,
|
||||
listingType: 0,
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err) {
|
||||
// In this case, call static should go through before sendTransaction
|
||||
// so there should be no tx hash on the error
|
||||
expectError(err, "!BALNFT");
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(err.info.hash).to.be.undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Here we hit a different code path by passing gas limit, send transaction should go through
|
||||
it("should throw !BALNFT revert message without gas limit", async () => {
|
||||
const collectionAddress = await sdk.deployer.deployNFTCollection({
|
||||
name: "Test Collection",
|
||||
primary_sale_recipient: "0x0000000000000000000000000000000000000000",
|
||||
});
|
||||
const marketplaceAddress = await sdk.deployer.deployMarketplace({
|
||||
name: "Test Marketplace",
|
||||
});
|
||||
|
||||
const collection = await sdk.getContract(collectionAddress);
|
||||
await collection.erc721.mintTo(marketplaceAddress, { name: "Test NFT" });
|
||||
|
||||
const marketplace = await sdk.getContract(marketplaceAddress);
|
||||
try {
|
||||
await marketplace.call(
|
||||
"createListing",
|
||||
{
|
||||
assetContract: collectionAddress,
|
||||
tokenId: 0,
|
||||
startTime: 1000000000000,
|
||||
secondsUntilEndTime: 10000000000000,
|
||||
quantityToList: 1,
|
||||
currencyToAccept: "0x0000000000000000000000000000000000000000",
|
||||
reservePricePerToken: 0,
|
||||
buyoutPricePerToken: 0,
|
||||
listingType: 0,
|
||||
},
|
||||
{
|
||||
gasLimit: 800000,
|
||||
},
|
||||
);
|
||||
expect.fail();
|
||||
} catch (err) {
|
||||
// In this case, call static should go through after sendTransaction
|
||||
// so we should have a hash on the error
|
||||
expectError(err, "!BALNFT");
|
||||
expect(err.info.hash).to.be.a("string");
|
||||
expect(err.info.hash).to.satisfy(
|
||||
(hash: string) => hash.startsWith("0x") && hash.length === 66,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -62,8 +62,10 @@ describe("Marketplace Contract", async () => {
|
||||
{
|
||||
name: "Test Marketplace",
|
||||
},
|
||||
"2",
|
||||
),
|
||||
);
|
||||
|
||||
dummyNftContract = await sdk.getNFTCollection(
|
||||
await sdk.deployer.deployBuiltInContract(
|
||||
NFTCollectionInitializer.contractType,
|
||||
|
||||
@@ -2,13 +2,23 @@ import {
|
||||
MockUploader,
|
||||
MockDownloader,
|
||||
ThirdwebStorage,
|
||||
IpfsUploadBatchOptions,
|
||||
FileOrBufferOrString,
|
||||
} from "@thirdweb-dev/storage";
|
||||
|
||||
const storage = {};
|
||||
|
||||
export function MockStorage(): ThirdwebStorage {
|
||||
// Store mapping of URIs to files/objects
|
||||
const storage = {};
|
||||
|
||||
const uploader = new MockUploader(storage);
|
||||
const downloader = new MockDownloader(storage);
|
||||
return new ThirdwebStorage({ uploader, downloader });
|
||||
}
|
||||
|
||||
export async function mockUploadWithCID(
|
||||
cid: any,
|
||||
file: FileOrBufferOrString,
|
||||
options?: IpfsUploadBatchOptions | undefined,
|
||||
) {
|
||||
storage[cid] = file;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -40,6 +40,11 @@ describe("NFTCollection", async () => {
|
||||
expect(all[0].metadata.name).to.eq("Test NFT");
|
||||
});
|
||||
|
||||
it("should fetch transactions", async () => {
|
||||
const txns = await collection.getTransactions();
|
||||
expect(txns.length).to.eq(2);
|
||||
});
|
||||
|
||||
it("should fetch balance of NFTs", async () => {
|
||||
const all = await collection.getAll();
|
||||
const balance = await collection.balanceOf(
|
||||
@@ -148,14 +153,19 @@ describe("NFTCollection", async () => {
|
||||
expect(creators[0].address).to.equal(sdk.wallet.getAddress());
|
||||
expect(creators[0].share).to.equal(100);
|
||||
|
||||
const numNfts = (await collection.getAll()).length;
|
||||
|
||||
const newCreator = Keypair.generate().publicKey.toBase58();
|
||||
await collection.updateCreators([
|
||||
{
|
||||
address: sdk.wallet.getAddress() as string,
|
||||
share: 75,
|
||||
},
|
||||
{ address: newCreator, share: 25 },
|
||||
]);
|
||||
await collection.updateCreators(
|
||||
[
|
||||
{
|
||||
address: sdk.wallet.getAddress() as string,
|
||||
share: 75,
|
||||
},
|
||||
{ address: newCreator, share: 25 },
|
||||
],
|
||||
true,
|
||||
);
|
||||
|
||||
creators = await collection.getCreators();
|
||||
expect(creators.length).to.equal(2);
|
||||
@@ -164,36 +174,36 @@ describe("NFTCollection", async () => {
|
||||
expect(creators[1].address).to.equal(newCreator);
|
||||
expect(creators[1].share).to.equal(25);
|
||||
|
||||
// TODO (adam): fix this test
|
||||
// const all = await collection.getAll();
|
||||
// for (const nft of all) {
|
||||
// // @ts-ignore
|
||||
// const creatorsOfNft = await collection.nft.creatorsOf(nft.metadata.id);
|
||||
// expect(creatorsOfNft.length).to.equal(2);
|
||||
// expect(creatorsOfNft[0].address).to.equal(sdk.wallet.getAddress());
|
||||
// expect(creatorsOfNft[0].share).to.equal(75);
|
||||
// expect(creatorsOfNft[1].address).to.equal(newCreator);
|
||||
// expect(creatorsOfNft[1].share).to.equal(25);
|
||||
// }
|
||||
const all = await collection.getAll();
|
||||
expect(all.length).to.equal(numNfts);
|
||||
for (const nft of all) {
|
||||
// @ts-ignore
|
||||
const creatorsOfNft = await collection.nft.creatorsOf(nft.metadata.id);
|
||||
expect(creatorsOfNft.length).to.equal(2);
|
||||
expect(creatorsOfNft[0].address).to.equal(sdk.wallet.getAddress());
|
||||
expect(creatorsOfNft[0].share).to.equal(75);
|
||||
expect(creatorsOfNft[1].address).to.equal(newCreator);
|
||||
expect(creatorsOfNft[1].share).to.equal(25);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should update royalty", async () => {
|
||||
let royalty = await collection.getRoyalty();
|
||||
expect(royalty).to.equal(0);
|
||||
|
||||
const mintAddress = (await collection.getAll())[0].metadata.id;
|
||||
// @ts-ignore
|
||||
let nft = await collection.nft.getRaw(mintAddress);
|
||||
expect(nft.isMutable).to.equal(true);
|
||||
expect(nft.sellerFeeBasisPoints).to.equal(0);
|
||||
const numNfts = (await collection.getAll()).length;
|
||||
|
||||
await collection.updateRoyalty(100);
|
||||
await collection.updateRoyalty(100, true);
|
||||
|
||||
royalty = await collection.getRoyalty();
|
||||
expect(royalty).to.equal(100);
|
||||
|
||||
// TODO (adam): fix this test
|
||||
// nft = await collection.nft.getRaw(mintAddress);
|
||||
// expect(nft.sellerFeeBasisPoints).to.equal(100);
|
||||
const all = await collection.getAll();
|
||||
expect(all.length).to.equal(numNfts);
|
||||
for (const singleNft of all) {
|
||||
// @ts-ignore
|
||||
const royaltyOfNft = await collection.nft.getRaw(singleNft.metadata.id);
|
||||
expect(royaltyOfNft.sellerFeeBasisPoints).to.equal(100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,11 +131,20 @@ describe("NFTDrop", async () => {
|
||||
expect(creators[0].address).to.equal(sdk.wallet.getAddress());
|
||||
expect(creators[0].share).to.equal(100);
|
||||
|
||||
const numNfts = (await drop.getAllClaimed()).length;
|
||||
const newCreator = Keypair.generate().publicKey.toBase58();
|
||||
await drop.updateCreators([
|
||||
{ address: sdk.wallet.getAddress() as string, share: 75 },
|
||||
{ address: newCreator, share: 25 },
|
||||
]);
|
||||
|
||||
await drop.updateCreators(
|
||||
[
|
||||
{
|
||||
address: sdk.wallet.getAddress() as string,
|
||||
share: 75,
|
||||
verified: true,
|
||||
},
|
||||
{ address: newCreator, share: 25, verified: true },
|
||||
],
|
||||
true,
|
||||
);
|
||||
|
||||
creators = await drop.getCreators();
|
||||
expect(creators.length).to.equal(2);
|
||||
@@ -143,14 +152,39 @@ describe("NFTDrop", async () => {
|
||||
expect(creators[0].share).to.equal(75);
|
||||
expect(creators[1].address).to.equal(newCreator);
|
||||
expect(creators[1].share).to.equal(25);
|
||||
|
||||
const all = await drop.getAllClaimed();
|
||||
expect(all.length).to.equal(numNfts);
|
||||
for (const nft of all) {
|
||||
// @ts-ignore
|
||||
const creatorsOfNft = await drop.nft.creatorsOf(nft.metadata.id);
|
||||
expect(creatorsOfNft.length).to.equal(3);
|
||||
// Skip checking the first creator which is the candy machine creator pda
|
||||
expect(creatorsOfNft[1].address).to.equal(sdk.wallet.getAddress());
|
||||
expect(creatorsOfNft[1].share).to.equal(75);
|
||||
expect(creatorsOfNft[2].address).to.equal(newCreator);
|
||||
expect(creatorsOfNft[2].share).to.equal(25);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should update royalty", async () => {
|
||||
let royalty = await drop.getRoyalty();
|
||||
expect(royalty).to.equal(0);
|
||||
await drop.updateRoyalty(100);
|
||||
|
||||
const mintAddress = (await drop.getAll())[0].metadata.id;
|
||||
// @ts-ignore
|
||||
let nft = await drop.nft.getRaw(mintAddress);
|
||||
expect(nft.isMutable).to.equal(true);
|
||||
expect(nft.sellerFeeBasisPoints).to.equal(0);
|
||||
|
||||
await drop.updateRoyalty(100, true);
|
||||
|
||||
royalty = await drop.getRoyalty();
|
||||
expect(royalty).to.equal(100);
|
||||
|
||||
// @ts-ignore
|
||||
nft = await drop.nft.getRaw(mintAddress);
|
||||
expect(nft.sellerFeeBasisPoints).to.equal(100);
|
||||
});
|
||||
|
||||
it("Should get all correctly", async () => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/storage
|
||||
|
||||
## 1.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#474](https://github.com/thirdweb-dev/js/pull/474) [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Edit mock uploader, downloader
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/storage",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"main": "dist/thirdweb-dev-storage.cjs.js",
|
||||
"module": "dist/thirdweb-dev-storage.esm.js",
|
||||
"exports": {
|
||||
|
||||
@@ -17,8 +17,11 @@ export class MockDownloader implements IStorageDownloader {
|
||||
}
|
||||
|
||||
async download(url: string): Promise<Response> {
|
||||
const [cid, name] = url.replace("mock://", "").split("/");
|
||||
const data = this.storage[cid][name];
|
||||
const [cid, name] = url.includes("mock://")
|
||||
? url.replace("mock://", "").split("/")
|
||||
: url.replace("ipfs://", "").split("/");
|
||||
const data = name ? this.storage[cid][name] : this.storage[cid];
|
||||
|
||||
return {
|
||||
async json() {
|
||||
return Promise.resolve(JSON.parse(data));
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# @thirdweb-dev/unity-js-bridge
|
||||
|
||||
## 0.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`448b357`](https://github.com/thirdweb-dev/js/commit/448b357ed4b864e05e531837ca3bc97fbcab3291)]:
|
||||
- @thirdweb-dev/sdk@3.8.1
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0b38dac`](https://github.com/thirdweb-dev/js/commit/0b38dac852e54d0bccd263631f4342aee39d7c29), [`29d0c62`](https://github.com/thirdweb-dev/js/commit/29d0c62610cdb88f75d19574bddcb028b5c40393), [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c), [`f295ec6`](https://github.com/thirdweb-dev/js/commit/f295ec67b4cc422c51beda94cbc4d54a31ce9566), [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c), [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c), [`f1aecc3`](https://github.com/thirdweb-dev/js/commit/f1aecc30499e39fb23a205bade939bd939f0d0e4)]:
|
||||
- @thirdweb-dev/sdk@3.8.0
|
||||
- @thirdweb-dev/storage@1.0.7
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ec1f232`](https://github.com/thirdweb-dev/js/commit/ec1f2324c7a0e58c2d2a209cfe0d194807ba5b70), [`d29f7e7`](https://github.com/thirdweb-dev/js/commit/d29f7e78f09063630e812dd41164d8a41d4f97dd)]:
|
||||
- @thirdweb-dev/sdk@3.7.4
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b45d761`](https://github.com/thirdweb-dev/js/commit/b45d761f8f2695b48312ec6eff15e0549e6a8a41), [`5f8ec45`](https://github.com/thirdweb-dev/js/commit/5f8ec45af7c81d075a84846098f38cad90d03fb9), [`04de5b2`](https://github.com/thirdweb-dev/js/commit/04de5b278362573991140c3a496de7451e5c770d)]:
|
||||
- @thirdweb-dev/sdk@3.7.3
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`19b8e18`](https://github.com/thirdweb-dev/js/commit/19b8e18b6bf68e374c817fefbf2fd29ac5573052)]:
|
||||
- @thirdweb-dev/sdk@3.7.2
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/unity-js-bridge",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.6",
|
||||
"main": "dist/thirdweb-unity-bridge.js",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
+36
-121
@@ -2,51 +2,24 @@
|
||||
"$schema": "https://turborepo.org/schema.json",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"outputs": [
|
||||
"dist/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
"outputs": ["dist/**"],
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"@thirdweb-dev/wallets#build": {
|
||||
"outputs": [
|
||||
"dist/**",
|
||||
"evm/**",
|
||||
"solana/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
"outputs": ["dist/**", "evm/**", "solana/**"],
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"@thirdweb-dev/pay#build": {
|
||||
"outputs": [
|
||||
"dist/**",
|
||||
"integrations/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
"outputs": ["dist/**", "integrations/**"],
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"@thirdweb-dev/sdk#build": {
|
||||
"outputs": [
|
||||
"dist/**",
|
||||
"evm/**",
|
||||
"solana/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
"outputs": ["dist/**", "evm/**", "solana/**"],
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"@thirdweb-dev/react#build": {
|
||||
"outputs": [
|
||||
"dist/**",
|
||||
"evm/**",
|
||||
"solana/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
"outputs": ["dist/**", "evm/**", "solana/**"],
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"@thirdweb-dev/react-core#build": {
|
||||
"outputs": ["dist/**", "evm/**", "solana/**"],
|
||||
@@ -61,106 +34,52 @@
|
||||
"next/**",
|
||||
"next-auth/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"@thirdweb-dev/contracts-js#build": {
|
||||
"outputs": [
|
||||
"dist/**",
|
||||
"factories/**",
|
||||
"src/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
"outputs": ["dist/**", "factories/**", "src/**"],
|
||||
"inputs": ["package.json"],
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"test": {
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
"src/**",
|
||||
"test/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"inputs": ["src/**", "test/**"],
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"test:solana": {
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
"src/core/**",
|
||||
"src/solana/**",
|
||||
"test/solana/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"inputs": ["src/core/**", "src/solana/**", "test/solana/**"],
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"test:evm": {
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
"src/core/**",
|
||||
"src/evm/**",
|
||||
"test/evm/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"inputs": ["src/core/**", "src/evm/**", "test/evm/**"],
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"e2e": {
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
"src/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"inputs": ["src/**"],
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"e2e:e2e-cra-5": {
|
||||
"outputs": [
|
||||
"build"
|
||||
],
|
||||
"inputs": [
|
||||
"src/**",
|
||||
"fixtures/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"outputs": ["build"],
|
||||
"inputs": ["src/**", "fixtures/**"],
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"e2e:e2e-next-12": {
|
||||
"outputs": [
|
||||
".next"
|
||||
],
|
||||
"inputs": [
|
||||
"src/**",
|
||||
"fixtures/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"outputs": [".next"],
|
||||
"inputs": ["src/**", "fixtures/**"],
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"e2e:e2e-vite-3": {
|
||||
"outputs": [
|
||||
"dist"
|
||||
],
|
||||
"inputs": [
|
||||
"src/**",
|
||||
"fixtures/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"outputs": ["dist"],
|
||||
"inputs": ["src/**", "fixtures/**"],
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"thirdweb#e2e": {
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
"src/**",
|
||||
"e2e/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"inputs": ["src/**", "e2e/**"],
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"lint": {
|
||||
"outputs": []
|
||||
@@ -176,12 +95,8 @@
|
||||
},
|
||||
"push": {
|
||||
"cache": false,
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
"dependsOn": ["build"]
|
||||
}
|
||||
},
|
||||
"globalEnv": [
|
||||
"CI"
|
||||
]
|
||||
}
|
||||
"globalEnv": ["CI"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user