Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8516fc11f9 | ||
|
|
b4e7c74ce5 | ||
|
|
d6646475e2 | ||
|
|
4dbcbd13db | ||
|
|
4eb628363a | ||
|
|
381b669cd1 | ||
|
|
bebcf46c3a | ||
|
|
1b2bfc83d7 | ||
|
|
576bb579f6 | ||
|
|
23680d19cb | ||
|
|
94e354aad3 | ||
|
|
e18606e034 | ||
|
|
ca2576f818 | ||
|
|
ec35f32d2b |
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
daysUntilStale: 14
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 4.1.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/wallets@2.5.6
|
||||
|
||||
## 4.1.63
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/wallets@2.5.5
|
||||
|
||||
## 4.1.62
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "4.1.62",
|
||||
"version": "4.1.64",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"browser": {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @thirdweb-dev/chains
|
||||
|
||||
## 0.1.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2812](https://github.com/thirdweb-dev/js/pull/2812) [`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2802](https://github.com/thirdweb-dev/js/pull/2802) [`ca2576f`](https://github.com/thirdweb-dev/js/commit/ca2576f8180b6b5b9fe47087a53d1f13665c5028) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "IOTA EVM",
|
||||
"chainId": 1075,
|
||||
"explorers": [],
|
||||
"faucets": [
|
||||
"https://evm-toolkit.evm.testnet.iotaledger.net"
|
||||
],
|
||||
"icon": {
|
||||
"url": "ipfs://bafkreibky2sy6qhi6arktayvologkrgu5kudpgdxfkx4uosbvmstz7v4di",
|
||||
"width": 720,
|
||||
"height": 720,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://www.iota.org",
|
||||
"name": "IOTA EVM Testnet",
|
||||
"nativeCurrency": {
|
||||
"name": "IOTA",
|
||||
"symbol": "IOTA",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 1075,
|
||||
"rpc": [
|
||||
"https://1075.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://json-rpc.evm.testnet.iotaledger.net"
|
||||
],
|
||||
"shortName": "iotaevm-testnet",
|
||||
"slug": "iota-evm-testnet",
|
||||
"testnet": true,
|
||||
"title": "IOTA EVM Testnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "Cyber",
|
||||
"chainId": 111557560,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "Cyber Testnet Explorer",
|
||||
"url": "https://testnet.cyberscan.co",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
|
||||
"width": 551,
|
||||
"height": 540,
|
||||
"format": "png"
|
||||
}
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"icon": {
|
||||
"url": "ipfs://QmP61yDNPv7fxY9ZzPs4CjQDbZLoKtF8eWWjszVYbwkabd",
|
||||
"width": 1000,
|
||||
"height": 1000,
|
||||
"format": "svg"
|
||||
},
|
||||
"infoURL": "https://cyber.co/",
|
||||
"name": "Cyber Testnet",
|
||||
"nativeCurrency": {
|
||||
"name": "Sepolia Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 111557560,
|
||||
"parent": {
|
||||
"type": "L2",
|
||||
"chain": "eip155-11155111",
|
||||
"bridges": [
|
||||
{
|
||||
"url": "https://op-bridge.alt.technology/deposit?id=111557560"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rpc": [
|
||||
"https://111557560.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://cyber-testnet.alt.technology/",
|
||||
"wss://cyber-testnet.alt.technology/ws"
|
||||
],
|
||||
"shortName": "cysep",
|
||||
"slug": "cyber-testnet",
|
||||
"testnet": true
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "MOS",
|
||||
"chainId": 1188,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "mosscan",
|
||||
"url": "https://www.mosscan.com",
|
||||
"standard": "none",
|
||||
"icon": {
|
||||
"url": "ipfs://QmczQf61mZmg3NmmGnwseDxzH1AqapA252KCTT85GuNG96",
|
||||
"width": 51,
|
||||
"height": 50,
|
||||
"format": "png"
|
||||
}
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"icon": {
|
||||
"url": "ipfs://QmczQf61mZmg3NmmGnwseDxzH1AqapA252KCTT85GuNG96",
|
||||
"width": 51,
|
||||
"height": 50,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://www.mosscan.com",
|
||||
"name": "ClubMos Mainnet",
|
||||
"nativeCurrency": {
|
||||
"name": "ClubMos",
|
||||
"symbol": "MOS",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 1188,
|
||||
"rpc": [
|
||||
"https://1188.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet.mosscan.com"
|
||||
],
|
||||
"shortName": "MOS",
|
||||
"slug": "clubmos",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "ETH",
|
||||
"chainId": 1221,
|
||||
"explorers": [],
|
||||
"faucets": [],
|
||||
"icon": {
|
||||
"url": "ipfs://QmeC4mKGbBpwmLfFxUSsareWkSWJomevUmex8ajjxo1zHx",
|
||||
"width": 500,
|
||||
"height": 500,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://www.cyclenetwork.io/",
|
||||
"name": "Cycle Network Testnet",
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 1221,
|
||||
"rpc": [
|
||||
"https://1221.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc-testnet.cyclenetwork.io"
|
||||
],
|
||||
"shortName": "Cycle",
|
||||
"slug": "cycle-network-testnet",
|
||||
"testnet": true
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "MagApe",
|
||||
"chainId": 141319,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "etherscan",
|
||||
"url": "http://testnet-api.magape.io:81",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmPsxWMvnxnZ9dZcRJJMaE24o34dQsnsAMCJh3kVDrE1aB",
|
||||
"width": 1000,
|
||||
"height": 1000,
|
||||
"format": "png"
|
||||
}
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"features": [
|
||||
{
|
||||
"name": "EIP155"
|
||||
}
|
||||
],
|
||||
"icon": {
|
||||
"url": "ipfs://QmPsxWMvnxnZ9dZcRJJMaE24o34dQsnsAMCJh3kVDrE1aB",
|
||||
"width": 1000,
|
||||
"height": 1000,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://magape.io",
|
||||
"name": "MagApe Testnet",
|
||||
"nativeCurrency": {
|
||||
"name": "MagApe",
|
||||
"symbol": "MAG",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 141319,
|
||||
"rpc": [
|
||||
"https://141319.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://testnet-api.magape.io/chain/"
|
||||
],
|
||||
"shortName": "mag",
|
||||
"slug": "magape-testnet",
|
||||
"testnet": true,
|
||||
"title": "MagApeChain"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "ETH",
|
||||
"chainId": 167000,
|
||||
"explorers": [],
|
||||
"faucets": [],
|
||||
"icon": {
|
||||
"url": "ipfs://QmcHdmVr5VRUJq13jnM6tgah5Ge7hn3Dm14eY6vwivJ5ui",
|
||||
"width": 288,
|
||||
"height": 258,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://taiko.xyz",
|
||||
"name": "Taiko Mainnet",
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 167000,
|
||||
"rpc": [
|
||||
"https://167000.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.taiko.xyz",
|
||||
"wss://ws.taiko.xyz"
|
||||
],
|
||||
"shortName": "tko-mainnet",
|
||||
"slug": "taiko",
|
||||
"status": "incubating",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
"rpc": [
|
||||
"https://167008.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.katla.taiko.xyz",
|
||||
"wss://rpc.katla.taiko.xyz",
|
||||
"wss://ws.katla.taiko.xyz",
|
||||
"https://taiko-katla.drpc.org",
|
||||
"wss://taiko-katla.drpc.org"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "Nexis Network",
|
||||
"chainId": 2370,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "Nexis Testnet Explorer",
|
||||
"url": "https://evm-testnet.nexscan.io",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"faucets": [
|
||||
"https://evm-faucet.nexis.network"
|
||||
],
|
||||
"icon": {
|
||||
"url": "ipfs://QmdJwWuMgbhoZhgFM4zWrZne6qs5ktcL1vBLKX12VGgALM",
|
||||
"width": 1892,
|
||||
"height": 1892,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://nexis.network/",
|
||||
"name": "Nexis Network Testnet",
|
||||
"nativeCurrency": {
|
||||
"name": "Nexis",
|
||||
"symbol": "NZT",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 2370,
|
||||
"rpc": [
|
||||
"https://2370.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://evm-testnet.nexis.network"
|
||||
],
|
||||
"shortName": "nzt",
|
||||
"slug": "nexis-network-testnet",
|
||||
"testnet": true
|
||||
} as const satisfies Chain;
|
||||
@@ -2,8 +2,33 @@ import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "NEURA",
|
||||
"chainId": 267,
|
||||
"explorers": [],
|
||||
"faucets": [],
|
||||
"explorers": [
|
||||
{
|
||||
"name": "blockscout",
|
||||
"url": "https://explorer.neura-testnet.ankr.com",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
|
||||
"width": 551,
|
||||
"height": 540,
|
||||
"format": "png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ankrscan-neura",
|
||||
"url": "https://testnet.explorer.neuraprotocol.io",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmWdpK5WFKrosaCSpJRCvU7uXeKk2qVeCckTxh6Zw2JrK8",
|
||||
"width": 600,
|
||||
"height": 600,
|
||||
"format": "png"
|
||||
}
|
||||
}
|
||||
],
|
||||
"faucets": [
|
||||
"https://testnet.neuraprotocol.io/faucet"
|
||||
],
|
||||
"features": [
|
||||
{
|
||||
"name": "EIP155"
|
||||
@@ -26,11 +51,14 @@ export default {
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 267,
|
||||
"rpc": [],
|
||||
"rpc": [
|
||||
"https://267.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.ankr.com/neura_testnet"
|
||||
],
|
||||
"shortName": "tneura",
|
||||
"slip44": 1,
|
||||
"slug": "neura-testnet",
|
||||
"status": "incubating",
|
||||
"status": "active",
|
||||
"testnet": true,
|
||||
"title": "Neura Testnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "ETH",
|
||||
"chainId": 2810,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "Morph Holesky Testnet Explorer",
|
||||
"url": "https://explorer-holesky.morphl2.io",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"infoURL": "https://morphl2.io",
|
||||
"name": "Morph Holesky",
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 2810,
|
||||
"parent": {
|
||||
"type": "L2",
|
||||
"chain": "eip155-1",
|
||||
"bridges": [
|
||||
{
|
||||
"url": "https://bridge-holesky.morphl2.io"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rpc": [
|
||||
"https://2810.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc-holesky.morphl2.io"
|
||||
],
|
||||
"shortName": "hmorph",
|
||||
"slip44": 1,
|
||||
"slug": "morph-holesky",
|
||||
"testnet": true,
|
||||
"title": "Morph Holesky Testnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "Avalanche",
|
||||
"chainId": 58316,
|
||||
"explorers": [],
|
||||
"faucets": [],
|
||||
"features": [],
|
||||
"icon": {
|
||||
"url": "https://images.ctfassets.net/9bazykntljf6/62CceHSYsRS4D9fgDSkLRB/877cb8f26954e1743ff535fd7fdaf78f/avacloud-placeholder.svg",
|
||||
"width": 256,
|
||||
"height": 256,
|
||||
"format": "svg"
|
||||
},
|
||||
"infoURL": "https://avacloud.io",
|
||||
"name": "QaUser1943 Testnet",
|
||||
"nativeCurrency": {
|
||||
"name": "QaUser1943 Testnet Token",
|
||||
"symbol": "JAI",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 58316,
|
||||
"redFlags": [],
|
||||
"rpc": [
|
||||
"https://58316.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://subnets.avax.network/defi-kingdoms/dfk-chain/rpc"
|
||||
],
|
||||
"shortName": "QaUser1943 Testnet",
|
||||
"slug": "qauser1943-testnet",
|
||||
"testnet": true
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "Planq",
|
||||
"chainId": 7077,
|
||||
"explorers": [],
|
||||
"faucets": [],
|
||||
"icon": {
|
||||
"url": "ipfs://QmWEy9xK5BoqxPuVs7T48WM4exJrxzkEFt45iHcxWqUy8D",
|
||||
"width": 256,
|
||||
"height": 256,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://planq.network",
|
||||
"name": "Planq Atlas Testnet",
|
||||
"nativeCurrency": {
|
||||
"name": "Planq",
|
||||
"symbol": "tPLQ",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 7077,
|
||||
"rpc": [
|
||||
"https://7077.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://evm-rpc-atlas.planq.network"
|
||||
],
|
||||
"shortName": "planq-atlas-testnet",
|
||||
"slug": "planq-atlas-testnet",
|
||||
"testnet": true
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "Cyber",
|
||||
"chainId": 7560,
|
||||
"explorers": [],
|
||||
"faucets": [],
|
||||
"icon": {
|
||||
"url": "ipfs://QmP61yDNPv7fxY9ZzPs4CjQDbZLoKtF8eWWjszVYbwkabd",
|
||||
"width": 1000,
|
||||
"height": 1000,
|
||||
"format": "svg"
|
||||
},
|
||||
"infoURL": "https://cyber.co/",
|
||||
"name": "Cyber Mainnet",
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 7560,
|
||||
"parent": {
|
||||
"type": "L2",
|
||||
"chain": "eip155-1"
|
||||
},
|
||||
"rpc": [],
|
||||
"shortName": "cyeth",
|
||||
"slug": "cyber",
|
||||
"status": "incubating",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "Avalanche",
|
||||
"chainId": 79490,
|
||||
"explorers": [],
|
||||
"faucets": [],
|
||||
"features": [],
|
||||
"icon": {
|
||||
"url": "https://images.ctfassets.net/9bazykntljf6/62CceHSYsRS4D9fgDSkLRB/877cb8f26954e1743ff535fd7fdaf78f/avacloud-placeholder.svg",
|
||||
"width": 256,
|
||||
"height": 256,
|
||||
"format": "svg"
|
||||
},
|
||||
"infoURL": "https://avacloud.io",
|
||||
"name": "QaUser2672 Testnet",
|
||||
"nativeCurrency": {
|
||||
"name": "QaUser2672 Testnet Token",
|
||||
"symbol": "ENU",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 79490,
|
||||
"redFlags": [],
|
||||
"rpc": [
|
||||
"https://79490.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://subnets.avax.network/defi-kingdoms/dfk-chain/rpc"
|
||||
],
|
||||
"shortName": "QaUser2672 Testnet",
|
||||
"slug": "qauser2672-testnet",
|
||||
"testnet": true
|
||||
} as const satisfies Chain;
|
||||
@@ -11,22 +11,22 @@ export default {
|
||||
"url": "https://explorerl2new-camp-network-4xje7wy105.t.conduit.xyz",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
|
||||
"width": 83,
|
||||
"height": 82,
|
||||
"format": "svg"
|
||||
"url": "ipfs://Qmd5ux27W44fjxHP2opz7eLhJ6CJJm9WR6VcKNhbQBxiSd/QOyzwbO2_400x400.jpg",
|
||||
"width": 400,
|
||||
"height": 400,
|
||||
"format": "jpg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"features": [],
|
||||
"icon": {
|
||||
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
|
||||
"width": 551,
|
||||
"height": 540,
|
||||
"format": "png"
|
||||
"url": "ipfs://Qmd5ux27W44fjxHP2opz7eLhJ6CJJm9WR6VcKNhbQBxiSd/QOyzwbO2_400x400.jpg",
|
||||
"width": 400,
|
||||
"height": 400,
|
||||
"format": "jpg"
|
||||
},
|
||||
"name": "Camp Network",
|
||||
"name": "Camp Network Testnet",
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
@@ -38,7 +38,7 @@ export default {
|
||||
"https://90354.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc-camp-network-4xje7wy105.t.conduit.xyz/"
|
||||
],
|
||||
"shortName": "campaign-l2",
|
||||
"slug": "camp-network",
|
||||
"testnet": false
|
||||
"shortName": "campaign-l2-test",
|
||||
"slug": "camp-network-testnet",
|
||||
"testnet": true
|
||||
} as const satisfies Chain;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/chains",
|
||||
"version": "0.1.95",
|
||||
"version": "0.1.97",
|
||||
"main": "dist/thirdweb-dev-chains.cjs.js",
|
||||
"module": "dist/thirdweb-dev-chains.esm.js",
|
||||
"exports": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,25 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.13.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
|
||||
- @thirdweb-dev/chains@0.1.97
|
||||
- @thirdweb-dev/sdk@4.0.66
|
||||
- @thirdweb-dev/wallets@2.5.6
|
||||
- @thirdweb-dev/auth@4.1.64
|
||||
|
||||
## 0.13.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ca2576f`](https://github.com/thirdweb-dev/js/commit/ca2576f8180b6b5b9fe47087a53d1f13665c5028)]:
|
||||
- @thirdweb-dev/chains@0.1.96
|
||||
- @thirdweb-dev/sdk@4.0.65
|
||||
- @thirdweb-dev/wallets@2.5.5
|
||||
- @thirdweb-dev/auth@4.1.63
|
||||
|
||||
## 0.13.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/cli",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.13.70",
|
||||
"version": "0.13.72",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @thirdweb-dev/react-core
|
||||
|
||||
## 4.6.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
|
||||
- @thirdweb-dev/chains@0.1.97
|
||||
- @thirdweb-dev/sdk@4.0.66
|
||||
- @thirdweb-dev/wallets@2.5.6
|
||||
- @thirdweb-dev/auth@4.1.64
|
||||
|
||||
## 4.6.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ca2576f`](https://github.com/thirdweb-dev/js/commit/ca2576f8180b6b5b9fe47087a53d1f13665c5028)]:
|
||||
- @thirdweb-dev/chains@0.1.96
|
||||
- @thirdweb-dev/sdk@4.0.65
|
||||
- @thirdweb-dev/wallets@2.5.5
|
||||
- @thirdweb-dev/auth@4.1.63
|
||||
|
||||
## 4.6.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-core",
|
||||
"version": "4.6.3",
|
||||
"version": "4.6.5",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react-core",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @thirdweb-dev/react-native-compat
|
||||
|
||||
## 0.7.37
|
||||
|
||||
## 0.7.36
|
||||
|
||||
## 0.7.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native-compat",
|
||||
"description": "Shims for Thirdweb in React Native Projects",
|
||||
"version": "0.7.35",
|
||||
"version": "0.7.37",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react-native-compat",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @thirdweb-dev/react-native
|
||||
|
||||
## 0.7.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
|
||||
- @thirdweb-dev/chains@0.1.97
|
||||
- @thirdweb-dev/sdk@4.0.66
|
||||
- @thirdweb-dev/react-core@4.6.5
|
||||
- @thirdweb-dev/wallets@2.5.6
|
||||
|
||||
## 0.7.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ca2576f`](https://github.com/thirdweb-dev/js/commit/ca2576f8180b6b5b9fe47087a53d1f13665c5028)]:
|
||||
- @thirdweb-dev/chains@0.1.96
|
||||
- @thirdweb-dev/sdk@4.0.65
|
||||
- @thirdweb-dev/react-core@4.6.4
|
||||
- @thirdweb-dev/wallets@2.5.5
|
||||
|
||||
## 0.7.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native",
|
||||
"version": "0.7.35",
|
||||
"version": "0.7.37",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react-native",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 4.6.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
|
||||
- @thirdweb-dev/chains@0.1.97
|
||||
- @thirdweb-dev/sdk@4.0.66
|
||||
- @thirdweb-dev/react-core@4.6.5
|
||||
- @thirdweb-dev/wallets@2.5.6
|
||||
|
||||
## 4.6.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ca2576f`](https://github.com/thirdweb-dev/js/commit/ca2576f8180b6b5b9fe47087a53d1f13665c5028)]:
|
||||
- @thirdweb-dev/chains@0.1.96
|
||||
- @thirdweb-dev/sdk@4.0.65
|
||||
- @thirdweb-dev/react-core@4.6.4
|
||||
- @thirdweb-dev/wallets@2.5.5
|
||||
|
||||
## 4.6.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "4.6.3",
|
||||
"version": "4.6.5",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @thirdweb-dev/sdk
|
||||
|
||||
## 4.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4dbcbd1`](https://github.com/thirdweb-dev/js/commit/4dbcbd13dbd388454993aee7e67b43bb9d9249aa), [`d664647`](https://github.com/thirdweb-dev/js/commit/d6646475e2715c666316174e1c035a035a075ab1), [`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
|
||||
- thirdweb@5.9.0
|
||||
- @thirdweb-dev/chains@0.1.97
|
||||
|
||||
## 4.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`381b669`](https://github.com/thirdweb-dev/js/commit/381b669cd1b4c621519636211f47a6910ca048f0), [`94e354a`](https://github.com/thirdweb-dev/js/commit/94e354aad30fc94b4d4c379e01810b0f4e2087bf), [`bebcf46`](https://github.com/thirdweb-dev/js/commit/bebcf46c3a900fcbff6d544b18c7f0140c5ca9be), [`1b2bfc8`](https://github.com/thirdweb-dev/js/commit/1b2bfc83d786d1c7b7d1fefa480dbab559e84c54), [`ca2576f`](https://github.com/thirdweb-dev/js/commit/ca2576f8180b6b5b9fe47087a53d1f13665c5028)]:
|
||||
- thirdweb@5.8.0
|
||||
- @thirdweb-dev/chains@0.1.96
|
||||
|
||||
## 4.0.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "4.0.64",
|
||||
"version": "4.0.66",
|
||||
"description": "The main thirdweb SDK.",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/sdk",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# @thirdweb-dev/unity-js-bridge
|
||||
|
||||
## 0.6.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
|
||||
- @thirdweb-dev/chains@0.1.97
|
||||
- @thirdweb-dev/sdk@4.0.66
|
||||
- @thirdweb-dev/wallets@2.5.6
|
||||
- @thirdweb-dev/auth@4.1.64
|
||||
|
||||
## 0.6.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2805](https://github.com/thirdweb-dev/js/pull/2805) [`ec35f32`](https://github.com/thirdweb-dev/js/commit/ec35f32d2baf5a8ff70531fc07a6b1ad57164db7) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - Unity 4.12.1
|
||||
|
||||
- Updated dependencies [[`ca2576f`](https://github.com/thirdweb-dev/js/commit/ca2576f8180b6b5b9fe47087a53d1f13665c5028)]:
|
||||
- @thirdweb-dev/chains@0.1.96
|
||||
- @thirdweb-dev/sdk@4.0.65
|
||||
- @thirdweb-dev/wallets@2.5.5
|
||||
- @thirdweb-dev/auth@4.1.63
|
||||
|
||||
## 0.6.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/unity-js-bridge",
|
||||
"version": "0.6.13",
|
||||
"version": "0.6.15",
|
||||
"main": "dist/thirdweb-unity-bridge.js",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -171,7 +171,7 @@ class ThirdwebBridge implements TWBridge {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix use of any
|
||||
(globalThis as any).X_SDK_PLATFORM = "unity";
|
||||
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix use of any
|
||||
(globalThis as any).X_SDK_VERSION = "4.12.0";
|
||||
(globalThis as any).X_SDK_VERSION = "4.12.1";
|
||||
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix use of any
|
||||
(globalThis as any).X_SDK_OS = browser?.os ?? "unknown";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @thirdweb-dev/wallets
|
||||
|
||||
## 2.5.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
|
||||
- @thirdweb-dev/chains@0.1.97
|
||||
- @thirdweb-dev/sdk@4.0.66
|
||||
|
||||
## 2.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ca2576f`](https://github.com/thirdweb-dev/js/commit/ca2576f8180b6b5b9fe47087a53d1f13665c5028)]:
|
||||
- @thirdweb-dev/chains@0.1.96
|
||||
- @thirdweb-dev/sdk@4.0.65
|
||||
|
||||
## 2.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/wallets",
|
||||
"version": "2.5.4",
|
||||
"version": "2.5.6",
|
||||
"main": "dist/thirdweb-dev-wallets.cjs.js",
|
||||
"module": "dist/thirdweb-dev-wallets.esm.js",
|
||||
"browser": {
|
||||
|
||||
@@ -1,5 +1,61 @@
|
||||
# thirdweb
|
||||
|
||||
## 5.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#2793](https://github.com/thirdweb-dev/js/pull/2793) [`4dbcbd1`](https://github.com/thirdweb-dev/js/commit/4dbcbd13dbd388454993aee7e67b43bb9d9249aa) Thanks [@jnsdls](https://github.com/jnsdls)! - **thirdweb/react**
|
||||
|
||||
Add a new optional `auth` prop to `<ConnectButton />` to allow specifying a SIWE auth flow after users connect their wallets.
|
||||
|
||||
```jsx
|
||||
<ConnectButton
|
||||
client={client}
|
||||
auth={{
|
||||
isLoggedIn: async (address) => {
|
||||
// check if the user is logged in by calling your server, etc.
|
||||
// then return a boolean value
|
||||
return true || false;
|
||||
},
|
||||
getLoginPayload: async ({ address, chainId }) => {
|
||||
// send the address (and optional chainId) to your server to generate the login payload for the user to sign
|
||||
// you can use the `generatePayload` function from `thirdweb/auth` to generate the payload
|
||||
// once you have retrieved the payload return it from this function
|
||||
|
||||
return; // <the login payload here>
|
||||
},
|
||||
doLogin: async (loginParams) => {
|
||||
// send the login params to your server where you can validate them using the `verifyPayload` function
|
||||
// from `thirdweb/auth`
|
||||
// you can then set a cookie or return a token to save in local storage, etc
|
||||
// `isLoggedIn` will automatically get called again after this function resolves
|
||||
},
|
||||
doLogout: async () => {
|
||||
// do anything you need to do such as clearing cookies, etc when the user should be logged out
|
||||
// `isLoggedIn` will automatically get called again after this function resolves
|
||||
},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2816](https://github.com/thirdweb-dev/js/pull/2816) [`d664647`](https://github.com/thirdweb-dev/js/commit/d6646475e2715c666316174e1c035a035a075ab1) Thanks [@jnsdls](https://github.com/jnsdls)! - adapters(viem): explicitly type walletClient return type
|
||||
|
||||
## 5.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#2806](https://github.com/thirdweb-dev/js/pull/2806) [`94e354a`](https://github.com/thirdweb-dev/js/commit/94e354aad30fc94b4d4c379e01810b0f4e2087bf) Thanks [@jnsdls](https://github.com/jnsdls)! - Implement `Offers` in the Marketplace extensions, available via `thirdweb/extensions/marketplace`.
|
||||
|
||||
- [#2814](https://github.com/thirdweb-dev/js/pull/2814) [`bebcf46`](https://github.com/thirdweb-dev/js/commit/bebcf46c3a900fcbff6d544b18c7f0140c5ca9be) Thanks [@jnsdls](https://github.com/jnsdls)! - Add support for updating metadata for ERC1155 contracts
|
||||
|
||||
- [#2811](https://github.com/thirdweb-dev/js/pull/2811) [`1b2bfc8`](https://github.com/thirdweb-dev/js/commit/1b2bfc83d786d1c7b7d1fefa480dbab559e84c54) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Added ERC1155 and ERC20 signature mint extensions
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2815](https://github.com/thirdweb-dev/js/pull/2815) [`381b669`](https://github.com/thirdweb-dev/js/commit/381b669cd1b4c621519636211f47a6910ca048f0) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix query invalidation when using react hooks
|
||||
|
||||
## 5.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"version": "5.7.0",
|
||||
"version": "5.9.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thirdweb-dev/js.git#main"
|
||||
@@ -226,7 +226,7 @@
|
||||
"push": "yalc push",
|
||||
"size": "size-limit",
|
||||
"test:watch": "vitest -c ./test/vitest.config.ts dev",
|
||||
"test": "vitest run -c ./test/vitest.config.ts",
|
||||
"test": "vitest run -c ./test/vitest.config.ts --coverage",
|
||||
"test:cov": "vitest dev -c ./test/vitest.config.ts --coverage",
|
||||
"test:dev": "vitest run -c ./test/vitest.config.ts",
|
||||
"typedoc": "node scripts/typedoc.mjs",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[
|
||||
"event TokensMintedWithSignature(address indexed signer, address indexed mintedTo, uint256 indexed tokenIdMinted, (address to, address royaltyRecipient, uint256 royaltyBps, address primarySaleRecipient, uint256 tokenId, string uri, uint256 quantity, uint256 pricePerToken, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) mintRequest)",
|
||||
"function mintWithSignature((address to, address royaltyRecipient, uint256 royaltyBps, address primarySaleRecipient, uint256 tokenId, string uri, uint256 quantity, uint256 pricePerToken, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) req, bytes signature) payable returns (address signer)",
|
||||
"function mintWithSignature((address to, address royaltyRecipient, uint256 royaltyBps, address primarySaleRecipient, uint256 tokenId, string uri, uint256 quantity, uint256 pricePerToken, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) payload, bytes signature) payable returns (address signer)",
|
||||
"function verify((address to, address royaltyRecipient, uint256 royaltyBps, address primarySaleRecipient, uint256 tokenId, string uri, uint256 quantity, uint256 pricePerToken, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) req, bytes signature) view returns (bool success, address signer)"
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[
|
||||
"event TokensMintedWithSignature(address indexed signer, address indexed mintedTo, (address to, address primarySaleRecipient, uint256 quantity, uint256 price, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) mintRequest)",
|
||||
"function mintWithSignature((address to, address primarySaleRecipient, uint256 quantity, uint256 price, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) req, bytes signature) payable returns (address signer)",
|
||||
"function mintWithSignature((address to, address primarySaleRecipient, uint256 quantity, uint256 price, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) payload, bytes signature) payable returns (address signer)",
|
||||
"function verify((address to, address primarySaleRecipient, uint256 quantity, uint256 price, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) req, bytes signature) view returns (bool success, address signer)"
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"event NewOffer(address indexed offeror, uint256 indexed offerId, address indexed assetContract, (uint256 offerId, uint256 tokenId, uint256 quantity, uint256 totalPrice, uint256 expirationTimestamp, address offeror, address assetContract, address currency, uint8 tokenType, uint8 status) offer)",
|
||||
"function acceptOffer(uint256 _offerId)",
|
||||
"function cancelOffer(uint256 _offerId)",
|
||||
"function totalOffers() view returns (uint256)",
|
||||
"function getAllOffers(uint256 _startId, uint256 _endId) view returns ((uint256 offerId, uint256 tokenId, uint256 quantity, uint256 totalPrice, uint256 expirationTimestamp, address offeror, address assetContract, address currency, uint8 tokenType, uint8 status)[] offers)",
|
||||
"function getAllValidOffers(uint256 _startId, uint256 _endId) view returns ((uint256 offerId, uint256 tokenId, uint256 quantity, uint256 totalPrice, uint256 expirationTimestamp, address offeror, address assetContract, address currency, uint8 tokenType, uint8 status)[] offers)",
|
||||
"function getOffer(uint256 _offerId) view returns ((uint256 offerId, uint256 tokenId, uint256 quantity, uint256 totalPrice, uint256 expirationTimestamp, address offeror, address assetContract, address currency, uint8 tokenType, uint8 status) offer)",
|
||||
|
||||
@@ -170,7 +170,7 @@ type ToViemWalletClientOptions = {
|
||||
chain: Chain;
|
||||
};
|
||||
|
||||
function toViemWalletClient(options: ToViemWalletClientOptions) {
|
||||
function toViemWalletClient(options: ToViemWalletClientOptions): WalletClient {
|
||||
const { account, chain, client } = options;
|
||||
if (!account) {
|
||||
throw new Error("Wallet not connected.");
|
||||
|
||||
@@ -68,6 +68,11 @@ export {
|
||||
mintAdditionalSupplyTo,
|
||||
type MintAdditionalSupplyToParams,
|
||||
} from "../../extensions/erc1155/write/mintAdditionalSupplyTo.js";
|
||||
export {
|
||||
setTokenURI,
|
||||
type SetTokenURIParams,
|
||||
} from "../../extensions/erc1155/__generated__/INFTMetadata/write/setTokenURI.js";
|
||||
export { freezeMetadata } from "../../extensions/erc1155/__generated__/INFTMetadata/write/freezeMetadata.js";
|
||||
|
||||
// EVENTS
|
||||
|
||||
@@ -83,6 +88,9 @@ export {
|
||||
approvalForAllEvent,
|
||||
type ApprovalForAllEventFilters,
|
||||
} from "../../extensions/erc1155/__generated__/IERC1155/events/ApprovalForAll.js";
|
||||
export { metadataUpdateEvent } from "../../extensions/erc1155/__generated__/INFTMetadata/events/MetadataUpdate.js";
|
||||
export { metadataFrozenEvent } from "../../extensions/erc1155/__generated__/INFTMetadata/events/MetadataFrozen.js";
|
||||
export { batchMetadataUpdateEvent } from "../../extensions/erc1155/__generated__/INFTMetadata/events/BatchMetadataUpdate.js";
|
||||
|
||||
/**
|
||||
* DROPS extension for ERC1155
|
||||
@@ -103,6 +111,15 @@ export {
|
||||
type SetClaimConditionsParams,
|
||||
} from "../../extensions/erc1155/drops/write/setClaimConditions.js";
|
||||
|
||||
/**
|
||||
* SIGNATURE extension for ERC1155
|
||||
*/
|
||||
export {
|
||||
mintWithSignature,
|
||||
type GenerateMintSignatureOptions,
|
||||
generateMintSignature,
|
||||
} from "../../extensions/erc1155/write/sigMint.js";
|
||||
|
||||
// EVENTS
|
||||
export { tokensLazyMintedEvent } from "../../extensions/erc1155/__generated__/ILazyMint/events/TokensLazyMinted.js";
|
||||
export { tokensClaimedEvent } from "../../extensions/erc1155/__generated__/IDrop1155/events/TokensClaimed.js";
|
||||
|
||||
@@ -77,3 +77,24 @@ export {
|
||||
setClaimConditions,
|
||||
type SetClaimConditionsParams,
|
||||
} from "../../extensions/erc20/drops/write/setClaimConditions.js";
|
||||
|
||||
/**
|
||||
* SIGNATURE extension for ERC20
|
||||
*/
|
||||
export {
|
||||
mintWithSignature,
|
||||
type GenerateMintSignatureOptions,
|
||||
generateMintSignature,
|
||||
} from "../../extensions/erc20/write/sigMint.js";
|
||||
|
||||
// ----------------------------
|
||||
// WETH
|
||||
// ----------------------------
|
||||
export {
|
||||
deposit,
|
||||
type DepositParams,
|
||||
} from "../../extensions/erc20/write/deposit.js";
|
||||
export {
|
||||
withdraw,
|
||||
type WithdrawParams,
|
||||
} from "../../extensions/erc20/__generated__/IWETH/write/withdraw.js";
|
||||
|
||||
@@ -1,34 +1,27 @@
|
||||
// ----------------------------
|
||||
// COMMON
|
||||
// ----------------------------
|
||||
|
||||
export type {
|
||||
DirectListing,
|
||||
ListingStatus,
|
||||
} from "../../extensions/marketplace/types.js";
|
||||
|
||||
// ----------------------------
|
||||
// DIRECT LISTINGS
|
||||
// ----------------------------
|
||||
|
||||
export type { DirectListing } from "../../extensions/marketplace/direct-listings/types.js";
|
||||
|
||||
// READ
|
||||
export { totalListings } from "../../extensions/marketplace/__generated__/IDirectListings/read/totalListings.js";
|
||||
export {
|
||||
getListing,
|
||||
type GetListingParams,
|
||||
} from "../../extensions/marketplace/read/direct/getListing.js";
|
||||
} from "../../extensions/marketplace/direct-listings/read/getListing.js";
|
||||
export {
|
||||
getAllListings,
|
||||
type GetAllListingParams,
|
||||
} from "../../extensions/marketplace/read/direct/getAllListings.js";
|
||||
} from "../../extensions/marketplace/direct-listings/read/getAllListings.js";
|
||||
export {
|
||||
getAllValidListings,
|
||||
type GetAllValidListingParams,
|
||||
} from "../../extensions/marketplace/read/direct/getAllValidListings.js";
|
||||
} from "../../extensions/marketplace/direct-listings/read/getAllValidListings.js";
|
||||
export {
|
||||
isBuyerApprovedForListing,
|
||||
type IsBuyerApprovedForListingParams,
|
||||
} from "../../extensions/marketplace/read/direct/isBuyerApprovedForListing.js";
|
||||
} from "../../extensions/marketplace/direct-listings/read/isBuyerApprovedForListing.js";
|
||||
export {
|
||||
isCurrencyApprovedForListing,
|
||||
type IsCurrencyApprovedForListingParams,
|
||||
@@ -36,17 +29,17 @@ export {
|
||||
export {
|
||||
currencyPriceForListing,
|
||||
type CurrencyPriceForListingParams,
|
||||
} from "../../extensions/marketplace/read/direct/currencyPriceForListing.js";
|
||||
} from "../../extensions/marketplace/direct-listings/read/currencyPriceForListing.js";
|
||||
|
||||
// WRITE
|
||||
export {
|
||||
createListing,
|
||||
type CreateListingParams,
|
||||
} from "../../extensions/marketplace/write/direct/createListing.js";
|
||||
} from "../../extensions/marketplace/direct-listings/write/createListing.js";
|
||||
export {
|
||||
updateListing,
|
||||
type UpdateListingParams,
|
||||
} from "../../extensions/marketplace/write/direct/updateListing.js";
|
||||
} from "../../extensions/marketplace/direct-listings/write/updateListing.js";
|
||||
export {
|
||||
cancelListing,
|
||||
type CancelListingParams,
|
||||
@@ -54,7 +47,7 @@ export {
|
||||
export {
|
||||
buyFromListing,
|
||||
type BuyFromListingParams,
|
||||
} from "../../extensions/marketplace/write/direct/buyFromListing.js";
|
||||
} from "../../extensions/marketplace/direct-listings/write/buyFromListing.js";
|
||||
export {
|
||||
approveBuyerForListing,
|
||||
type ApproveBuyerForListingParams,
|
||||
@@ -94,24 +87,26 @@ export {
|
||||
// ENGLISH AUCTIONS
|
||||
// ----------------------------
|
||||
|
||||
export type { EnglishAuction } from "../../extensions/marketplace/english-auctions/types.js";
|
||||
|
||||
// READ
|
||||
export { totalAuctions } from "../../extensions/marketplace/__generated__/IEnglishAuctions/read/totalAuctions.js";
|
||||
export {
|
||||
getAuction,
|
||||
type GetAuctionParams,
|
||||
} from "../../extensions/marketplace/read/english-auction/getAuction.js";
|
||||
} from "../../extensions/marketplace/english-auctions/read/getAuction.js";
|
||||
export {
|
||||
getAllAuctions,
|
||||
type GetAllAuctionParams,
|
||||
} from "../../extensions/marketplace/read/english-auction/getAllAuctions.js";
|
||||
} from "../../extensions/marketplace/english-auctions/read/getAllAuctions.js";
|
||||
export {
|
||||
getAllValidAuctions,
|
||||
type GetAllValidAuctionParams,
|
||||
} from "../../extensions/marketplace/read/english-auction/getAllValidAuctions.js";
|
||||
} from "../../extensions/marketplace/english-auctions/read/getAllValidAuctions.js";
|
||||
export {
|
||||
getWinningBid,
|
||||
type GetWinningBidParams,
|
||||
} from "../../extensions/marketplace/read/english-auction/getWinningBid.js";
|
||||
} from "../../extensions/marketplace/english-auctions/read/getWinningBid.js";
|
||||
export {
|
||||
isNewWinningBid,
|
||||
type IsNewWinningBidParams,
|
||||
@@ -121,11 +116,11 @@ export {
|
||||
export {
|
||||
createAuction,
|
||||
type CreateAuctionParams,
|
||||
} from "../../extensions/marketplace/write/english-auction/createAuction.js";
|
||||
} from "../../extensions/marketplace/english-auctions/write/createAuction.js";
|
||||
export {
|
||||
bidInAuction,
|
||||
type BidInAuctionParams,
|
||||
} from "../../extensions/marketplace/write/english-auction/bidInAuction.js";
|
||||
} from "../../extensions/marketplace/english-auctions/write/bidInAuction.js";
|
||||
export {
|
||||
cancelAuction,
|
||||
type CancelAuctionParams,
|
||||
@@ -133,7 +128,7 @@ export {
|
||||
export {
|
||||
buyoutAuction,
|
||||
type BuyoutAuctionParams,
|
||||
} from "../../extensions/marketplace/write/english-auction/buyoutAuction.js";
|
||||
} from "../../extensions/marketplace/english-auctions/write/buyoutAuction.js";
|
||||
export {
|
||||
collectAuctionPayout,
|
||||
type CollectAuctionPayoutParams,
|
||||
@@ -145,7 +140,7 @@ export {
|
||||
export {
|
||||
executeSale,
|
||||
type ExecuteSaleParams,
|
||||
} from "../../extensions/marketplace/write/english-auction/executeSale.js";
|
||||
} from "../../extensions/marketplace/english-auctions/write/executeSale.js";
|
||||
|
||||
// EVENTS
|
||||
export {
|
||||
@@ -164,3 +159,52 @@ export {
|
||||
newBidEvent,
|
||||
type NewBidEventFilters,
|
||||
} from "../../extensions/marketplace/__generated__/IEnglishAuctions/events/NewBid.js";
|
||||
|
||||
// ----------------------------
|
||||
// OFFERS
|
||||
// ----------------------------
|
||||
|
||||
export type { Offer } from "../../extensions/marketplace/offers/types.js";
|
||||
|
||||
// READ
|
||||
export { totalOffers } from "../../extensions/marketplace/__generated__/IOffers/read/totalOffers.js";
|
||||
export {
|
||||
getAllOffers,
|
||||
type GetAllOffersParams,
|
||||
} from "../../extensions/marketplace/offers/read/getAllOffers.js";
|
||||
export {
|
||||
getAllValidOffers,
|
||||
type GetAllValidOffersParams,
|
||||
} from "../../extensions/marketplace/offers/read/getAllValidOffers.js";
|
||||
export {
|
||||
getOffer,
|
||||
type GetOfferParams,
|
||||
} from "../../extensions/marketplace/offers/read/getOffer.js";
|
||||
|
||||
// WRITE
|
||||
export {
|
||||
makeOffer,
|
||||
type MakeOfferParams,
|
||||
} from "../../extensions/marketplace/offers/write/makeOffer.js";
|
||||
export {
|
||||
cancelOffer,
|
||||
type CancelOfferParams,
|
||||
} from "../../extensions/marketplace/__generated__/IOffers/write/cancelOffer.js";
|
||||
export {
|
||||
acceptOffer,
|
||||
type AcceptOfferParams,
|
||||
} from "../../extensions/marketplace/offers/write/acceptOffer.js";
|
||||
|
||||
// EVENTS
|
||||
export {
|
||||
acceptedOfferEvent,
|
||||
type AcceptedOfferEventFilters,
|
||||
} from "../../extensions/marketplace/__generated__/IOffers/events/AcceptedOffer.js";
|
||||
export {
|
||||
cancelledOfferEvent,
|
||||
type CancelledOfferEventFilters,
|
||||
} from "../../extensions/marketplace/__generated__/IOffers/events/CancelledOffer.js";
|
||||
export {
|
||||
newOfferEvent,
|
||||
type NewOfferEventFilters,
|
||||
} from "../../extensions/marketplace/__generated__/IOffers/events/NewOffer.js";
|
||||
|
||||
@@ -96,3 +96,6 @@ export {
|
||||
AutoConnect,
|
||||
type AutoConnectProps,
|
||||
} from "../react/core/hooks/connection/useAutoConnect.js";
|
||||
|
||||
// auth
|
||||
export { type SiweAuthOptions } from "../react/core/hooks/auth/useSiweAuth.js";
|
||||
|
||||
+8
-8
@@ -13,9 +13,9 @@ import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js";
|
||||
* Represents the parameters for the "mintWithSignature" function.
|
||||
*/
|
||||
export type MintWithSignatureParams = WithOverrides<{
|
||||
req: AbiParameterToPrimitiveType<{
|
||||
payload: AbiParameterToPrimitiveType<{
|
||||
type: "tuple";
|
||||
name: "req";
|
||||
name: "payload";
|
||||
components: [
|
||||
{ type: "address"; name: "to" },
|
||||
{ type: "address"; name: "royaltyRecipient" },
|
||||
@@ -38,7 +38,7 @@ export const FN_SELECTOR = "0x98a6e993" as const;
|
||||
const FN_INPUTS = [
|
||||
{
|
||||
type: "tuple",
|
||||
name: "req",
|
||||
name: "payload",
|
||||
components: [
|
||||
{
|
||||
type: "address",
|
||||
@@ -132,7 +132,7 @@ export async function isMintWithSignatureSupported(
|
||||
* ```ts
|
||||
* import { encodeMintWithSignatureParams } "thirdweb/extensions/erc1155";
|
||||
* const result = encodeMintWithSignatureParams({
|
||||
* req: ...,
|
||||
* payload: ...,
|
||||
* signature: ...,
|
||||
* });
|
||||
* ```
|
||||
@@ -140,7 +140,7 @@ export async function isMintWithSignatureSupported(
|
||||
export function encodeMintWithSignatureParams(
|
||||
options: MintWithSignatureParams,
|
||||
) {
|
||||
return encodeAbiParameters(FN_INPUTS, [options.req, options.signature]);
|
||||
return encodeAbiParameters(FN_INPUTS, [options.payload, options.signature]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +152,7 @@ export function encodeMintWithSignatureParams(
|
||||
* ```ts
|
||||
* import { encodeMintWithSignature } "thirdweb/extensions/erc1155";
|
||||
* const result = encodeMintWithSignature({
|
||||
* req: ...,
|
||||
* payload: ...,
|
||||
* signature: ...,
|
||||
* });
|
||||
* ```
|
||||
@@ -177,7 +177,7 @@ export function encodeMintWithSignature(options: MintWithSignatureParams) {
|
||||
*
|
||||
* const transaction = mintWithSignature({
|
||||
* contract,
|
||||
* req: ...,
|
||||
* payload: ...,
|
||||
* signature: ...,
|
||||
* });
|
||||
*
|
||||
@@ -203,7 +203,7 @@ export function mintWithSignature(
|
||||
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const,
|
||||
params: async () => {
|
||||
const resolvedOptions = await asyncOptions();
|
||||
return [resolvedOptions.req, resolvedOptions.signature] as const;
|
||||
return [resolvedOptions.payload, resolvedOptions.signature] as const;
|
||||
},
|
||||
value: async () => (await asyncOptions()).overrides?.value,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../test/src/test-wallets.js";
|
||||
import { type ThirdwebContract, getContract } from "../../contract/contract.js";
|
||||
import { sendAndConfirmTransaction } from "../../exports/transaction.js";
|
||||
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { getContractMetadata } from "../common/read/getContractMetadata.js";
|
||||
import { deployERC1155Contract } from "../prebuilts/deploy-erc1155.js";
|
||||
import { balanceOf } from "./__generated__/IERC1155/read/balanceOf.js";
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../test/src/test-wallets.js";
|
||||
import { type ThirdwebContract, getContract } from "../../contract/contract.js";
|
||||
import { sendAndConfirmTransaction } from "../../exports/transaction.js";
|
||||
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { getContractMetadata } from "../common/read/getContractMetadata.js";
|
||||
import { deployERC1155Contract } from "../prebuilts/deploy-erc1155.js";
|
||||
import { balanceOf } from "./__generated__/IERC1155/read/balanceOf.js";
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import type { AbiParameterToPrimitiveType, Address } from "abitype";
|
||||
import { maxUint256 } from "viem";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
|
||||
import type { ThirdwebContract } from "../../../contract/contract.js";
|
||||
import { toBigInt } from "../../../utils/bigint.js";
|
||||
import { dateToSeconds, tenYearsFromNow } from "../../../utils/date.js";
|
||||
import type { Hex } from "../../../utils/encoding/hex.js";
|
||||
import type { NFTInput } from "../../../utils/nft/parseNft.js";
|
||||
import { randomBytes32 } from "../../../utils/uuid.js";
|
||||
import type { Account } from "../../../wallets/interfaces/wallet.js";
|
||||
import { mintWithSignature as generatedMintWithSignature } from "../__generated__/ISignatureMintERC1155/write/mintWithSignature.js";
|
||||
|
||||
/**
|
||||
* Mints a new ERC1155 token with the given minter signature
|
||||
* @param options - The transaction options.
|
||||
* @example
|
||||
* ```ts
|
||||
* import { mintWithSignature, generateMintSignature } from "thirdweb/extensions/erc1155";
|
||||
*
|
||||
* const { payload, signature } = await generateMintSignature(...)
|
||||
*
|
||||
* const transaction = mintWithSignature({
|
||||
* contract,
|
||||
* payload,
|
||||
* signature,
|
||||
* });
|
||||
* await sendTransaction({ transaction, account });
|
||||
* ```
|
||||
* @extension ERC1155
|
||||
* @returns A promise that resolves to the transaction result.
|
||||
*/
|
||||
export const mintWithSignature = generatedMintWithSignature;
|
||||
|
||||
export type GenerateMintSignatureOptions = {
|
||||
account: Account;
|
||||
contract: ThirdwebContract;
|
||||
mintRequest: GeneratePayloadInput;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the payload and signature for minting an ERC1155 token.
|
||||
* @param options - The options for the minting process.
|
||||
* @example
|
||||
* ```ts
|
||||
* import { mintWithSignature, generateMintSignature } from "thirdweb/extensions/erc1155";
|
||||
*
|
||||
* const { payload, signature } = await generateMintSignature({
|
||||
* account,
|
||||
* contract,
|
||||
* mintRequest: {
|
||||
* to: "0x...",
|
||||
* quantity: 10n,
|
||||
* metadata: {
|
||||
* name: "My NFT",
|
||||
* description: "This is my NFT",
|
||||
* image: "https://example.com/image.png",
|
||||
* },
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* const transaction = mintWithSignature({
|
||||
* contract,
|
||||
* payload,
|
||||
* signature,
|
||||
* });
|
||||
* await sendTransaction({ transaction, account });
|
||||
* ```
|
||||
* @extension ERC1155
|
||||
* @returns A promise that resolves to the payload and signature.
|
||||
*/
|
||||
export async function generateMintSignature(
|
||||
options: GenerateMintSignatureOptions,
|
||||
) {
|
||||
const { mintRequest, account, contract } = options;
|
||||
const currency = mintRequest.currency || NATIVE_TOKEN_ADDRESS;
|
||||
const [pricePerToken, uri, uid] = await Promise.all([
|
||||
// price per token in wei
|
||||
(async () => {
|
||||
// if priceInWei is provided, use it
|
||||
if ("pricePerTokenWei" in mintRequest && mintRequest.pricePerTokenWei) {
|
||||
return mintRequest.pricePerTokenWei;
|
||||
}
|
||||
// if price is provided, convert it to wei
|
||||
if ("pricePerToken" in mintRequest && mintRequest.pricePerToken) {
|
||||
const { convertErc20Amount } = await import(
|
||||
"../../../utils/extensions/convert-erc20-amount.js"
|
||||
);
|
||||
return await convertErc20Amount({
|
||||
amount: mintRequest.pricePerToken,
|
||||
client: contract.client,
|
||||
chain: contract.chain,
|
||||
erc20Address: currency,
|
||||
});
|
||||
}
|
||||
// if neither price nor priceInWei is provided, default to 0
|
||||
return 0n;
|
||||
})(),
|
||||
// uri
|
||||
(async () => {
|
||||
if ("metadata" in mintRequest) {
|
||||
if (typeof mintRequest.metadata === "object") {
|
||||
// async import the upload function because it is not always required
|
||||
const { upload } = await import("../../../storage/upload.js");
|
||||
return await upload({
|
||||
client: options.contract.client,
|
||||
files: [mintRequest.metadata],
|
||||
});
|
||||
}
|
||||
return mintRequest.metadata;
|
||||
}
|
||||
return "";
|
||||
})(),
|
||||
// uid computation
|
||||
mintRequest.uid || (await randomBytes32()),
|
||||
]);
|
||||
|
||||
const startTime = mintRequest.validityStartTimestamp || new Date(0);
|
||||
const endTime = mintRequest.validityEndTimestamp || tenYearsFromNow();
|
||||
|
||||
const payload: PayloadType = {
|
||||
uri,
|
||||
currency,
|
||||
uid,
|
||||
pricePerToken,
|
||||
tokenId:
|
||||
"tokenId" in mintRequest ? mintRequest.tokenId || maxUint256 : maxUint256,
|
||||
quantity: mintRequest.quantity,
|
||||
to: mintRequest.to,
|
||||
royaltyRecipient: mintRequest.royaltyRecipient || account.address,
|
||||
royaltyBps: toBigInt(mintRequest.royaltyBps || 0),
|
||||
primarySaleRecipient: mintRequest.primarySaleRecipient || account.address,
|
||||
validityStartTimestamp: dateToSeconds(startTime),
|
||||
validityEndTimestamp: dateToSeconds(endTime),
|
||||
};
|
||||
|
||||
const signature = await account.signTypedData({
|
||||
domain: {
|
||||
name: "TokenERC1155",
|
||||
version: "1",
|
||||
chainId: contract.chain.id,
|
||||
verifyingContract: contract.address as Hex,
|
||||
},
|
||||
types: { MintRequest: MintRequest1155 },
|
||||
primaryType: "MintRequest",
|
||||
message: payload,
|
||||
});
|
||||
return { payload, signature };
|
||||
}
|
||||
|
||||
type PayloadType = AbiParameterToPrimitiveType<{
|
||||
type: "tuple";
|
||||
name: "payload";
|
||||
components: typeof MintRequest1155;
|
||||
}>;
|
||||
|
||||
type GeneratePayloadInput = {
|
||||
to: string;
|
||||
quantity: bigint;
|
||||
royaltyRecipient?: Address;
|
||||
royaltyBps?: number;
|
||||
primarySaleRecipient?: Address;
|
||||
pricePerToken?: string;
|
||||
pricePerTokenWei?: bigint;
|
||||
currency?: Address;
|
||||
validityStartTimestamp?: Date;
|
||||
validityEndTimestamp?: Date;
|
||||
uid?: Hex;
|
||||
} & (
|
||||
| {
|
||||
metadata: NFTInput | string;
|
||||
}
|
||||
| { tokenId: bigint }
|
||||
);
|
||||
|
||||
const MintRequest1155 = [
|
||||
{ name: "to", type: "address" },
|
||||
{ name: "royaltyRecipient", type: "address" },
|
||||
{ name: "royaltyBps", type: "uint256" },
|
||||
{ name: "primarySaleRecipient", type: "address" },
|
||||
{ name: "tokenId", type: "uint256" },
|
||||
{ name: "uri", type: "string" },
|
||||
{ name: "quantity", type: "uint256" },
|
||||
{ name: "pricePerToken", type: "uint256" },
|
||||
{ name: "currency", type: "address" },
|
||||
{ name: "validityStartTimestamp", type: "uint128" },
|
||||
{ name: "validityEndTimestamp", type: "uint128" },
|
||||
{ name: "uid", type: "bytes32" },
|
||||
] as const;
|
||||
@@ -0,0 +1,152 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "../../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
|
||||
import {
|
||||
TEST_ACCOUNT_A,
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../../test/src/test-wallets.js";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
getContract,
|
||||
} from "../../../contract/contract.js";
|
||||
import { sendTransaction } from "../../../transaction/actions/send-transaction.js";
|
||||
import { toHex } from "../../../utils/encoding/hex.js";
|
||||
import { deployERC20Contract } from "../../prebuilts/deploy-erc20.js";
|
||||
import { deployERC1155Contract } from "../../prebuilts/deploy-erc1155.js";
|
||||
import { generateMintSignature, mintWithSignature } from "./sigMint.js";
|
||||
|
||||
// skip this test suite if there is no secret key available to test with
|
||||
// TODO: remove reliance on secret key during unit tests entirely
|
||||
describe.runIf(process.env.TW_SECRET_KEY)("generateMintSignature1155", () => {
|
||||
let erc1155Contract: ThirdwebContract;
|
||||
let erc20TokenContract: ThirdwebContract;
|
||||
|
||||
beforeAll(async () => {
|
||||
erc1155Contract = getContract({
|
||||
address: await deployERC1155Contract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "Test",
|
||||
symbol: "TST",
|
||||
},
|
||||
type: "TokenERC1155",
|
||||
}),
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
|
||||
erc20TokenContract = getContract({
|
||||
address: await deployERC20Contract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestToken",
|
||||
symbol: "TSTT",
|
||||
},
|
||||
type: "TokenERC20",
|
||||
}),
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it("should generate a mint signature and mint an NFT", async () => {
|
||||
const { payload, signature } = await generateMintSignature({
|
||||
mintRequest: {
|
||||
to: TEST_ACCOUNT_B.address,
|
||||
quantity: 10n,
|
||||
metadata: {
|
||||
name: "My NFT",
|
||||
description: "This is my NFT",
|
||||
image: "https://example.com/image.png",
|
||||
},
|
||||
},
|
||||
account: TEST_ACCOUNT_A,
|
||||
contract: erc1155Contract,
|
||||
});
|
||||
const transaction = mintWithSignature({
|
||||
contract: erc1155Contract,
|
||||
payload,
|
||||
signature,
|
||||
});
|
||||
const { transactionHash } = await sendTransaction({
|
||||
transaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
expect(transactionHash.length).toBe(66);
|
||||
});
|
||||
|
||||
it("should generate a mint signature with default values", async () => {
|
||||
const { payload, signature } = await generateMintSignature({
|
||||
mintRequest: {
|
||||
to: TEST_ACCOUNT_B.address,
|
||||
quantity: 10n,
|
||||
metadata: "https://example.com/token",
|
||||
},
|
||||
account: TEST_ACCOUNT_A,
|
||||
contract: erc1155Contract,
|
||||
});
|
||||
|
||||
expect(payload.to).toBe(TEST_ACCOUNT_B.address);
|
||||
expect(payload.royaltyRecipient).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(payload.royaltyBps).toBe(0n);
|
||||
expect(payload.primarySaleRecipient).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(payload.uri).toBe("https://example.com/token");
|
||||
expect(payload.pricePerToken).toBe(0n);
|
||||
expect(payload.quantity).toBe(10n);
|
||||
expect(payload.currency).toBe(NATIVE_TOKEN_ADDRESS);
|
||||
expect(payload.validityStartTimestamp).toBe(0n);
|
||||
expect(payload.validityEndTimestamp).toBeGreaterThan(0n);
|
||||
expect(payload.uid).toBeDefined();
|
||||
expect(signature.length).toBe(132);
|
||||
|
||||
const transaction = mintWithSignature({
|
||||
contract: erc1155Contract,
|
||||
payload,
|
||||
signature,
|
||||
});
|
||||
const { transactionHash } = await sendTransaction({
|
||||
transaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
expect(transactionHash.length).toBe(66);
|
||||
});
|
||||
|
||||
it("should generate a mint signature with custom values", async () => {
|
||||
const uid = toHex("abcdef1234567890", { size: 32 });
|
||||
const { payload, signature } = await generateMintSignature({
|
||||
mintRequest: {
|
||||
to: TEST_ACCOUNT_B.address,
|
||||
quantity: 10n,
|
||||
royaltyRecipient: TEST_ACCOUNT_B.address,
|
||||
royaltyBps: 500,
|
||||
primarySaleRecipient: TEST_ACCOUNT_A.address,
|
||||
metadata: "https://example.com/token",
|
||||
pricePerToken: "0.2",
|
||||
currency: erc20TokenContract.address,
|
||||
validityStartTimestamp: new Date(1635724800),
|
||||
validityEndTimestamp: new Date(1867260800),
|
||||
uid,
|
||||
},
|
||||
account: TEST_ACCOUNT_A,
|
||||
contract: erc1155Contract,
|
||||
});
|
||||
|
||||
expect(payload.to).toBe(TEST_ACCOUNT_B.address);
|
||||
expect(payload.royaltyRecipient).toBe(TEST_ACCOUNT_B.address);
|
||||
expect(payload.royaltyBps).toBe(500n);
|
||||
expect(payload.primarySaleRecipient).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(payload.uri).toBe("https://example.com/token");
|
||||
expect(payload.pricePerToken).toBe(200000000000000000n);
|
||||
expect(payload.quantity).toBe(10n);
|
||||
expect(payload.currency).toBe(erc20TokenContract.address);
|
||||
expect(payload.validityStartTimestamp).toBe(1635724n);
|
||||
expect(payload.validityEndTimestamp).toBe(1867260n);
|
||||
expect(payload.uid).toBe(uid);
|
||||
expect(signature.length).toBe(132);
|
||||
});
|
||||
});
|
||||
Generated
+8
-8
@@ -13,9 +13,9 @@ import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js";
|
||||
* Represents the parameters for the "mintWithSignature" function.
|
||||
*/
|
||||
export type MintWithSignatureParams = WithOverrides<{
|
||||
req: AbiParameterToPrimitiveType<{
|
||||
payload: AbiParameterToPrimitiveType<{
|
||||
type: "tuple";
|
||||
name: "req";
|
||||
name: "payload";
|
||||
components: [
|
||||
{ type: "address"; name: "to" },
|
||||
{ type: "address"; name: "primarySaleRecipient" },
|
||||
@@ -34,7 +34,7 @@ export const FN_SELECTOR = "0x8f0fefbb" as const;
|
||||
const FN_INPUTS = [
|
||||
{
|
||||
type: "tuple",
|
||||
name: "req",
|
||||
name: "payload",
|
||||
components: [
|
||||
{
|
||||
type: "address",
|
||||
@@ -112,7 +112,7 @@ export async function isMintWithSignatureSupported(
|
||||
* ```ts
|
||||
* import { encodeMintWithSignatureParams } "thirdweb/extensions/erc20";
|
||||
* const result = encodeMintWithSignatureParams({
|
||||
* req: ...,
|
||||
* payload: ...,
|
||||
* signature: ...,
|
||||
* });
|
||||
* ```
|
||||
@@ -120,7 +120,7 @@ export async function isMintWithSignatureSupported(
|
||||
export function encodeMintWithSignatureParams(
|
||||
options: MintWithSignatureParams,
|
||||
) {
|
||||
return encodeAbiParameters(FN_INPUTS, [options.req, options.signature]);
|
||||
return encodeAbiParameters(FN_INPUTS, [options.payload, options.signature]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +132,7 @@ export function encodeMintWithSignatureParams(
|
||||
* ```ts
|
||||
* import { encodeMintWithSignature } "thirdweb/extensions/erc20";
|
||||
* const result = encodeMintWithSignature({
|
||||
* req: ...,
|
||||
* payload: ...,
|
||||
* signature: ...,
|
||||
* });
|
||||
* ```
|
||||
@@ -157,7 +157,7 @@ export function encodeMintWithSignature(options: MintWithSignatureParams) {
|
||||
*
|
||||
* const transaction = mintWithSignature({
|
||||
* contract,
|
||||
* req: ...,
|
||||
* payload: ...,
|
||||
* signature: ...,
|
||||
* });
|
||||
*
|
||||
@@ -183,7 +183,7 @@ export function mintWithSignature(
|
||||
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const,
|
||||
params: async () => {
|
||||
const resolvedOptions = await asyncOptions();
|
||||
return [resolvedOptions.req, resolvedOptions.signature] as const;
|
||||
return [resolvedOptions.payload, resolvedOptions.signature] as const;
|
||||
},
|
||||
value: async () => (await asyncOptions()).overrides?.value,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../test/src/test-wallets.js";
|
||||
import { type ThirdwebContract, getContract } from "../../contract/contract.js";
|
||||
import { sendAndConfirmTransaction } from "../../exports/transaction.js";
|
||||
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { getContractMetadata } from "../common/read/getContractMetadata.js";
|
||||
import { deployERC20Contract } from "../prebuilts/deploy-erc20.js";
|
||||
import { claimTo } from "./drops/write/claimTo.js";
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { prepareContractCall } from "../../../transaction/prepare-contract-call.js";
|
||||
import type { BaseTransactionOptions } from "../../../transaction/types.js";
|
||||
import { toWei } from "../../../utils/units.js";
|
||||
import { FN_SELECTOR } from "../__generated__/IWETH/write/deposit.js";
|
||||
|
||||
export type DepositParams =
|
||||
| {
|
||||
amount: string;
|
||||
}
|
||||
| { amountWei: bigint };
|
||||
|
||||
/**
|
||||
* Calls the "deposit" function on the contract.
|
||||
* @param options - The options for the "deposit" function.
|
||||
* @returns A prepared transaction object.
|
||||
* @extension ERC20
|
||||
* @example
|
||||
* ```ts
|
||||
* import { deposit } from "thirdweb/extensions/erc20";
|
||||
*
|
||||
* const transaction = deposit();
|
||||
*
|
||||
* // Send the transaction
|
||||
* ...
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export function deposit(options: BaseTransactionOptions<DepositParams>) {
|
||||
return prepareContractCall({
|
||||
contract: options.contract,
|
||||
method: [FN_SELECTOR, [], []] as const,
|
||||
value: "amountWei" in options ? options.amountWei : toWei(options.amount),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import type { AbiParameterToPrimitiveType, Address } from "abitype";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
|
||||
import type { ThirdwebContract } from "../../../contract/contract.js";
|
||||
import { dateToSeconds, tenYearsFromNow } from "../../../utils/date.js";
|
||||
import type { Hex } from "../../../utils/encoding/hex.js";
|
||||
import { randomBytes32 } from "../../../utils/uuid.js";
|
||||
import type { Account } from "../../../wallets/interfaces/wallet.js";
|
||||
import { name } from "../../common/read/name.js";
|
||||
import { mintWithSignature as generatedMintWithSignature } from "../__generated__/ISignatureMintERC20/write/mintWithSignature.js";
|
||||
|
||||
/**
|
||||
* Mints a new ERC20 token with the given minter signature
|
||||
* @param options - The transaction options.
|
||||
* @example
|
||||
* ```ts
|
||||
* import { mintWithSignature, generateMintSignature } from "thirdweb/extensions/erc20";
|
||||
*
|
||||
* const { payload, signature } = await generateMintSignature(...)
|
||||
*
|
||||
* const transaction = mintWithSignature({
|
||||
* contract,
|
||||
* payload,
|
||||
* signature,
|
||||
* });
|
||||
* await sendTransaction({ transaction, account });
|
||||
* ```
|
||||
* @extension ERC20
|
||||
* @returns A promise that resolves to the transaction result.
|
||||
*/
|
||||
export const mintWithSignature = generatedMintWithSignature;
|
||||
|
||||
export type GenerateMintSignatureOptions = {
|
||||
account: Account;
|
||||
contract: ThirdwebContract;
|
||||
mintRequest: GeneratePayloadInput;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the payload and signature for minting an ERC20 token.
|
||||
* @param options - The options for the minting process.
|
||||
* @example
|
||||
* ```ts
|
||||
* import { mintWithSignature, generateMintSignature } from "thirdweb/extensions/erc20";
|
||||
*
|
||||
* const { payload, signature } = await generateMintSignature({
|
||||
* account,
|
||||
* contract,
|
||||
* mintRequest: {
|
||||
* to: "0x...",
|
||||
* quantity: "10",
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* const transaction = mintWithSignature({
|
||||
* contract,
|
||||
* payload,
|
||||
* signature,
|
||||
* });
|
||||
* await sendTransaction({ transaction, account });
|
||||
* ```
|
||||
* @extension ERC20
|
||||
* @returns A promise that resolves to the payload and signature.
|
||||
*/
|
||||
export async function generateMintSignature(
|
||||
options: GenerateMintSignatureOptions,
|
||||
) {
|
||||
const { mintRequest, account, contract } = options;
|
||||
const currency = mintRequest.currency || NATIVE_TOKEN_ADDRESS;
|
||||
const [price, quantity, uid, tokenName] = await Promise.all([
|
||||
// price per token in wei
|
||||
(async () => {
|
||||
// if priceInWei is provided, use it
|
||||
if ("priceInWei" in mintRequest && mintRequest.priceInWei) {
|
||||
return mintRequest.priceInWei;
|
||||
}
|
||||
// if price is provided, convert it to wei
|
||||
if ("price" in mintRequest && mintRequest.price) {
|
||||
const { convertErc20Amount } = await import(
|
||||
"../../../utils/extensions/convert-erc20-amount.js"
|
||||
);
|
||||
return await convertErc20Amount({
|
||||
amount: mintRequest.price,
|
||||
client: contract.client,
|
||||
chain: contract.chain,
|
||||
erc20Address: currency,
|
||||
});
|
||||
}
|
||||
// if neither price nor priceInWei is provided, default to 0
|
||||
return 0n;
|
||||
})(),
|
||||
// quantity in wei
|
||||
(async () => {
|
||||
// if the quantity is already passed in wei, use it
|
||||
if ("quantityWei" in mintRequest) {
|
||||
return mintRequest.quantityWei;
|
||||
}
|
||||
// otherwise convert the quantity to wei using the contract's OWN decimals
|
||||
const { convertErc20Amount } = await import(
|
||||
"../../../utils/extensions/convert-erc20-amount.js"
|
||||
);
|
||||
return await convertErc20Amount({
|
||||
amount: mintRequest.quantity,
|
||||
client: contract.client,
|
||||
chain: contract.chain,
|
||||
erc20Address: contract.address,
|
||||
});
|
||||
})(),
|
||||
// uid computation
|
||||
mintRequest.uid || (await randomBytes32()),
|
||||
// ERC20Permit (EIP-712) spec differs from signature mint 721, 1155.
|
||||
// it uses the token name in the domain separator
|
||||
name({
|
||||
contract,
|
||||
}),
|
||||
]);
|
||||
|
||||
const startTime = mintRequest.validityStartTimestamp || new Date(0);
|
||||
const endTime = mintRequest.validityEndTimestamp || tenYearsFromNow();
|
||||
|
||||
const payload: PayloadType = {
|
||||
price,
|
||||
quantity,
|
||||
uid,
|
||||
currency,
|
||||
to: mintRequest.to,
|
||||
primarySaleRecipient: mintRequest.primarySaleRecipient || account.address,
|
||||
validityStartTimestamp: dateToSeconds(startTime),
|
||||
validityEndTimestamp: dateToSeconds(endTime),
|
||||
};
|
||||
|
||||
const signature = await account.signTypedData({
|
||||
domain: {
|
||||
name: tokenName,
|
||||
version: "1",
|
||||
chainId: contract.chain.id,
|
||||
verifyingContract: contract.address as Hex,
|
||||
},
|
||||
types: { MintRequest: MintRequest20 },
|
||||
primaryType: "MintRequest",
|
||||
message: payload,
|
||||
});
|
||||
return { payload, signature };
|
||||
}
|
||||
|
||||
type PayloadType = AbiParameterToPrimitiveType<{
|
||||
type: "tuple";
|
||||
name: "payload";
|
||||
components: typeof MintRequest20;
|
||||
}>;
|
||||
|
||||
type GeneratePayloadInput = {
|
||||
to: string;
|
||||
primarySaleRecipient?: Address;
|
||||
price?: string;
|
||||
priceInWei?: bigint;
|
||||
currency?: Address;
|
||||
validityStartTimestamp?: Date;
|
||||
validityEndTimestamp?: Date;
|
||||
uid?: Hex;
|
||||
} & ({ quantity: string } | { quantityWei: bigint });
|
||||
|
||||
export const MintRequest20 = [
|
||||
{ name: "to", type: "address" },
|
||||
{ name: "primarySaleRecipient", type: "address" },
|
||||
{ name: "quantity", type: "uint256" },
|
||||
{ name: "price", type: "uint256" },
|
||||
{ name: "currency", type: "address" },
|
||||
{ name: "validityStartTimestamp", type: "uint128" },
|
||||
{ name: "validityEndTimestamp", type: "uint128" },
|
||||
{ name: "uid", type: "bytes32" },
|
||||
] as const;
|
||||
@@ -0,0 +1,140 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "../../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
|
||||
import {
|
||||
TEST_ACCOUNT_A,
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../../test/src/test-wallets.js";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
getContract,
|
||||
} from "../../../contract/contract.js";
|
||||
import { sendTransaction } from "../../../transaction/actions/send-transaction.js";
|
||||
import { toHex } from "../../../utils/encoding/hex.js";
|
||||
import { deployERC20Contract } from "../../prebuilts/deploy-erc20.js";
|
||||
import { generateMintSignature, mintWithSignature } from "./sigMint.js";
|
||||
|
||||
// skip this test suite if there is no secret key available to test with
|
||||
// TODO: remove reliance on secret key during unit tests entirely
|
||||
describe.runIf(process.env.TW_SECRET_KEY)("generateMintSignature20", () => {
|
||||
let erc20Contract: ThirdwebContract;
|
||||
let erc20TokenContract: ThirdwebContract;
|
||||
|
||||
beforeAll(async () => {
|
||||
erc20Contract = getContract({
|
||||
address: await deployERC20Contract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "Test",
|
||||
symbol: "TST",
|
||||
},
|
||||
type: "TokenERC20",
|
||||
}),
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
erc20TokenContract = getContract({
|
||||
address: await deployERC20Contract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestToken",
|
||||
symbol: "TSTT",
|
||||
},
|
||||
type: "TokenERC20",
|
||||
}),
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it("should generate a mint signature and mint an NFT", async () => {
|
||||
const { payload, signature } = await generateMintSignature({
|
||||
mintRequest: {
|
||||
to: TEST_ACCOUNT_B.address,
|
||||
quantity: "0.1",
|
||||
},
|
||||
account: TEST_ACCOUNT_A,
|
||||
contract: erc20Contract,
|
||||
});
|
||||
const transaction = mintWithSignature({
|
||||
contract: erc20Contract,
|
||||
payload,
|
||||
signature,
|
||||
});
|
||||
const { transactionHash } = await sendTransaction({
|
||||
transaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
expect(transactionHash.length).toBe(66);
|
||||
});
|
||||
|
||||
it("should generate a mint signature with default values", async () => {
|
||||
const { payload, signature } = await generateMintSignature({
|
||||
mintRequest: {
|
||||
to: TEST_ACCOUNT_B.address,
|
||||
quantity: "0.1",
|
||||
},
|
||||
account: TEST_ACCOUNT_A,
|
||||
contract: erc20Contract,
|
||||
});
|
||||
|
||||
expect(payload.to).toBe("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
|
||||
expect(payload.primarySaleRecipient).toBe(
|
||||
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
||||
);
|
||||
expect(payload.quantity).toBe(100000000000000000n);
|
||||
expect(payload.price).toBe(0n);
|
||||
expect(payload.currency).toBe(NATIVE_TOKEN_ADDRESS);
|
||||
expect(payload.validityStartTimestamp).toBe(0n);
|
||||
expect(payload.validityEndTimestamp).toBeGreaterThan(0n);
|
||||
expect(payload.uid).toBeDefined();
|
||||
expect(signature.length).toBe(132);
|
||||
|
||||
const transaction = mintWithSignature({
|
||||
contract: erc20Contract,
|
||||
payload,
|
||||
signature,
|
||||
});
|
||||
const { transactionHash } = await sendTransaction({
|
||||
transaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
expect(transactionHash.length).toBe(66);
|
||||
});
|
||||
|
||||
it("should generate a mint signature with custom values", async () => {
|
||||
const { payload, signature } = await generateMintSignature({
|
||||
mintRequest: {
|
||||
to: TEST_ACCOUNT_B.address,
|
||||
quantity: "0.005",
|
||||
primarySaleRecipient: TEST_ACCOUNT_A.address,
|
||||
price: "0.2",
|
||||
currency: erc20TokenContract.address,
|
||||
validityStartTimestamp: new Date(1635724800),
|
||||
validityEndTimestamp: new Date(1867260800),
|
||||
uid: toHex("abcdef1234567890", { size: 32 }),
|
||||
},
|
||||
account: TEST_ACCOUNT_A,
|
||||
contract: erc20Contract,
|
||||
});
|
||||
|
||||
expect(payload.to).toBe("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
|
||||
expect(payload.primarySaleRecipient).toBe(
|
||||
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
||||
);
|
||||
expect(payload.quantity).toBe(5000000000000000n);
|
||||
expect(payload.price).toBe(200000000000000000n);
|
||||
expect(payload.currency).toBe(erc20TokenContract.address);
|
||||
expect(payload.validityStartTimestamp).toBe(1635724n);
|
||||
expect(payload.validityEndTimestamp).toBe(1867260n);
|
||||
expect(payload.uid).toBe(
|
||||
"0x6162636465663132333435363738393000000000000000000000000000000000",
|
||||
);
|
||||
expect(signature.length).toBe(132);
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../test/src/test-wallets.js";
|
||||
import { type ThirdwebContract, getContract } from "../../contract/contract.js";
|
||||
import { sendAndConfirmTransaction } from "../../exports/transaction.js";
|
||||
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { getContractMetadata } from "../common/read/getContractMetadata.js";
|
||||
import { deployERC721Contract } from "../prebuilts/deploy-erc721.js";
|
||||
import { balanceOf } from "./__generated__/IERC721A/read/balanceOf.js";
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import type { AbiParameterToPrimitiveType } from "abitype";
|
||||
import type { AbiParameterToPrimitiveType, Address } from "abitype";
|
||||
import type { Hex } from "viem";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
|
||||
import type { ThirdwebContract } from "../../../contract/contract.js";
|
||||
import { upload } from "../../../storage/upload.js";
|
||||
import { toBigInt } from "../../../utils/bigint.js";
|
||||
import { dateToSeconds, tenYearsFromNow } from "../../../utils/date.js";
|
||||
import type { NFTInput } from "../../../utils/nft/parseNft.js";
|
||||
import { toUnits } from "../../../utils/units.js";
|
||||
import { randomBytes32 } from "../../../utils/uuid.js";
|
||||
import type { Account } from "../../../wallets/interfaces/wallet.js";
|
||||
import { decimals } from "../../erc20/read/decimals.js";
|
||||
import { mintWithSignature as generatedMintWithSignature } from "../__generated__/ISignatureMintERC721/write/mintWithSignature.js";
|
||||
|
||||
/**
|
||||
@@ -72,39 +70,64 @@ export async function generateMintSignature(
|
||||
options: GenerateMintSignatureOptions,
|
||||
) {
|
||||
const { mintRequest, account, contract } = options;
|
||||
let priceInWei = 0n;
|
||||
if (mintRequest.price) {
|
||||
const d = await decimals(options).catch(() => 18);
|
||||
priceInWei = toUnits(mintRequest.price.toString(), d);
|
||||
}
|
||||
|
||||
const currency = mintRequest.currency || NATIVE_TOKEN_ADDRESS;
|
||||
const [price, uri, uid] = await Promise.all([
|
||||
// price per token in wei
|
||||
(async () => {
|
||||
// if priceInWei is provided, use it
|
||||
if ("priceInWei" in mintRequest && mintRequest.priceInWei) {
|
||||
return mintRequest.priceInWei;
|
||||
}
|
||||
// if price is provided, convert it to wei
|
||||
if ("price" in mintRequest && mintRequest.price) {
|
||||
const { convertErc20Amount } = await import(
|
||||
"../../../utils/extensions/convert-erc20-amount.js"
|
||||
);
|
||||
return await convertErc20Amount({
|
||||
amount: mintRequest.price,
|
||||
client: contract.client,
|
||||
chain: contract.chain,
|
||||
erc20Address: currency,
|
||||
});
|
||||
}
|
||||
// if neither price nor priceInWei is provided, default to 0
|
||||
return 0n;
|
||||
})(),
|
||||
// uri
|
||||
(async () => {
|
||||
if ("metadata" in mintRequest) {
|
||||
if (typeof mintRequest.metadata === "object") {
|
||||
// async import the upload function because it is not always required
|
||||
const { upload } = await import("../../../storage/upload.js");
|
||||
return await upload({
|
||||
client: options.contract.client,
|
||||
files: [mintRequest.metadata],
|
||||
});
|
||||
}
|
||||
return mintRequest.metadata;
|
||||
}
|
||||
return "";
|
||||
})(),
|
||||
// uid computation
|
||||
mintRequest.uid || (await randomBytes32()),
|
||||
]);
|
||||
|
||||
const startTime = mintRequest.validityStartTimestamp || new Date(0);
|
||||
const endTime = mintRequest.validityEndTimestamp || tenYearsFromNow();
|
||||
|
||||
const uid = mintRequest.uid || (await randomBytes32());
|
||||
|
||||
let uri: string;
|
||||
if (typeof mintRequest.metadata === "object") {
|
||||
uri = await upload({
|
||||
client: options.contract.client,
|
||||
files: [mintRequest.metadata],
|
||||
});
|
||||
} else {
|
||||
uri = mintRequest.metadata;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
const payload: PayloadType = {
|
||||
uri,
|
||||
currency,
|
||||
uid,
|
||||
price,
|
||||
to: mintRequest.to,
|
||||
royaltyRecipient: mintRequest.royaltyRecipient || account.address,
|
||||
royaltyBps: toBigInt(mintRequest.royaltyBps || 0),
|
||||
primarySaleRecipient: mintRequest.primarySaleRecipient || account.address,
|
||||
uri: uri || "",
|
||||
price: priceInWei,
|
||||
currency: mintRequest.currency || NATIVE_TOKEN_ADDRESS,
|
||||
validityStartTimestamp: dateToSeconds(startTime),
|
||||
validityEndTimestamp: dateToSeconds(endTime),
|
||||
uid,
|
||||
} as PayloadType;
|
||||
};
|
||||
|
||||
const signature = await account.signTypedData({
|
||||
domain: {
|
||||
@@ -129,14 +152,15 @@ type PayloadType = AbiParameterToPrimitiveType<{
|
||||
type GeneratePayloadInput = {
|
||||
to: string;
|
||||
metadata: NFTInput | string;
|
||||
royaltyRecipient?: string;
|
||||
royaltyRecipient?: Address;
|
||||
royaltyBps?: number;
|
||||
primarySaleRecipient?: string;
|
||||
price?: number | string;
|
||||
currency?: string;
|
||||
primarySaleRecipient?: Address;
|
||||
price?: string;
|
||||
priceInWei?: bigint;
|
||||
currency?: Address;
|
||||
validityStartTimestamp?: Date;
|
||||
validityEndTimestamp?: Date;
|
||||
uid?: string;
|
||||
uid?: Hex;
|
||||
};
|
||||
|
||||
const MintRequest721 = [
|
||||
|
||||
+44
-20
@@ -1,7 +1,6 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "../../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
|
||||
import { USDT_CONTRACT_ADDRESS } from "../../../../test/src/test-contracts.js";
|
||||
import {
|
||||
TEST_ACCOUNT_A,
|
||||
TEST_ACCOUNT_B,
|
||||
@@ -13,32 +12,46 @@ import {
|
||||
} from "../../../contract/contract.js";
|
||||
import { sendTransaction } from "../../../transaction/actions/send-transaction.js";
|
||||
import { toHex } from "../../../utils/encoding/hex.js";
|
||||
import { deployERC20Contract } from "../../prebuilts/deploy-erc20.js";
|
||||
import { deployERC721Contract } from "../../prebuilts/deploy-erc721.js";
|
||||
import { generateMintSignature, mintWithSignature } from "./sigMint.js";
|
||||
|
||||
// skip this test suite if there is no secret key available to test with
|
||||
// TODO: remove reliance on secret key during unit tests entirely
|
||||
describe.runIf(process.env.TW_SECRET_KEY)(
|
||||
"generateMintSignature",
|
||||
{
|
||||
timeout: 120000,
|
||||
},
|
||||
"generateMintSignature721",
|
||||
|
||||
() => {
|
||||
let erc721Contract: ThirdwebContract;
|
||||
let erc20TokenContract: ThirdwebContract;
|
||||
|
||||
beforeAll(async () => {
|
||||
const contractAddress = await deployERC721Contract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
erc721Contract = getContract({
|
||||
address: await deployERC721Contract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "Test",
|
||||
symbol: "TST",
|
||||
},
|
||||
type: "TokenERC721",
|
||||
}),
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "Test",
|
||||
symbol: "TST",
|
||||
},
|
||||
type: "TokenERC721",
|
||||
});
|
||||
erc721Contract = getContract({
|
||||
address: contractAddress,
|
||||
|
||||
erc20TokenContract = getContract({
|
||||
address: await deployERC20Contract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestToken",
|
||||
symbol: "TSTT",
|
||||
},
|
||||
type: "TokenERC20",
|
||||
}),
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
@@ -94,6 +107,17 @@ describe.runIf(process.env.TW_SECRET_KEY)(
|
||||
expect(payload.validityEndTimestamp).toBeGreaterThan(0n);
|
||||
expect(payload.uid).toBeDefined();
|
||||
expect(signature.length).toBe(132);
|
||||
|
||||
const transaction = mintWithSignature({
|
||||
contract: erc721Contract,
|
||||
payload,
|
||||
signature,
|
||||
});
|
||||
const { transactionHash } = await sendTransaction({
|
||||
transaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
expect(transactionHash.length).toBe(66);
|
||||
});
|
||||
|
||||
it("should generate a mint signature with custom values", async () => {
|
||||
@@ -104,10 +128,10 @@ describe.runIf(process.env.TW_SECRET_KEY)(
|
||||
royaltyBps: 500,
|
||||
primarySaleRecipient: TEST_ACCOUNT_A.address,
|
||||
metadata: "https://example.com/token",
|
||||
price: 0.2,
|
||||
currency: USDT_CONTRACT_ADDRESS,
|
||||
validityStartTimestamp: new Date(1635724800), // October 31, 2021 00:00:00 UTC
|
||||
validityEndTimestamp: new Date(1667260800), // October 31, 2022 00:00:00 UTC
|
||||
price: "0.2",
|
||||
currency: erc20TokenContract.address,
|
||||
validityStartTimestamp: new Date(1635724800),
|
||||
validityEndTimestamp: new Date(1867260800),
|
||||
uid: toHex("abcdef1234567890", { size: 32 }),
|
||||
},
|
||||
account: TEST_ACCOUNT_A,
|
||||
@@ -124,9 +148,9 @@ describe.runIf(process.env.TW_SECRET_KEY)(
|
||||
);
|
||||
expect(payload.uri).toBe("https://example.com/token");
|
||||
expect(payload.price).toBe(200000000000000000n);
|
||||
expect(payload.currency).toBe(USDT_CONTRACT_ADDRESS);
|
||||
expect(payload.currency).toBe(erc20TokenContract.address);
|
||||
expect(payload.validityStartTimestamp).toBe(1635724n);
|
||||
expect(payload.validityEndTimestamp).toBe(1667260n);
|
||||
expect(payload.validityEndTimestamp).toBe(1867260n);
|
||||
expect(payload.uid).toBe(
|
||||
"0x6162636465663132333435363738393000000000000000000000000000000000",
|
||||
);
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { readContract } from "../../../../../transaction/read-contract.js";
|
||||
import type { BaseTransactionOptions } from "../../../../../transaction/types.js";
|
||||
|
||||
import { decodeAbiParameters } from "viem";
|
||||
import type { Hex } from "../../../../../utils/encoding/hex.js";
|
||||
import type { ThirdwebContract } from "../../../../../contract/contract.js";
|
||||
import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js";
|
||||
|
||||
export const FN_SELECTOR = "0xa9fd8ed1" as const;
|
||||
const FN_INPUTS = [] as const;
|
||||
const FN_OUTPUTS = [
|
||||
{
|
||||
type: "uint256",
|
||||
},
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Checks if the `totalOffers` method is supported by the given contract.
|
||||
* @param contract The ThirdwebContract.
|
||||
* @returns A promise that resolves to a boolean indicating if the `totalOffers` method is supported.
|
||||
* @extension MARKETPLACE
|
||||
* @example
|
||||
* ```ts
|
||||
* import { isTotalOffersSupported } from "thirdweb/extensions/marketplace";
|
||||
*
|
||||
* const supported = await isTotalOffersSupported(contract);
|
||||
* ```
|
||||
*/
|
||||
export async function isTotalOffersSupported(contract: ThirdwebContract<any>) {
|
||||
return detectMethod({
|
||||
contract,
|
||||
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the result of the totalOffers function call.
|
||||
* @param result - The hexadecimal result to decode.
|
||||
* @returns The decoded result as per the FN_OUTPUTS definition.
|
||||
* @extension MARKETPLACE
|
||||
* @example
|
||||
* ```ts
|
||||
* import { decodeTotalOffersResult } from "thirdweb/extensions/marketplace";
|
||||
* const result = decodeTotalOffersResult("...");
|
||||
* ```
|
||||
*/
|
||||
export function decodeTotalOffersResult(result: Hex) {
|
||||
return decodeAbiParameters(FN_OUTPUTS, result)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the "totalOffers" function on the contract.
|
||||
* @param options - The options for the totalOffers function.
|
||||
* @returns The parsed result of the function call.
|
||||
* @extension MARKETPLACE
|
||||
* @example
|
||||
* ```ts
|
||||
* import { totalOffers } from "thirdweb/extensions/marketplace";
|
||||
*
|
||||
* const result = await totalOffers();
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export async function totalOffers(options: BaseTransactionOptions) {
|
||||
return readContract({
|
||||
contract: options.contract,
|
||||
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const,
|
||||
params: [],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "../../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
|
||||
import {
|
||||
TEST_ACCOUNT_A,
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../../test/src/test-wallets.js";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
getContract,
|
||||
} from "../../../contract/contract.js";
|
||||
import { parseEventLogs } from "../../../event/actions/parse-logs.js";
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { balanceOf as balanceOfErc721 } from "../../erc721/__generated__/IERC721A/read/balanceOf.js";
|
||||
import { approve as approveErc721 } from "../../erc721/__generated__/IERC721A/write/approve.js";
|
||||
import { tokensMintedEvent as tokensMintedEventErc721 } from "../../erc721/__generated__/IMintableERC721/events/TokensMinted.js";
|
||||
import { mintTo as mintToErc721 } from "../../erc721/write/mintTo.js";
|
||||
import { balanceOf as balanceOfErc1155 } from "../../erc1155/__generated__/IERC1155/read/balanceOf.js";
|
||||
import { setApprovalForAll } from "../../erc1155/__generated__/IERC1155/write/setApprovalForAll.js";
|
||||
import { claimTo } from "../../erc1155/drops/write/claimTo.js";
|
||||
import { setClaimConditions } from "../../erc1155/drops/write/setClaimConditions.js";
|
||||
import { lazyMint } from "../../erc1155/write/lazyMint.js";
|
||||
import { deployERC721Contract } from "../../prebuilts/deploy-erc721.js";
|
||||
import { deployERC1155Contract } from "../../prebuilts/deploy-erc1155.js";
|
||||
import { deployMarketplaceContract } from "../../prebuilts/deploy-marketplace.js";
|
||||
import { newListingEvent } from "../__generated__/IDirectListings/events/NewListing.js";
|
||||
import { totalListings } from "../__generated__/IDirectListings/read/totalListings.js";
|
||||
import { getAllListings } from "./read/getAllListings.js";
|
||||
import { getAllValidListings } from "./read/getAllValidListings.js";
|
||||
import { getListing } from "./read/getListing.js";
|
||||
import { isListingValid } from "./utils.js";
|
||||
import { buyFromListing } from "./write/buyFromListing.js";
|
||||
import { createListing } from "./write/createListing.js";
|
||||
|
||||
describe("Marketplace: Direct Listings", () => {
|
||||
describe("ERC721", () => {
|
||||
let nftTokenId: bigint;
|
||||
let marketplaceContract: ThirdwebContract;
|
||||
let erc721Contract: ThirdwebContract;
|
||||
beforeAll(async () => {
|
||||
marketplaceContract = getContract({
|
||||
address: await deployMarketplaceContract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestMarketPlace",
|
||||
},
|
||||
}),
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
// also deploy an ERC721 contract
|
||||
erc721Contract = getContract({
|
||||
address: await deployERC721Contract({
|
||||
type: "TokenERC721",
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestERC721",
|
||||
},
|
||||
}),
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction: mintToErc721({
|
||||
contract: erc721Contract,
|
||||
to: TEST_ACCOUNT_A.address,
|
||||
nft: { name: "Test:ERC721:DirectListing" },
|
||||
}),
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const mintEvents = parseEventLogs({
|
||||
events: [tokensMintedEventErc721()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(mintEvents.length).toBe(1);
|
||||
expect(mintEvents[0]?.args.tokenIdMinted).toBeDefined();
|
||||
|
||||
nftTokenId = mintEvents[0]?.args.tokenIdMinted as bigint;
|
||||
// does a lot of stuff, this may take a while
|
||||
}, 120_000);
|
||||
|
||||
it("should work for basic listings (Native Currency)", async () => {
|
||||
// listings should be 0 length to start
|
||||
const listings = await getAllListings({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(listings.length).toBe(0);
|
||||
// oh and so should totalListings
|
||||
expect(await totalListings({ contract: marketplaceContract })).toBe(0n);
|
||||
|
||||
// approve first
|
||||
const approveTx = approveErc721({
|
||||
contract: erc721Contract,
|
||||
to: marketplaceContract.address,
|
||||
tokenId: nftTokenId,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: approveTx,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const transaction = createListing({
|
||||
contract: marketplaceContract,
|
||||
assetContractAddress: erc721Contract.address,
|
||||
tokenId: nftTokenId,
|
||||
pricePerToken: "1",
|
||||
});
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const listingEvents = parseEventLogs({
|
||||
events: [newListingEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(listingEvents.length).toBe(1);
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: OK in tests
|
||||
const listingEvent = listingEvents[0]!;
|
||||
|
||||
expect(listingEvent.args.listingCreator).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listingEvent.args.assetContract).toBe(erc721Contract.address);
|
||||
|
||||
// at this point listings should be 1
|
||||
const listingsAfter = await getAllListings({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(listingsAfter.length).toBe(1);
|
||||
// valid listings should also be 1!
|
||||
const validListings = await getAllValidListings({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(validListings.length).toBe(1);
|
||||
// and totalListings should be 1
|
||||
expect(await totalListings({ contract: marketplaceContract })).toBe(1n);
|
||||
|
||||
// explicitly retrieve the listing!
|
||||
const listing = await getListing({
|
||||
contract: marketplaceContract,
|
||||
listingId: listingEvent.args.listingId,
|
||||
});
|
||||
|
||||
expect(listing).toBeDefined();
|
||||
expect(listing.status).toBe("ACTIVE");
|
||||
expect(listing.creatorAddress).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listing.assetContractAddress).toBe(erc721Contract.address);
|
||||
expect(listing.tokenId).toBe(nftTokenId);
|
||||
expect(listing.currencyValuePerToken).toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "1",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 1000000000000000000n,
|
||||
}
|
||||
`);
|
||||
expect(listing.asset).toMatchInlineSnapshot(`
|
||||
{
|
||||
"id": 0n,
|
||||
"metadata": {
|
||||
"name": "Test:ERC721:DirectListing",
|
||||
},
|
||||
"owner": null,
|
||||
"tokenURI": "ipfs://QmewoGwuooC1Vno1ZTnmKmEUaimDygraR1fEEHdTeraF34/0",
|
||||
"type": "ERC721",
|
||||
}
|
||||
`);
|
||||
|
||||
// check the listing is valid
|
||||
const listingValidity = await isListingValid({
|
||||
listing,
|
||||
contract: marketplaceContract,
|
||||
quantity: 1n,
|
||||
});
|
||||
|
||||
expect(listingValidity).toMatchInlineSnapshot(`
|
||||
{
|
||||
"valid": true,
|
||||
}
|
||||
`);
|
||||
|
||||
// expect the buyer to have an initial balance of 0
|
||||
await expect(
|
||||
balanceOfErc721({
|
||||
contract: erc721Contract,
|
||||
owner: TEST_ACCOUNT_B.address,
|
||||
}),
|
||||
).resolves.toBe(0n);
|
||||
|
||||
const buyTx = buyFromListing({
|
||||
contract: marketplaceContract,
|
||||
listingId: listingEvent.args.listingId,
|
||||
recipient: TEST_ACCOUNT_B.address,
|
||||
quantity: 1n,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: buyTx,
|
||||
account: TEST_ACCOUNT_B,
|
||||
});
|
||||
|
||||
// expect the buyer to have a new balance of 1
|
||||
await expect(
|
||||
balanceOfErc721({
|
||||
contract: erc721Contract,
|
||||
owner: TEST_ACCOUNT_B.address,
|
||||
}),
|
||||
).resolves.toBe(1n);
|
||||
// expect the seller to no longer have the token
|
||||
await expect(
|
||||
balanceOfErc721({
|
||||
contract: erc721Contract,
|
||||
owner: TEST_ACCOUNT_A.address,
|
||||
}),
|
||||
).resolves.toBe(0n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ERC1155 Drop", () => {
|
||||
let nftTokenId: bigint;
|
||||
let marketplaceContract: ThirdwebContract;
|
||||
let erc1155Contract: ThirdwebContract;
|
||||
beforeAll(async () => {
|
||||
marketplaceContract = getContract({
|
||||
address: await deployMarketplaceContract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestMarketPlace",
|
||||
},
|
||||
}),
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
// also deploy an ERC721 contract
|
||||
erc1155Contract = getContract({
|
||||
address: await deployERC1155Contract({
|
||||
type: "DropERC1155",
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestERC1155",
|
||||
},
|
||||
}),
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
// lazy mint 10 tokens
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: lazyMint({
|
||||
contract: erc1155Contract,
|
||||
nfts: [{ name: "Test:ERC1155:DirectListing" }],
|
||||
}),
|
||||
account: TEST_ACCOUNT_A,
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// set claim condition (just public is fine)
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: setClaimConditions({
|
||||
contract: erc1155Contract,
|
||||
tokenId: 0n,
|
||||
phases: [{}],
|
||||
}),
|
||||
account: TEST_ACCOUNT_A,
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// claim 10 tokens
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: claimTo({
|
||||
contract: erc1155Contract,
|
||||
tokenId: 0n,
|
||||
to: TEST_ACCOUNT_A.address,
|
||||
quantity: 10n,
|
||||
}),
|
||||
account: TEST_ACCOUNT_A,
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
nftTokenId = 0n;
|
||||
// does a lot of stuff, this may take a while
|
||||
}, 120_000);
|
||||
|
||||
it("should work for basic listings (Native Currency)", async () => {
|
||||
// listings should be 0 length to start
|
||||
const listings = await getAllListings({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(listings.length).toBe(0);
|
||||
// oh and so should totalListings
|
||||
expect(await totalListings({ contract: marketplaceContract })).toBe(0n);
|
||||
|
||||
// approve first
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: setApprovalForAll({
|
||||
contract: erc1155Contract,
|
||||
operator: marketplaceContract.address,
|
||||
approved: true,
|
||||
}),
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
// this should fail because we're listing more than we have
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: createListing({
|
||||
contract: marketplaceContract,
|
||||
assetContractAddress: erc1155Contract.address,
|
||||
tokenId: nftTokenId,
|
||||
pricePerToken: "1",
|
||||
quantity: 20n,
|
||||
}),
|
||||
account: TEST_ACCOUNT_A,
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
[TransactionError: Error - Marketplace: not owner or approved tokens.
|
||||
|
||||
contract: ${marketplaceContract.address}
|
||||
chainId: 31337]
|
||||
`);
|
||||
|
||||
// this should work because we're listing the correct amount
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction: createListing({
|
||||
contract: marketplaceContract,
|
||||
assetContractAddress: erc1155Contract.address,
|
||||
tokenId: nftTokenId,
|
||||
pricePerToken: "0.01",
|
||||
quantity: 1n,
|
||||
}),
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const listingEvents = parseEventLogs({
|
||||
events: [newListingEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(listingEvents.length).toBe(1);
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: OK in tests
|
||||
const listingEvent = listingEvents[0]!;
|
||||
|
||||
expect(listingEvent.args.listingCreator).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listingEvent.args.assetContract).toBe(erc1155Contract.address);
|
||||
|
||||
// at this point listings should be 1
|
||||
const listingsAfter = await getAllListings({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(listingsAfter.length).toBe(1);
|
||||
// valid listings should also be 1!
|
||||
const validListings = await getAllValidListings({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(validListings.length).toBe(1);
|
||||
// and totalListings should be 1
|
||||
expect(await totalListings({ contract: marketplaceContract })).toBe(1n);
|
||||
|
||||
// explicitly retrieve the listing!
|
||||
const listing = await getListing({
|
||||
contract: marketplaceContract,
|
||||
listingId: listingEvent.args.listingId,
|
||||
});
|
||||
|
||||
expect(listing).toBeDefined();
|
||||
expect(listing.status).toBe("ACTIVE");
|
||||
expect(listing.creatorAddress).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listing.assetContractAddress).toBe(erc1155Contract.address);
|
||||
expect(listing.tokenId).toBe(nftTokenId);
|
||||
expect(listing.currencyValuePerToken).toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "0.01",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 10000000000000000n,
|
||||
}
|
||||
`);
|
||||
expect(listing.asset).toMatchInlineSnapshot(`
|
||||
{
|
||||
"id": 0n,
|
||||
"metadata": {
|
||||
"name": "Test:ERC1155:DirectListing",
|
||||
},
|
||||
"owner": null,
|
||||
"supply": 10n,
|
||||
"tokenURI": "ipfs://QmdwzcoSGSzoRSMBk2efkHjGVRsyUaR2Lm79YTuL324NtM/0",
|
||||
"type": "ERC1155",
|
||||
}
|
||||
`);
|
||||
|
||||
// check the listing is valid
|
||||
const listingValidity = await isListingValid({
|
||||
listing,
|
||||
contract: marketplaceContract,
|
||||
quantity: 1n,
|
||||
});
|
||||
|
||||
expect(listingValidity).toMatchInlineSnapshot(`
|
||||
{
|
||||
"valid": true,
|
||||
}
|
||||
`);
|
||||
|
||||
// expect the buyer to have an initial balance of 0
|
||||
await expect(
|
||||
balanceOfErc1155({
|
||||
contract: erc1155Contract,
|
||||
owner: TEST_ACCOUNT_B.address,
|
||||
tokenId: nftTokenId,
|
||||
}),
|
||||
).resolves.toBe(0n);
|
||||
|
||||
const buyTx = buyFromListing({
|
||||
contract: marketplaceContract,
|
||||
listingId: listingEvent.args.listingId,
|
||||
recipient: TEST_ACCOUNT_B.address,
|
||||
quantity: 1n,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: buyTx,
|
||||
account: TEST_ACCOUNT_B,
|
||||
});
|
||||
|
||||
// expect the buyer to have a new balance of 1
|
||||
await expect(
|
||||
balanceOfErc1155({
|
||||
contract: erc1155Contract,
|
||||
owner: TEST_ACCOUNT_B.address,
|
||||
tokenId: nftTokenId,
|
||||
}),
|
||||
).resolves.toBe(1n);
|
||||
// expect the seller to only have 9 of the tokens
|
||||
await expect(
|
||||
balanceOfErc1155({
|
||||
contract: erc1155Contract,
|
||||
owner: TEST_ACCOUNT_A.address,
|
||||
tokenId: nftTokenId,
|
||||
}),
|
||||
).resolves.toBe(9n);
|
||||
});
|
||||
});
|
||||
});
|
||||
+3
-2
@@ -4,8 +4,9 @@ import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { min } from "../../../../utils/bigint.js";
|
||||
import { getAllListings as getAllListingGenerated } from "../../__generated__/IDirectListings/read/getAllListings.js";
|
||||
import { totalListings } from "../../__generated__/IDirectListings/read/totalListings.js";
|
||||
import type { DirectListing } from "../../types.js";
|
||||
import { getAllInBatches, mapDirectListing } from "../../utils.js";
|
||||
import { getAllInBatches } from "../../utils.js";
|
||||
import type { DirectListing } from "../types.js";
|
||||
import { mapDirectListing } from "../utils.js";
|
||||
|
||||
const DEFAULT_QUERY_ALL_COUNT = 100n;
|
||||
|
||||
+3
-2
@@ -4,8 +4,9 @@ import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { min } from "../../../../utils/bigint.js";
|
||||
import { getAllValidListings as getAllValidListingGenerated } from "../../__generated__/IDirectListings/read/getAllValidListings.js";
|
||||
import { totalListings } from "../../__generated__/IDirectListings/read/totalListings.js";
|
||||
import type { DirectListing } from "../../types.js";
|
||||
import { getAllInBatches, mapDirectListing } from "../../utils.js";
|
||||
import { getAllInBatches } from "../../utils.js";
|
||||
import type { DirectListing } from "../types.js";
|
||||
import { mapDirectListing } from "../utils.js";
|
||||
|
||||
const DEFAULT_QUERY_ALL_COUNT = 100n;
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import {
|
||||
type GetListingParams as GetListingParamsGenerated,
|
||||
getListing as getListingGenerated,
|
||||
} from "../../__generated__/IDirectListings/read/getListing.js";
|
||||
import type { DirectListing } from "../../types.js";
|
||||
import { mapDirectListing } from "../../utils.js";
|
||||
import type { DirectListing } from "../types.js";
|
||||
import { mapDirectListing } from "../utils.js";
|
||||
|
||||
export type GetListingParams = GetListingParamsGenerated;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Address } from "abitype";
|
||||
import type { NFT } from "../../../utils/nft/parseNft.js";
|
||||
import type { GetBalanceResult } from "../../erc20/read/getBalance.js";
|
||||
import type { ListingStatus } from "../types.js";
|
||||
|
||||
export type DirectListing = {
|
||||
id: bigint;
|
||||
creatorAddress: Address;
|
||||
assetContractAddress: Address;
|
||||
tokenId: bigint;
|
||||
quantity: bigint;
|
||||
currencyContractAddress: Address;
|
||||
currencyValuePerToken: GetBalanceResult;
|
||||
pricePerToken: bigint;
|
||||
asset: NFT;
|
||||
startTimeInSeconds: bigint;
|
||||
endTimeInSeconds: bigint;
|
||||
isReservedListing: boolean;
|
||||
status: ListingStatus;
|
||||
};
|
||||
@@ -0,0 +1,178 @@
|
||||
import { getContract } from "../../../contract/contract.js";
|
||||
import type { BaseTransactionOptions } from "../../../transaction/types.js";
|
||||
import { toTokens } from "../../../utils/units.js";
|
||||
import { getCurrencyMetadata } from "../../erc20/read/getCurrencyMetadata.js";
|
||||
import { isERC721 } from "../../erc721/read/isERC721.js";
|
||||
import { isERC1155 } from "../../erc1155/read/isERC1155.js";
|
||||
import type { getListing } from "../__generated__/IDirectListings/read/getListing.js";
|
||||
import { computeStatus, getNFTAsset } from "../utils.js";
|
||||
import type { DirectListing } from "./types.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function mapDirectListing(
|
||||
options: BaseTransactionOptions<{
|
||||
latestBlock: { timestamp: bigint };
|
||||
rawListing: Awaited<ReturnType<typeof getListing>>;
|
||||
}>,
|
||||
): Promise<DirectListing> {
|
||||
const { latestBlock, rawListing } = options;
|
||||
// process the listing
|
||||
const status = computeStatus({
|
||||
listingStatus: rawListing.status,
|
||||
blockTimeStamp: latestBlock.timestamp,
|
||||
startTimestamp: rawListing.startTimestamp,
|
||||
endTimestamp: rawListing.endTimestamp,
|
||||
});
|
||||
|
||||
const [currencyValuePerToken, nftAsset] = await Promise.all([
|
||||
getCurrencyMetadata({
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawListing.currency,
|
||||
}),
|
||||
}),
|
||||
getNFTAsset({
|
||||
...options,
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawListing.assetContract,
|
||||
}),
|
||||
tokenId: rawListing.tokenId,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
id: rawListing.listingId,
|
||||
creatorAddress: rawListing.listingCreator,
|
||||
assetContractAddress: rawListing.assetContract,
|
||||
tokenId: rawListing.tokenId,
|
||||
quantity: rawListing.quantity,
|
||||
currencyContractAddress: rawListing.currency,
|
||||
currencyValuePerToken: {
|
||||
...currencyValuePerToken,
|
||||
value: rawListing.pricePerToken,
|
||||
displayValue: toTokens(
|
||||
rawListing.pricePerToken,
|
||||
currencyValuePerToken.decimals,
|
||||
),
|
||||
},
|
||||
pricePerToken: rawListing.pricePerToken,
|
||||
asset: nftAsset,
|
||||
startTimeInSeconds: rawListing.startTimestamp,
|
||||
endTimeInSeconds: rawListing.endTimestamp,
|
||||
isReservedListing: rawListing.reserved,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
export type IsListingValidParams = {
|
||||
listing: DirectListing;
|
||||
quantity?: bigint;
|
||||
};
|
||||
export type ValidReturn = { valid: true } | { valid: false; reason: string };
|
||||
|
||||
export async function isListingValid(
|
||||
options: BaseTransactionOptions<IsListingValidParams>,
|
||||
): Promise<ValidReturn> {
|
||||
const assetContract = getContract({
|
||||
...options.contract,
|
||||
address: options.listing.assetContractAddress,
|
||||
});
|
||||
|
||||
const [erc721, erc1155] = await Promise.all([
|
||||
isERC721({ contract: assetContract }),
|
||||
isERC1155({ contract: assetContract }),
|
||||
]);
|
||||
|
||||
// if the asset is an erc721 token
|
||||
if (erc721) {
|
||||
const [{ isApprovedForAll }, { getApproved }, { ownerOf }] =
|
||||
await Promise.all([
|
||||
import("../../erc721/__generated__/IERC721A/read/isApprovedForAll.js"),
|
||||
import("../../erc721/__generated__/IERC721A/read/getApproved.js"),
|
||||
import("../../erc721/__generated__/IERC721A/read/ownerOf.js"),
|
||||
]);
|
||||
// check for token approval
|
||||
const [approvedForAll, approvedOperator, tokenOwner] = await Promise.all([
|
||||
isApprovedForAll({
|
||||
contract: assetContract,
|
||||
owner: options.listing.creatorAddress,
|
||||
// the marketplace contract address has to be approved to transfer the token
|
||||
operator: options.contract.address,
|
||||
}),
|
||||
getApproved({
|
||||
contract: assetContract,
|
||||
tokenId: options.listing.tokenId,
|
||||
}).catch(() => ""),
|
||||
ownerOf({
|
||||
contract: assetContract,
|
||||
tokenId: options.listing.tokenId,
|
||||
}),
|
||||
]);
|
||||
// if the marketplace is not approved for all and the marketplace contract is not the approved operator for the token
|
||||
// -> the listing is not valid
|
||||
if (!approvedForAll && approvedOperator !== options.contract.address) {
|
||||
return { valid: false, reason: "Asset not approved for marketplace." };
|
||||
}
|
||||
// if the token owner is not the creator of the listing
|
||||
// -> the listing is not valid
|
||||
if (tokenOwner !== options.listing.creatorAddress) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: "Listing creator no longer owns this token.",
|
||||
};
|
||||
}
|
||||
// otherwise the listing is valid
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
// if the asset is an erc1155 token
|
||||
if (erc1155) {
|
||||
const [{ isApprovedForAll }, { balanceOf }] = await Promise.all([
|
||||
import("../../erc1155/__generated__/IERC1155/read/isApprovedForAll.js"),
|
||||
import("../../erc1155/__generated__/IERC1155/read/balanceOf.js"),
|
||||
]);
|
||||
|
||||
const [approvedForAll, balance] = await Promise.all([
|
||||
isApprovedForAll({
|
||||
contract: assetContract,
|
||||
owner: options.listing.creatorAddress,
|
||||
// the marketplace contract address has to be approved to transfer the token
|
||||
operator: options.contract.address,
|
||||
}),
|
||||
balanceOf({
|
||||
contract: assetContract,
|
||||
owner: options.listing.creatorAddress,
|
||||
tokenId: options.listing.tokenId,
|
||||
}),
|
||||
]);
|
||||
|
||||
// if the marketplace is not approved for all
|
||||
// -> the listing is not valid
|
||||
if (!approvedForAll) {
|
||||
return { valid: false, reason: "Asset not approved for marketplace." };
|
||||
}
|
||||
// if the balance is less than the quantity the user is trying to purchase or the listing quantity
|
||||
// -> the listing is not valid
|
||||
const quantityWanted = options.quantity || options.listing.quantity;
|
||||
if (balance < quantityWanted) {
|
||||
return {
|
||||
valid: false,
|
||||
reason:
|
||||
"Seller does not have enough balance of token to fulfill order.",
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
// if the asset is neither ERC721 nor ERC1155
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
reason: "AssetContract must implement ERC 1155 or ERC 721.",
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -2,8 +2,8 @@ import type { Address } from "abitype";
|
||||
import { isNativeTokenAddress } from "../../../../constants/addresses.js";
|
||||
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { buyFromListing as generatedBuyFromListing } from "../../__generated__/IDirectListings/write/buyFromListing.js";
|
||||
import { getListing } from "../../read/direct/getListing.js";
|
||||
import { isListingValid } from "../../utils.js";
|
||||
import { getListing } from "../read/getListing.js";
|
||||
import { isListingValid } from "../utils.js";
|
||||
|
||||
export type BuyFromListingParams = {
|
||||
listingId: bigint;
|
||||
+1
-1
@@ -11,7 +11,7 @@ import { toUnits } from "../../../../utils/units.js";
|
||||
import { isERC721 } from "../../../erc721/read/isERC721.js";
|
||||
import { isERC1155 } from "../../../erc1155/read/isERC1155.js";
|
||||
import { updateListing as generatedUpdateListing } from "../../__generated__/IDirectListings/write/updateListing.js";
|
||||
import { getListing } from "../../read/direct/getListing.js";
|
||||
import { getListing } from "../read/getListing.js";
|
||||
|
||||
export type UpdateListingParams = {
|
||||
/**
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "../../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
|
||||
import {
|
||||
TEST_ACCOUNT_A,
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../../test/src/test-wallets.js";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
getContract,
|
||||
} from "../../../contract/contract.js";
|
||||
import { parseEventLogs } from "../../../event/actions/parse-logs.js";
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { approve } from "../../erc721/__generated__/IERC721A/write/approve.js";
|
||||
import { tokensMintedEvent } from "../../erc721/__generated__/IMintableERC721/events/TokensMinted.js";
|
||||
import { mintTo } from "../../erc721/write/mintTo.js";
|
||||
import { deployERC721Contract } from "../../prebuilts/deploy-erc721.js";
|
||||
import { deployMarketplaceContract } from "../../prebuilts/deploy-marketplace.js";
|
||||
import { newAuctionEvent } from "../__generated__/IEnglishAuctions/events/NewAuction.js";
|
||||
import { totalAuctions } from "../__generated__/IEnglishAuctions/read/totalAuctions.js";
|
||||
import { getAllAuctions } from "./read/getAllAuctions.js";
|
||||
import { getAllValidAuctions } from "./read/getAllValidAuctions.js";
|
||||
import { getAuction } from "./read/getAuction.js";
|
||||
import { getWinningBid } from "./read/getWinningBid.js";
|
||||
import { bidInAuction } from "./write/bidInAuction.js";
|
||||
import { buyoutAuction } from "./write/buyoutAuction.js";
|
||||
import { createAuction } from "./write/createAuction.js";
|
||||
|
||||
describe("Marketplace: English Auctions", () => {
|
||||
let nftTokenId: bigint;
|
||||
let marketplaceContract: ThirdwebContract;
|
||||
let erc721Contract: ThirdwebContract;
|
||||
beforeAll(async () => {
|
||||
const marketplaceAddress = await deployMarketplaceContract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestMarketPlace",
|
||||
},
|
||||
});
|
||||
marketplaceContract = getContract({
|
||||
address: marketplaceAddress,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
// also deploy an ERC721 contract
|
||||
const erc721Address = await deployERC721Contract({
|
||||
type: "TokenERC721",
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestERC721",
|
||||
},
|
||||
});
|
||||
|
||||
erc721Contract = getContract({
|
||||
address: erc721Address,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
const mintTransaction = mintTo({
|
||||
contract: erc721Contract,
|
||||
to: TEST_ACCOUNT_A.address,
|
||||
nft: { name: "Test:ERC721:EnglishAuction" },
|
||||
});
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction: mintTransaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const mintEvents = parseEventLogs({
|
||||
events: [tokensMintedEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(mintEvents.length).toBe(1);
|
||||
expect(mintEvents[0]?.args.tokenIdMinted).toBeDefined();
|
||||
|
||||
nftTokenId = mintEvents[0]?.args.tokenIdMinted as bigint;
|
||||
// does a lot of stuff, this may take a while
|
||||
}, 120_000);
|
||||
|
||||
it("should work for basic auctions (Native Currency)", async () => {
|
||||
// auctions should be 0 length to start
|
||||
const auctions = await getAllAuctions({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(auctions.length).toBe(0);
|
||||
// oh and so should totalAuctions
|
||||
expect(await totalAuctions({ contract: marketplaceContract })).toBe(0n);
|
||||
|
||||
// approve first
|
||||
const approveTx = approve({
|
||||
contract: erc721Contract,
|
||||
to: marketplaceContract.address,
|
||||
tokenId: nftTokenId,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: approveTx,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const transaction = createAuction({
|
||||
contract: marketplaceContract,
|
||||
assetContractAddress: erc721Contract.address,
|
||||
tokenId: nftTokenId,
|
||||
minimumBidAmount: "1",
|
||||
buyoutBidAmount: "10",
|
||||
});
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const listingEvents = parseEventLogs({
|
||||
events: [newAuctionEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(listingEvents.length).toBe(1);
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: OK in tests
|
||||
const listingEvent = listingEvents[0]!;
|
||||
|
||||
expect(listingEvent.args.auctionCreator).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listingEvent.args.assetContract).toBe(erc721Contract.address);
|
||||
|
||||
// at this point auctions should be 1
|
||||
const auctionsAfter = await getAllAuctions({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(auctionsAfter.length).toBe(1);
|
||||
// valid auctions should also be 1!
|
||||
const validAuctions = await getAllValidAuctions({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(validAuctions.length).toBe(1);
|
||||
// and totalauctions should be 1
|
||||
expect(await totalAuctions({ contract: marketplaceContract })).toBe(1n);
|
||||
|
||||
// explicitly retrieve the listing!
|
||||
const listing = await getAuction({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listingEvent.args.auctionId,
|
||||
});
|
||||
|
||||
expect(listing).toBeDefined();
|
||||
expect(listing.status).toBe("ACTIVE");
|
||||
expect(listing.creatorAddress).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listing.assetContractAddress).toBe(erc721Contract.address);
|
||||
expect(listing.tokenId).toBe(nftTokenId);
|
||||
expect(listing.minimumBidCurrencyValue).toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "1",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 1000000000000000000n,
|
||||
}
|
||||
`);
|
||||
expect(listing.buyoutCurrencyValue).toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "10",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 10000000000000000000n,
|
||||
}
|
||||
`);
|
||||
expect(listing.asset).toMatchInlineSnapshot(`
|
||||
{
|
||||
"id": 0n,
|
||||
"metadata": {
|
||||
"name": "Test:ERC721:EnglishAuction",
|
||||
},
|
||||
"owner": null,
|
||||
"tokenURI": "ipfs://QmQSqsA3fJu9dLhBb7FxMf8LxsFtKL3dXzPdQbyEVhGjTF/0",
|
||||
"type": "ERC721",
|
||||
}
|
||||
`);
|
||||
|
||||
// check for a winning bid
|
||||
await expect(
|
||||
getWinningBid({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
// invalid bid amount 1: 0 bid (0 is not allowed)
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "0",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot("[Error: Bid amount is zero]");
|
||||
// invalid bid amount 2: 11 bid (over buyout)
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "11",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Bid amount is above the buyout amount]",
|
||||
);
|
||||
// invalid bid amount 3: below minimum bid (but not 0)
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "0.5",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Bid amount is below the minimum bid amount]",
|
||||
);
|
||||
|
||||
// valid bid amount: "2"
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "2",
|
||||
}),
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// check for a new winning bid
|
||||
await expect(
|
||||
getWinningBid({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"bidAmountWei": 2000000000000000000n,
|
||||
"bidderAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
||||
"currencyAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
"currencyValue": {
|
||||
"decimals": 18,
|
||||
"displayValue": "2",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 2000000000000000000n,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
// invalid bid amount, above minimum but below existing winning bid
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "1.5",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Bid amount is too low to outbid the existing winning bid]",
|
||||
);
|
||||
|
||||
// invalid bid amount, above winning bit but below bid + bidBuffer (default 500bps)
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
// 2 * 1.05 = 2.1, so 2.05 is invalid
|
||||
bidAmount: "2.05",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Bid amount is too low to outbid the existing winning bid]",
|
||||
);
|
||||
|
||||
// actually outbid the winning bid
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "3",
|
||||
}),
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// check for a new winning bid
|
||||
await expect(
|
||||
getWinningBid({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"bidAmountWei": 3000000000000000000n,
|
||||
"bidderAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
||||
"currencyAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
"currencyValue": {
|
||||
"decimals": 18,
|
||||
"displayValue": "3",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 3000000000000000000n,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
// buyout auction
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: buyoutAuction({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// check for a new winning bid
|
||||
await expect(
|
||||
getWinningBid({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"bidAmountWei": 10000000000000000000n,
|
||||
"bidderAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
||||
"currencyAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
"currencyValue": {
|
||||
"decimals": 18,
|
||||
"displayValue": "10",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 10000000000000000000n,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
+3
-2
@@ -4,8 +4,9 @@ import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { min } from "../../../../utils/bigint.js";
|
||||
import { getAllAuctions as getAllAuctionsGenerated } from "../../__generated__/IEnglishAuctions/read/getAllAuctions.js";
|
||||
import { totalAuctions } from "../../__generated__/IEnglishAuctions/read/totalAuctions.js";
|
||||
import type { EnglishAuction } from "../../types.js";
|
||||
import { getAllInBatches, mapEnglishAuction } from "../../utils.js";
|
||||
import { getAllInBatches } from "../../utils.js";
|
||||
import type { EnglishAuction } from "../types.js";
|
||||
import { mapEnglishAuction } from "../utils.js";
|
||||
|
||||
const DEFAULT_QUERY_ALL_COUNT = 100n;
|
||||
|
||||
+3
-2
@@ -4,8 +4,9 @@ import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { min } from "../../../../utils/bigint.js";
|
||||
import { getAllValidAuctions as getAllValidAuctionsGenerated } from "../../__generated__/IEnglishAuctions/read/getAllValidAuctions.js";
|
||||
import { totalAuctions } from "../../__generated__/IEnglishAuctions/read/totalAuctions.js";
|
||||
import type { EnglishAuction } from "../../types.js";
|
||||
import { getAllInBatches, mapEnglishAuction } from "../../utils.js";
|
||||
import { getAllInBatches } from "../../utils.js";
|
||||
import type { EnglishAuction } from "../types.js";
|
||||
import { mapEnglishAuction } from "../utils.js";
|
||||
|
||||
const DEFAULT_QUERY_ALL_COUNT = 100n;
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import {
|
||||
type GetAuctionParams as GetAuctionParamsGenerated,
|
||||
getAuction as getAuctionGenerated,
|
||||
} from "../../__generated__/IEnglishAuctions/read/getAuction.js";
|
||||
import type { EnglishAuction } from "../../types.js";
|
||||
import { mapEnglishAuction } from "../../utils.js";
|
||||
import type { EnglishAuction } from "../types.js";
|
||||
import { mapEnglishAuction } from "../utils.js";
|
||||
|
||||
export type GetAuctionParams = GetAuctionParamsGenerated;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { Address } from "abitype";
|
||||
import type { NFT } from "../../../utils/nft/parseNft.js";
|
||||
import type { GetBalanceResult } from "../../erc20/read/getBalance.js";
|
||||
import type { ListingStatus } from "../types.js";
|
||||
|
||||
export type EnglishAuction = {
|
||||
id: bigint;
|
||||
creatorAddress: Address;
|
||||
assetContractAddress: Address;
|
||||
tokenId: bigint;
|
||||
quantity: bigint;
|
||||
currencyContractAddress: Address;
|
||||
minimumBidAmount: bigint;
|
||||
minimumBidCurrencyValue: GetBalanceResult;
|
||||
buyoutBidAmount: bigint;
|
||||
buyoutCurrencyValue: GetBalanceResult;
|
||||
timeBufferInSeconds: bigint;
|
||||
bidBufferBps: bigint;
|
||||
startTimeInSeconds: bigint;
|
||||
endTimeInSeconds: bigint;
|
||||
asset: NFT;
|
||||
status: ListingStatus;
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import { getContract } from "../../../contract/contract.js";
|
||||
import type { BaseTransactionOptions } from "../../../transaction/types.js";
|
||||
import { toTokens } from "../../../utils/units.js";
|
||||
import { getCurrencyMetadata } from "../../erc20/read/getCurrencyMetadata.js";
|
||||
import type { getAuction } from "../__generated__/IEnglishAuctions/read/getAuction.js";
|
||||
import { computeStatus, getNFTAsset } from "../utils.js";
|
||||
import type { EnglishAuction } from "./types.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function mapEnglishAuction(
|
||||
options: BaseTransactionOptions<{
|
||||
latestBlock: { timestamp: bigint };
|
||||
rawAuction: Awaited<ReturnType<typeof getAuction>>;
|
||||
}>,
|
||||
): Promise<EnglishAuction> {
|
||||
const { latestBlock, rawAuction } = options;
|
||||
// process the listing
|
||||
const status = computeStatus({
|
||||
listingStatus: rawAuction.status,
|
||||
blockTimeStamp: latestBlock.timestamp,
|
||||
startTimestamp: rawAuction.startTimestamp,
|
||||
endTimestamp: rawAuction.endTimestamp,
|
||||
});
|
||||
|
||||
const [auctionCurrencyMetadata, nftAsset] = await Promise.all([
|
||||
getCurrencyMetadata({
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawAuction.currency,
|
||||
}),
|
||||
}),
|
||||
getNFTAsset({
|
||||
...options,
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawAuction.assetContract,
|
||||
}),
|
||||
tokenId: rawAuction.tokenId,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
id: rawAuction.auctionId,
|
||||
creatorAddress: rawAuction.auctionCreator,
|
||||
assetContractAddress: rawAuction.assetContract,
|
||||
tokenId: rawAuction.tokenId,
|
||||
quantity: rawAuction.quantity,
|
||||
currencyContractAddress: rawAuction.currency,
|
||||
asset: nftAsset,
|
||||
startTimeInSeconds: rawAuction.startTimestamp,
|
||||
endTimeInSeconds: rawAuction.endTimestamp,
|
||||
status,
|
||||
minimumBidAmount: rawAuction.minimumBidAmount,
|
||||
minimumBidCurrencyValue: {
|
||||
...auctionCurrencyMetadata,
|
||||
value: rawAuction.minimumBidAmount,
|
||||
displayValue: toTokens(
|
||||
rawAuction.minimumBidAmount,
|
||||
auctionCurrencyMetadata.decimals,
|
||||
),
|
||||
},
|
||||
buyoutBidAmount: rawAuction.buyoutBidAmount,
|
||||
buyoutCurrencyValue: {
|
||||
...auctionCurrencyMetadata,
|
||||
value: rawAuction.buyoutBidAmount,
|
||||
displayValue: toTokens(
|
||||
rawAuction.buyoutBidAmount,
|
||||
auctionCurrencyMetadata.decimals,
|
||||
),
|
||||
},
|
||||
timeBufferInSeconds: rawAuction.timeBufferInSeconds,
|
||||
bidBufferBps: rawAuction.bidBufferBps,
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -2,8 +2,8 @@ import { isNativeTokenAddress } from "../../../../constants/addresses.js";
|
||||
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { isNewWinningBid } from "../../__generated__/IEnglishAuctions/read/isNewWinningBid.js";
|
||||
import { bidInAuction as generatedBidInAuction } from "../../__generated__/IEnglishAuctions/write/bidInAuction.js";
|
||||
import { getAuction } from "../../read/english-auction/getAuction.js";
|
||||
import { getWinningBid } from "../../read/english-auction/getWinningBid.js";
|
||||
import { getAuction } from "../read/getAuction.js";
|
||||
import { getWinningBid } from "../read/getWinningBid.js";
|
||||
|
||||
export type BidInAuctionParams = {
|
||||
auctionId: bigint;
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { isNativeTokenAddress } from "../../../../constants/addresses.js";
|
||||
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { bidInAuction as generatedBidInAuction } from "../../__generated__/IEnglishAuctions/write/bidInAuction.js";
|
||||
import { getAuction } from "../../read/english-auction/getAuction.js";
|
||||
import { getAuction } from "../read/getAuction.js";
|
||||
|
||||
export type BuyoutAuctionParams = {
|
||||
auctionId: bigint;
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { cancelAuction as generatedCancelAuction } from "../../__generated__/IEnglishAuctions/write/cancelAuction.js";
|
||||
import { getWinningBid } from "../../read/english-auction/getWinningBid.js";
|
||||
import { getWinningBid } from "../read/getWinningBid.js";
|
||||
|
||||
export type CancelAuctionParams = {
|
||||
auctionId: bigint;
|
||||
+1
-2
@@ -2,7 +2,7 @@ import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { multicall } from "../../../common/__generated__/IMulticall/write/multicall.js";
|
||||
import { encodeCollectAuctionPayout } from "../../__generated__/IEnglishAuctions/write/collectAuctionPayout.js";
|
||||
import { encodeCollectAuctionTokens } from "../../__generated__/IEnglishAuctions/write/collectAuctionTokens.js";
|
||||
import { getWinningBid } from "../../read/english-auction/getWinningBid.js";
|
||||
import { getWinningBid } from "../read/getWinningBid.js";
|
||||
|
||||
export type ExecuteSaleParams = {
|
||||
auctionId: bigint;
|
||||
@@ -35,7 +35,6 @@ export function executeSale(
|
||||
contract: options.contract,
|
||||
auctionId: options.auctionId,
|
||||
});
|
||||
console.log("*** winning bid", winningBid);
|
||||
if (!winningBid) {
|
||||
throw new Error("Auction is still active");
|
||||
}
|
||||
@@ -1,569 +0,0 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
|
||||
import {
|
||||
TEST_ACCOUNT_A,
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../test/src/test-wallets.js";
|
||||
import { type ThirdwebContract, getContract } from "../../contract/contract.js";
|
||||
import { parseEventLogs } from "../../event/actions/parse-logs.js";
|
||||
import { balanceOf } from "../../exports/extensions/erc721.js";
|
||||
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { getContractMetadata } from "../common/read/getContractMetadata.js";
|
||||
import { approve } from "../erc721/__generated__/IERC721A/write/approve.js";
|
||||
import { tokensMintedEvent } from "../erc721/__generated__/IMintableERC721/events/TokensMinted.js";
|
||||
import { mintTo } from "../erc721/write/mintTo.js";
|
||||
import { deployERC721Contract } from "../prebuilts/deploy-erc721.js";
|
||||
import { deployMarketplaceContract } from "../prebuilts/deploy-marketplace.js";
|
||||
import { newListingEvent } from "./__generated__/IDirectListings/events/NewListing.js";
|
||||
import { totalListings } from "./__generated__/IDirectListings/read/totalListings.js";
|
||||
import { newAuctionEvent } from "./__generated__/IEnglishAuctions/events/NewAuction.js";
|
||||
import { totalAuctions } from "./__generated__/IEnglishAuctions/read/totalAuctions.js";
|
||||
import { getAllListings } from "./read/direct/getAllListings.js";
|
||||
import { getAllValidListings } from "./read/direct/getAllValidListings.js";
|
||||
import { getListing } from "./read/direct/getListing.js";
|
||||
import { getAllAuctions } from "./read/english-auction/getAllAuctions.js";
|
||||
import { getAllValidAuctions } from "./read/english-auction/getAllValidAuctions.js";
|
||||
import { getAuction } from "./read/english-auction/getAuction.js";
|
||||
import { getWinningBid } from "./read/english-auction/getWinningBid.js";
|
||||
import { isListingValid } from "./utils.js";
|
||||
import { buyFromListing } from "./write/direct/buyFromListing.js";
|
||||
import { createListing } from "./write/direct/createListing.js";
|
||||
import { bidInAuction } from "./write/english-auction/bidInAuction.js";
|
||||
import { buyoutAuction } from "./write/english-auction/buyoutAuction.js";
|
||||
import { createAuction } from "./write/english-auction/createAuction.js";
|
||||
// import { executeSale } from "./write/english-auction/executeSale.js";
|
||||
|
||||
describe.runIf(process.env.TW_SECRET_KEY)(
|
||||
"Marketplace",
|
||||
{
|
||||
timeout: 120000,
|
||||
},
|
||||
() => {
|
||||
let marketplaceContract: ThirdwebContract;
|
||||
let erc721Contract: ThirdwebContract;
|
||||
|
||||
beforeAll(async () => {
|
||||
const marketplaceAddress = await deployMarketplaceContract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestMarketPlace",
|
||||
},
|
||||
});
|
||||
marketplaceContract = getContract({
|
||||
address: marketplaceAddress,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
// also deploy an ERC721 contract
|
||||
const erc721Address = await deployERC721Contract({
|
||||
type: "TokenERC721",
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestERC721",
|
||||
},
|
||||
});
|
||||
|
||||
erc721Contract = getContract({
|
||||
address: erc721Address,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
// does a lot of stuff, this may take a while
|
||||
}, 60_000);
|
||||
|
||||
describe("Deployment", () => {
|
||||
it("should deploy marketplace contract", () => {
|
||||
expect(marketplaceContract).toBeDefined();
|
||||
});
|
||||
it("should have the correct name", async () => {
|
||||
const metadata = await getContractMetadata({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(metadata.name).toBe("TestMarketPlace");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Direct Listings", () => {
|
||||
let nftTokenId: bigint;
|
||||
beforeAll(async () => {
|
||||
const mintTransaction = mintTo({
|
||||
contract: erc721Contract,
|
||||
to: TEST_ACCOUNT_A.address,
|
||||
nft: { name: "Test:ERC721:DirectListing" },
|
||||
});
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction: mintTransaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const mintEvents = parseEventLogs({
|
||||
events: [tokensMintedEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(mintEvents.length).toBe(1);
|
||||
expect(mintEvents[0]?.args.tokenIdMinted).toBeDefined();
|
||||
|
||||
nftTokenId = mintEvents[0]?.args.tokenIdMinted as bigint;
|
||||
}, 30_000);
|
||||
|
||||
it("should work for basic listings (Native Currency)", async () => {
|
||||
// listings should be 0 length to start
|
||||
const listings = await getAllListings({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(listings.length).toBe(0);
|
||||
// oh and so should totalListings
|
||||
expect(await totalListings({ contract: marketplaceContract })).toBe(0n);
|
||||
|
||||
// approve first
|
||||
const approveTx = approve({
|
||||
contract: erc721Contract,
|
||||
to: marketplaceContract.address,
|
||||
tokenId: nftTokenId,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: approveTx,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const transaction = createListing({
|
||||
contract: marketplaceContract,
|
||||
assetContractAddress: erc721Contract.address,
|
||||
tokenId: nftTokenId,
|
||||
pricePerToken: "1",
|
||||
});
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const listingEvents = parseEventLogs({
|
||||
events: [newListingEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(listingEvents.length).toBe(1);
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: OK in tests
|
||||
const listingEvent = listingEvents[0]!;
|
||||
|
||||
expect(listingEvent.args.listingCreator).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listingEvent.args.assetContract).toBe(erc721Contract.address);
|
||||
|
||||
// at this point listings should be 1
|
||||
const listingsAfter = await getAllListings({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(listingsAfter.length).toBe(1);
|
||||
// valid listings should also be 1!
|
||||
const validListings = await getAllValidListings({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(validListings.length).toBe(1);
|
||||
// and totalListings should be 1
|
||||
expect(await totalListings({ contract: marketplaceContract })).toBe(1n);
|
||||
|
||||
// explicitly retrieve the listing!
|
||||
const listing = await getListing({
|
||||
contract: marketplaceContract,
|
||||
listingId: listingEvent.args.listingId,
|
||||
});
|
||||
|
||||
expect(listing).toBeDefined();
|
||||
expect(listing.status).toBe("ACTIVE");
|
||||
expect(listing.creatorAddress).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listing.assetContractAddress).toBe(erc721Contract.address);
|
||||
expect(listing.tokenId).toBe(nftTokenId);
|
||||
expect(listing.currencyValuePerToken).toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "1",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 1000000000000000000n,
|
||||
}
|
||||
`);
|
||||
expect(listing.asset).toMatchInlineSnapshot(`
|
||||
{
|
||||
"id": 0n,
|
||||
"metadata": {
|
||||
"name": "Test:ERC721:DirectListing",
|
||||
},
|
||||
"owner": null,
|
||||
"tokenURI": "ipfs://QmewoGwuooC1Vno1ZTnmKmEUaimDygraR1fEEHdTeraF34/0",
|
||||
"type": "ERC721",
|
||||
}
|
||||
`);
|
||||
|
||||
// check the listing is valid
|
||||
const listingValidity = await isListingValid({
|
||||
listing,
|
||||
contract: marketplaceContract,
|
||||
quantity: 1n,
|
||||
});
|
||||
|
||||
expect(listingValidity).toMatchInlineSnapshot(`
|
||||
{
|
||||
"valid": true,
|
||||
}
|
||||
`);
|
||||
|
||||
// expect the buyer to have an initial balance of 0
|
||||
await expect(
|
||||
balanceOf({
|
||||
contract: erc721Contract,
|
||||
owner: TEST_ACCOUNT_B.address,
|
||||
}),
|
||||
).resolves.toBe(0n);
|
||||
|
||||
const buyTx = buyFromListing({
|
||||
contract: marketplaceContract,
|
||||
listingId: listingEvent.args.listingId,
|
||||
recipient: TEST_ACCOUNT_B.address,
|
||||
quantity: 1n,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: buyTx,
|
||||
account: TEST_ACCOUNT_B,
|
||||
});
|
||||
|
||||
// expect the buyer to have a new balance of 1
|
||||
await expect(
|
||||
balanceOf({
|
||||
contract: erc721Contract,
|
||||
owner: TEST_ACCOUNT_B.address,
|
||||
}),
|
||||
).resolves.toBe(1n);
|
||||
// expect the seller to no longer have the token
|
||||
await expect(
|
||||
balanceOf({
|
||||
contract: erc721Contract,
|
||||
owner: TEST_ACCOUNT_A.address,
|
||||
}),
|
||||
).resolves.toBe(0n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("English Auctions", () => {
|
||||
let nftTokenId: bigint;
|
||||
beforeAll(async () => {
|
||||
const mintTransaction = mintTo({
|
||||
contract: erc721Contract,
|
||||
to: TEST_ACCOUNT_A.address,
|
||||
nft: { name: "Test:ERC721:EnglishAuction" },
|
||||
});
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction: mintTransaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const mintEvents = parseEventLogs({
|
||||
events: [tokensMintedEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(mintEvents.length).toBe(1);
|
||||
expect(mintEvents[0]?.args.tokenIdMinted).toBeDefined();
|
||||
|
||||
nftTokenId = mintEvents[0]?.args.tokenIdMinted as bigint;
|
||||
}, 30_000);
|
||||
|
||||
it("should work for basic auctions (Native Currency)", async () => {
|
||||
// auctions should be 0 length to start
|
||||
const auctions = await getAllAuctions({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(auctions.length).toBe(0);
|
||||
// oh and so should totalAuctions
|
||||
expect(await totalAuctions({ contract: marketplaceContract })).toBe(0n);
|
||||
|
||||
// approve first
|
||||
const approveTx = approve({
|
||||
contract: erc721Contract,
|
||||
to: marketplaceContract.address,
|
||||
tokenId: nftTokenId,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: approveTx,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const transaction = createAuction({
|
||||
contract: marketplaceContract,
|
||||
assetContractAddress: erc721Contract.address,
|
||||
tokenId: nftTokenId,
|
||||
minimumBidAmount: "1",
|
||||
buyoutBidAmount: "10",
|
||||
});
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const listingEvents = parseEventLogs({
|
||||
events: [newAuctionEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(listingEvents.length).toBe(1);
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: OK in tests
|
||||
const listingEvent = listingEvents[0]!;
|
||||
|
||||
expect(listingEvent.args.auctionCreator).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listingEvent.args.assetContract).toBe(erc721Contract.address);
|
||||
|
||||
// at this point auctions should be 1
|
||||
const auctionsAfter = await getAllAuctions({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(auctionsAfter.length).toBe(1);
|
||||
// valid auctions should also be 1!
|
||||
const validAuctions = await getAllValidAuctions({
|
||||
contract: marketplaceContract,
|
||||
});
|
||||
expect(validAuctions.length).toBe(1);
|
||||
// and totalauctions should be 1
|
||||
expect(await totalAuctions({ contract: marketplaceContract })).toBe(1n);
|
||||
|
||||
// explicitly retrieve the listing!
|
||||
const listing = await getAuction({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listingEvent.args.auctionId,
|
||||
});
|
||||
|
||||
expect(listing).toBeDefined();
|
||||
expect(listing.status).toBe("ACTIVE");
|
||||
expect(listing.creatorAddress).toBe(TEST_ACCOUNT_A.address);
|
||||
expect(listing.assetContractAddress).toBe(erc721Contract.address);
|
||||
expect(listing.tokenId).toBe(nftTokenId);
|
||||
expect(listing.minimumBidCurrencyValue).toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "1",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 1000000000000000000n,
|
||||
}
|
||||
`);
|
||||
expect(listing.buyoutCurrencyValue).toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "10",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 10000000000000000000n,
|
||||
}
|
||||
`);
|
||||
expect(listing.asset).toMatchInlineSnapshot(`
|
||||
{
|
||||
"id": 1n,
|
||||
"metadata": {
|
||||
"name": "Test:ERC721:EnglishAuction",
|
||||
},
|
||||
"owner": null,
|
||||
"tokenURI": "ipfs://QmQSqsA3fJu9dLhBb7FxMf8LxsFtKL3dXzPdQbyEVhGjTF/0",
|
||||
"type": "ERC721",
|
||||
}
|
||||
`);
|
||||
|
||||
// check for a winning bid
|
||||
await expect(
|
||||
getWinningBid({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
// invalid bid amount 1: 0 bid (0 is not allowed)
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "0",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Bid amount is zero]",
|
||||
);
|
||||
// invalid bid amount 2: 11 bid (over buyout)
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "11",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Bid amount is above the buyout amount]",
|
||||
);
|
||||
// invalid bid amount 3: below minimum bid (but not 0)
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "0.5",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Bid amount is below the minimum bid amount]",
|
||||
);
|
||||
|
||||
// valid bid amount: "2"
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "2",
|
||||
}),
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// check for a new winning bid
|
||||
await expect(
|
||||
getWinningBid({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"bidAmountWei": 2000000000000000000n,
|
||||
"bidderAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
||||
"currencyAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
"currencyValue": {
|
||||
"decimals": 18,
|
||||
"displayValue": "2",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 2000000000000000000n,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
// invalid bid amount, above minimum but below existing winning bid
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "1.5",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Bid amount is too low to outbid the existing winning bid]",
|
||||
);
|
||||
|
||||
// invalid bid amount, above winning bit but below bid + bidBuffer (default 500bps)
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
// 2 * 1.05 = 2.1, so 2.05 is invalid
|
||||
bidAmount: "2.05",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Bid amount is too low to outbid the existing winning bid]",
|
||||
);
|
||||
|
||||
// actually outbid the winning bid
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: bidInAuction({
|
||||
auctionId: listing.id,
|
||||
contract: marketplaceContract,
|
||||
bidAmount: "3",
|
||||
}),
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// check for a new winning bid
|
||||
await expect(
|
||||
getWinningBid({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"bidAmountWei": 3000000000000000000n,
|
||||
"bidderAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
||||
"currencyAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
"currencyValue": {
|
||||
"decimals": 18,
|
||||
"displayValue": "3",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 3000000000000000000n,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
// buyout auction
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: buyoutAuction({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// check for a new winning bid
|
||||
await expect(
|
||||
getWinningBid({
|
||||
contract: marketplaceContract,
|
||||
auctionId: listing.id,
|
||||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"bidAmountWei": 10000000000000000000n,
|
||||
"bidderAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
||||
"currencyAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
"currencyValue": {
|
||||
"decimals": 18,
|
||||
"displayValue": "10",
|
||||
"name": "Anvil Ether",
|
||||
"symbol": "ETH",
|
||||
"value": 10000000000000000000n,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
// // execute the sale
|
||||
// await expect(
|
||||
// sendAndConfirmTransaction({
|
||||
// account: TEST_ACCOUNT_A,
|
||||
// transaction: executeSale({
|
||||
// contract: marketplaceContract,
|
||||
// auctionId: listing.id,
|
||||
// }),
|
||||
// }),
|
||||
// ).resolves.toBeDefined();
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,346 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "../../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
|
||||
import {
|
||||
TEST_ACCOUNT_A,
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../../test/src/test-wallets.js";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
getContract,
|
||||
} from "../../../contract/contract.js";
|
||||
import { getDeployedInfraContract } from "../../../contract/deployment/utils/infra.js";
|
||||
import { parseEventLogs } from "../../../event/actions/parse-logs.js";
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { getBalance } from "../../erc20/read/getBalance.js";
|
||||
import { approve as approveErc20 } from "../../erc20/write/approve.js";
|
||||
import { deposit } from "../../erc20/write/deposit.js";
|
||||
import { ownerOf } from "../../erc721/__generated__/IERC721A/read/ownerOf.js";
|
||||
import { approve as approveErc721 } from "../../erc721/__generated__/IERC721A/write/approve.js";
|
||||
import { tokensMintedEvent } from "../../erc721/__generated__/IMintableERC721/events/TokensMinted.js";
|
||||
import { mintTo } from "../../erc721/write/mintTo.js";
|
||||
import { deployERC721Contract } from "../../prebuilts/deploy-erc721.js";
|
||||
import { deployMarketplaceContract } from "../../prebuilts/deploy-marketplace.js";
|
||||
import { newOfferEvent } from "../__generated__/IOffers/events/NewOffer.js";
|
||||
import { totalOffers } from "../__generated__/IOffers/read/totalOffers.js";
|
||||
import { cancelOffer } from "../__generated__/IOffers/write/cancelOffer.js";
|
||||
import { getAllOffers } from "./read/getAllOffers.js";
|
||||
import { getAllValidOffers } from "./read/getAllValidOffers.js";
|
||||
import { getOffer } from "./read/getOffer.js";
|
||||
import { acceptOffer } from "./write/acceptOffer.js";
|
||||
import { makeOffer } from "./write/makeOffer.js";
|
||||
|
||||
describe("Marketplace: Offers", () => {
|
||||
let nftTokenId: bigint;
|
||||
let marketplaceContract: ThirdwebContract;
|
||||
let erc721Contract: ThirdwebContract;
|
||||
let WETH9: ThirdwebContract;
|
||||
beforeAll(async () => {
|
||||
const marketplaceAddress = await deployMarketplaceContract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestMarketPlace",
|
||||
},
|
||||
});
|
||||
marketplaceContract = getContract({
|
||||
address: marketplaceAddress,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
// also deploy an ERC721 contract
|
||||
const erc721Address = await deployERC721Contract({
|
||||
type: "TokenERC721",
|
||||
account: TEST_ACCOUNT_A,
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
params: {
|
||||
name: "TestERC721",
|
||||
},
|
||||
});
|
||||
|
||||
erc721Contract = getContract({
|
||||
address: erc721Address,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
});
|
||||
|
||||
const mintTransaction = mintTo({
|
||||
contract: erc721Contract,
|
||||
to: TEST_ACCOUNT_A.address,
|
||||
nft: { name: "Test:ERC721:Offers" },
|
||||
});
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
transaction: mintTransaction,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const mintEvents = parseEventLogs({
|
||||
events: [tokensMintedEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
|
||||
expect(mintEvents.length).toBe(1);
|
||||
expect(mintEvents[0]?.args.tokenIdMinted).toBeDefined();
|
||||
|
||||
nftTokenId = mintEvents[0]?.args.tokenIdMinted as bigint;
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: test only and we know it will exist here because it gets deployed in the beforeAll
|
||||
WETH9 = (await getDeployedInfraContract({
|
||||
chain: ANVIL_CHAIN,
|
||||
contractId: "WETH9",
|
||||
constructorParams: [],
|
||||
client: TEST_CLIENT,
|
||||
}))!;
|
||||
|
||||
expect(WETH9.address).toMatchInlineSnapshot(
|
||||
`"0x81e609b897393731a3d23c1d311330340cebb9e9"`,
|
||||
);
|
||||
// does a lot of stuff, this may take a while
|
||||
}, 120_000);
|
||||
|
||||
// NOTE: this test suite relies on the order of operations in order to not re-deploy contracts over and over
|
||||
// this is generally not ideal, but we're doing it here to save time
|
||||
|
||||
it("should start out with 0 offers", async () => {
|
||||
await expect(totalOffers({ contract: marketplaceContract })).resolves.toBe(
|
||||
0n,
|
||||
);
|
||||
await expect(
|
||||
getAllOffers({ contract: marketplaceContract }),
|
||||
).resolves.toMatchInlineSnapshot("[]");
|
||||
await expect(
|
||||
getAllValidOffers({ contract: marketplaceContract }),
|
||||
).resolves.toMatchInlineSnapshot("[]");
|
||||
});
|
||||
|
||||
it("should allow a user to create an offer", async () => {
|
||||
// first deposit some tokens into the WETH9 contract
|
||||
// do do that, determine the address of the WETH9 contract
|
||||
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: deposit({ contract: WETH9, amount: "10" }),
|
||||
account: TEST_ACCOUNT_B,
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// check to make sure the weth9 balance is correct
|
||||
await expect(
|
||||
getBalance({ contract: WETH9, address: TEST_ACCOUNT_B.address }),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "10",
|
||||
"name": "Wrapped Ether",
|
||||
"symbol": "WETH",
|
||||
"value": 10000000000000000000n,
|
||||
}
|
||||
`);
|
||||
|
||||
// now approve the marketplace contract to spend the WETH9 tokens
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: approveErc20({
|
||||
contract: WETH9,
|
||||
spender: marketplaceContract.address,
|
||||
amount: "10",
|
||||
}),
|
||||
account: TEST_ACCOUNT_B,
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// now create an offer
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: makeOffer({
|
||||
assetContractAddress: erc721Contract.address,
|
||||
tokenId: nftTokenId,
|
||||
contract: marketplaceContract,
|
||||
currencyContractAddress: NATIVE_TOKEN_ADDRESS,
|
||||
offerExpiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24),
|
||||
totalOffer: "1",
|
||||
}),
|
||||
});
|
||||
expect(receipt).toBeDefined();
|
||||
|
||||
const [eventLog] = parseEventLogs({
|
||||
events: [newOfferEvent()],
|
||||
logs: receipt.logs,
|
||||
});
|
||||
expect(eventLog).toBeDefined();
|
||||
expect(eventLog?.args.offer.assetContract).toBe(erc721Contract.address);
|
||||
expect(eventLog?.args.offer.tokenId).toBe(nftTokenId);
|
||||
expect(eventLog?.args.offer.offeror).toBe(TEST_ACCOUNT_B.address);
|
||||
});
|
||||
|
||||
it("should have 1 offer", async () => {
|
||||
await expect(totalOffers({ contract: marketplaceContract })).resolves.toBe(
|
||||
1n,
|
||||
);
|
||||
await expect(
|
||||
getAllOffers({ contract: marketplaceContract }),
|
||||
).resolves.toHaveLength(1);
|
||||
await expect(
|
||||
getAllValidOffers({ contract: marketplaceContract }),
|
||||
).resolves.resolves.toHaveLength(1);
|
||||
const offer = await getOffer({
|
||||
contract: marketplaceContract,
|
||||
offerId: 0n,
|
||||
});
|
||||
expect(offer.status).toMatchInlineSnapshot(`"ACTIVE"`);
|
||||
expect(offer.assetContractAddress).toBe(erc721Contract.address);
|
||||
expect(offer.tokenId).toBe(nftTokenId);
|
||||
expect(offer.offerorAddress).toBe(TEST_ACCOUNT_B.address);
|
||||
expect(offer.asset).toMatchInlineSnapshot(`
|
||||
{
|
||||
"id": 0n,
|
||||
"metadata": {
|
||||
"name": "Test:ERC721:Offers",
|
||||
},
|
||||
"owner": null,
|
||||
"tokenURI": "ipfs://QmRk3sj4XxUx61SxBxt24uPJXDCf1G9G6iHAJgM6tbAuwD/0",
|
||||
"type": "ERC721",
|
||||
}
|
||||
`);
|
||||
expect(offer.currencyValue).toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "1",
|
||||
"name": "Wrapped Ether",
|
||||
"symbol": "WETH",
|
||||
"value": 1000000000000000000n,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should allow a user to cancel an offer", async () => {
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: cancelOffer({
|
||||
contract: marketplaceContract,
|
||||
offerId: 0n,
|
||||
}),
|
||||
account: TEST_ACCOUNT_B,
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
await expect(totalOffers({ contract: marketplaceContract })).resolves.toBe(
|
||||
1n,
|
||||
);
|
||||
await expect(
|
||||
getAllOffers({ contract: marketplaceContract }),
|
||||
).resolves.toHaveLength(1);
|
||||
// valid offers should be 0
|
||||
await expect(
|
||||
getAllValidOffers({ contract: marketplaceContract }),
|
||||
).resolves.resolves.toHaveLength(0);
|
||||
const offer = await getOffer({
|
||||
contract: marketplaceContract,
|
||||
offerId: 0n,
|
||||
});
|
||||
expect(offer.status).toMatchInlineSnapshot(`"CANCELLED"`);
|
||||
|
||||
// should no longer be able to accept the offer
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: acceptOffer({
|
||||
contract: marketplaceContract,
|
||||
offerId: 0n,
|
||||
}),
|
||||
account: TEST_ACCOUNT_B,
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
"[Error: Offer is not active]",
|
||||
);
|
||||
});
|
||||
|
||||
it("should allow an offer to be accepted", async () => {
|
||||
// first gotta create a new offer
|
||||
// thankfully we already have the WETH9 tokens approved and ready to go
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
account: TEST_ACCOUNT_B,
|
||||
transaction: makeOffer({
|
||||
assetContractAddress: erc721Contract.address,
|
||||
tokenId: nftTokenId,
|
||||
contract: marketplaceContract,
|
||||
currencyContractAddress: NATIVE_TOKEN_ADDRESS,
|
||||
offerExpiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24),
|
||||
// 2 WETH this time!
|
||||
totalOffer: "2",
|
||||
}),
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// check for the new offer
|
||||
await expect(totalOffers({ contract: marketplaceContract })).resolves.toBe(
|
||||
2n,
|
||||
);
|
||||
await expect(
|
||||
getAllOffers({ contract: marketplaceContract }),
|
||||
).resolves.toHaveLength(2);
|
||||
await expect(
|
||||
getAllValidOffers({ contract: marketplaceContract }),
|
||||
// should only have 1 valid offer!
|
||||
).resolves.resolves.toHaveLength(1);
|
||||
const offer = await getOffer({
|
||||
contract: marketplaceContract,
|
||||
offerId: 1n,
|
||||
});
|
||||
expect(offer.status).toMatchInlineSnapshot(`"ACTIVE"`);
|
||||
|
||||
// have to approve the marketplace contract to control the NFT!
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: approveErc721({
|
||||
contract: erc721Contract,
|
||||
to: marketplaceContract.address,
|
||||
tokenId: nftTokenId,
|
||||
}),
|
||||
account: TEST_ACCOUNT_A,
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// now we accept the offer!
|
||||
await expect(
|
||||
sendAndConfirmTransaction({
|
||||
transaction: acceptOffer({
|
||||
contract: marketplaceContract,
|
||||
offerId: 1n,
|
||||
}),
|
||||
account: TEST_ACCOUNT_A,
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// check the balances
|
||||
await expect(
|
||||
getBalance({ contract: WETH9, address: TEST_ACCOUNT_A.address }),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "2",
|
||||
"name": "Wrapped Ether",
|
||||
"symbol": "WETH",
|
||||
"value": 2000000000000000000n,
|
||||
}
|
||||
`);
|
||||
await expect(
|
||||
getBalance({ contract: WETH9, address: TEST_ACCOUNT_B.address }),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"decimals": 18,
|
||||
"displayValue": "8",
|
||||
"name": "Wrapped Ether",
|
||||
"symbol": "WETH",
|
||||
"value": 8000000000000000000n,
|
||||
}
|
||||
`);
|
||||
// check the owner of the NFT
|
||||
await expect(
|
||||
ownerOf({ contract: erc721Contract, tokenId: nftTokenId }),
|
||||
).resolves.toBe(TEST_ACCOUNT_B.address);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import { eth_getBlockByNumber } from "../../../../rpc/actions/eth_getBlockByNumber.js";
|
||||
import { getRpcClient } from "../../../../rpc/rpc.js";
|
||||
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { min } from "../../../../utils/bigint.js";
|
||||
import { getAllOffers as getAllOffersGenerated } from "../../__generated__/IOffers/read/getAllOffers.js";
|
||||
import { totalOffers } from "../../__generated__/IOffers/read/totalOffers.js";
|
||||
import { getAllInBatches } from "../../utils.js";
|
||||
import type { Offer } from "../types.js";
|
||||
import { mapOffer } from "../utils.js";
|
||||
|
||||
const DEFAULT_QUERY_ALL_COUNT = 100n;
|
||||
|
||||
export type GetAllOffersParams = {
|
||||
/**
|
||||
* The start index of the offers to retrieve.
|
||||
* @default 0
|
||||
*/
|
||||
start?: number;
|
||||
/**
|
||||
* The number of offers to retrieve.
|
||||
* @default 100
|
||||
*/
|
||||
count?: bigint;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves all offers based on the provided options.
|
||||
* @param options - The options for retrieving the offers.
|
||||
* @returns A promise that resolves to the offers array.
|
||||
* @extension MARKETPLACE
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* import { getAllOffers } from "thirdweb/extensions/marketplace";
|
||||
*
|
||||
* const listings = await getAllOffers({ contract, start: 0, count: 10 });
|
||||
* ```
|
||||
*/
|
||||
export async function getAllOffers(
|
||||
options: BaseTransactionOptions<GetAllOffersParams>,
|
||||
): Promise<Offer[]> {
|
||||
const totalCount = await totalOffers(options);
|
||||
// if the totalListingCount is 0, return an empty array and skip all other work
|
||||
if (totalCount === 0n) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const start = BigInt(options.start || 0);
|
||||
const count = BigInt(options.count || DEFAULT_QUERY_ALL_COUNT);
|
||||
const end = min(totalCount, start + count);
|
||||
|
||||
const rpcClient = getRpcClient(options.contract);
|
||||
const [rawOffers, latestBlock] = await Promise.all([
|
||||
getAllInBatches(
|
||||
(startId, endId) =>
|
||||
getAllOffersGenerated({ contract: options.contract, startId, endId }),
|
||||
{
|
||||
start,
|
||||
end,
|
||||
maxSize: DEFAULT_QUERY_ALL_COUNT,
|
||||
},
|
||||
// flatten the array of arrays
|
||||
).then((listings) => listings.flat()),
|
||||
// get the latest block number once
|
||||
eth_getBlockByNumber(rpcClient, {
|
||||
blockTag: "latest",
|
||||
}),
|
||||
]);
|
||||
|
||||
return await Promise.all(
|
||||
rawOffers.map((rawOffer) =>
|
||||
mapOffer({
|
||||
contract: options.contract,
|
||||
latestBlock,
|
||||
rawOffer,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { eth_getBlockByNumber } from "../../../../rpc/actions/eth_getBlockByNumber.js";
|
||||
import { getRpcClient } from "../../../../rpc/rpc.js";
|
||||
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { min } from "../../../../utils/bigint.js";
|
||||
import { getAllValidOffers as getAllValidOffersGenerated } from "../../__generated__/IOffers/read/getAllValidOffers.js";
|
||||
import { totalOffers } from "../../__generated__/IOffers/read/totalOffers.js";
|
||||
import { getAllInBatches } from "../../utils.js";
|
||||
import type { Offer } from "../types.js";
|
||||
import { mapOffer } from "../utils.js";
|
||||
|
||||
const DEFAULT_QUERY_ALL_COUNT = 100n;
|
||||
|
||||
export type GetAllValidOffersParams = {
|
||||
/**
|
||||
* The start index of the offers to retrieve.
|
||||
* @default 0
|
||||
*/
|
||||
start?: number;
|
||||
/**
|
||||
* The number of offers to retrieve.
|
||||
* @default 100
|
||||
*/
|
||||
count?: bigint;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves all valid offers based on the provided options.
|
||||
* @param options - The options for retrieving the valid offers.
|
||||
* @returns A promise that resolves to the offers array.
|
||||
* @extension MARKETPLACE
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* import { getAllValidOffers } from "thirdweb/extensions/marketplace";
|
||||
*
|
||||
* const validOffers = await getAllValidOffers({ contract, start: 0, count: 10 });
|
||||
* ```
|
||||
*/
|
||||
export async function getAllValidOffers(
|
||||
options: BaseTransactionOptions<GetAllValidOffersParams>,
|
||||
): Promise<Offer[]> {
|
||||
const totalCount = await totalOffers(options);
|
||||
// if the totalOffers count is 0, return an empty array and skip all other work
|
||||
if (totalCount === 0n) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const start = BigInt(options.start || 0);
|
||||
const count = BigInt(options.count || DEFAULT_QUERY_ALL_COUNT);
|
||||
const end = min(totalCount, start + count);
|
||||
|
||||
const rpcClient = getRpcClient(options.contract);
|
||||
const [rawOffers, latestBlock] = await Promise.all([
|
||||
getAllInBatches(
|
||||
(startId, endId) =>
|
||||
getAllValidOffersGenerated({
|
||||
contract: options.contract,
|
||||
startId,
|
||||
endId,
|
||||
}),
|
||||
{
|
||||
start,
|
||||
end,
|
||||
maxSize: DEFAULT_QUERY_ALL_COUNT,
|
||||
},
|
||||
// flatten the array of arrays
|
||||
).then((listings) => listings.flat()),
|
||||
// get the latest block number once
|
||||
eth_getBlockByNumber(rpcClient, {
|
||||
blockTag: "latest",
|
||||
}),
|
||||
]);
|
||||
|
||||
return await Promise.all(
|
||||
rawOffers.map((rawOffer) =>
|
||||
mapOffer({
|
||||
contract: options.contract,
|
||||
latestBlock,
|
||||
rawOffer,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { eth_getBlockByNumber } from "../../../../rpc/actions/eth_getBlockByNumber.js";
|
||||
import { getRpcClient } from "../../../../rpc/rpc.js";
|
||||
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import {
|
||||
type GetOfferParams as GetOfferParamsGenerated,
|
||||
getOffer as getOfferGenerated,
|
||||
} from "../../__generated__/IOffers/read/getOffer.js";
|
||||
import type { Offer } from "../types.js";
|
||||
import { mapOffer } from "../utils.js";
|
||||
|
||||
export type GetOfferParams = GetOfferParamsGenerated;
|
||||
|
||||
/**
|
||||
* Retrieves an offer based on the provided options.
|
||||
* @param options - The options for retrieving the offer.
|
||||
* @returns A promise that resolves to the offer.
|
||||
* @extension MARKETPLACE
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* import { getOffer } from "thirdweb/extensions/marketplace";
|
||||
*
|
||||
* const listing = await getOffer({ contract, listingId: 1n });
|
||||
* ```
|
||||
*/
|
||||
export async function getOffer(
|
||||
options: BaseTransactionOptions<GetOfferParams>,
|
||||
): Promise<Offer> {
|
||||
const rpcClient = getRpcClient(options.contract);
|
||||
const [rawOffer, latestBlock] = await Promise.all([
|
||||
getOfferGenerated(options),
|
||||
eth_getBlockByNumber(rpcClient, {
|
||||
blockTag: "latest",
|
||||
}),
|
||||
]);
|
||||
|
||||
return mapOffer({
|
||||
contract: options.contract,
|
||||
latestBlock,
|
||||
rawOffer,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { Address } from "abitype";
|
||||
import type { NFT } from "../../../utils/nft/parseNft.js";
|
||||
import type { GetBalanceResult } from "../../erc20/read/getBalance.js";
|
||||
import type { ListingStatus } from "../types.js";
|
||||
|
||||
export type Offer = {
|
||||
id: bigint;
|
||||
offerorAddress: Address;
|
||||
assetContractAddress: Address;
|
||||
tokenId: bigint;
|
||||
quantity: bigint;
|
||||
currencyContractAddress: Address;
|
||||
currencyValue: GetBalanceResult;
|
||||
totalPrice: bigint;
|
||||
asset: NFT;
|
||||
endTimeInSeconds: bigint;
|
||||
status: ListingStatus;
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import { getContract } from "../../../contract/contract.js";
|
||||
import type { BaseTransactionOptions } from "../../../transaction/types.js";
|
||||
import { toTokens } from "../../../utils/units.js";
|
||||
import { getCurrencyMetadata } from "../../erc20/read/getCurrencyMetadata.js";
|
||||
import type { getOffer } from "../__generated__/IOffers/read/getOffer.js";
|
||||
import { computeStatus, getNFTAsset } from "../utils.js";
|
||||
import type { Offer } from "./types.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function mapOffer(
|
||||
options: BaseTransactionOptions<{
|
||||
latestBlock: { timestamp: bigint };
|
||||
rawOffer: Awaited<ReturnType<typeof getOffer>>;
|
||||
}>,
|
||||
): Promise<Offer> {
|
||||
const { latestBlock, rawOffer } = options;
|
||||
// process the listing
|
||||
const status = computeStatus({
|
||||
listingStatus: rawOffer.status,
|
||||
blockTimeStamp: latestBlock.timestamp,
|
||||
// startTimestamp is always 0 for offers (they only have an expiration time not a start time)
|
||||
startTimestamp: 0n,
|
||||
endTimestamp: rawOffer.expirationTimestamp,
|
||||
});
|
||||
|
||||
const [currencyValuePerToken, nftAsset] = await Promise.all([
|
||||
getCurrencyMetadata({
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawOffer.currency,
|
||||
}),
|
||||
}),
|
||||
getNFTAsset({
|
||||
...options,
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawOffer.assetContract,
|
||||
}),
|
||||
tokenId: rawOffer.tokenId,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
id: rawOffer.offerId,
|
||||
offerorAddress: rawOffer.offeror,
|
||||
assetContractAddress: rawOffer.assetContract,
|
||||
tokenId: rawOffer.tokenId,
|
||||
quantity: rawOffer.quantity,
|
||||
currencyContractAddress: rawOffer.currency,
|
||||
currencyValue: {
|
||||
...currencyValuePerToken,
|
||||
value: rawOffer.totalPrice,
|
||||
displayValue: toTokens(
|
||||
rawOffer.totalPrice,
|
||||
currencyValuePerToken.decimals,
|
||||
),
|
||||
},
|
||||
totalPrice: rawOffer.totalPrice,
|
||||
asset: nftAsset,
|
||||
endTimeInSeconds: rawOffer.expirationTimestamp,
|
||||
status,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { getContract } from "../../../../contract/contract.js";
|
||||
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { allowance } from "../../../erc20/__generated__/IERC20/read/allowance.js";
|
||||
import { balanceOf } from "../../../erc20/__generated__/IERC20/read/balanceOf.js";
|
||||
import {
|
||||
type AcceptOfferParams as GeneratedAcceptOfferParams,
|
||||
acceptOffer as generatedAcceptOffer,
|
||||
} from "../../__generated__/IOffers/write/acceptOffer.js";
|
||||
import { getOffer } from "../read/getOffer.js";
|
||||
|
||||
export type AcceptOfferParams = GeneratedAcceptOfferParams;
|
||||
|
||||
/**
|
||||
* Accepts an offer after performing necessary checks and validations.
|
||||
* Throws an error if the offer is not active, the offeror's balance is insufficient,
|
||||
* or the offeror's allowance is insufficient.
|
||||
*
|
||||
* @param options - The options for accepting the offer.
|
||||
* @returns A transaction object that can be sent to accept the offer.
|
||||
* @throws Error when sending the transaction if the offer is not active, the offeror's balance is insufficient,
|
||||
* or the offeror's allowance is insufficient.
|
||||
* @extension MARKETPLACE
|
||||
* @example
|
||||
* ```ts
|
||||
* import { acceptOffer } from "thirdweb/extensions/marketplace";
|
||||
*
|
||||
* const acceptOfferTx = acceptOffer({
|
||||
* contract,
|
||||
* offerId: 1n,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function acceptOffer(
|
||||
options: BaseTransactionOptions<AcceptOfferParams>,
|
||||
) {
|
||||
return generatedAcceptOffer({
|
||||
contract: options.contract,
|
||||
asyncParams: async () => {
|
||||
const offer = await getOffer({
|
||||
contract: options.contract,
|
||||
offerId: options.offerId,
|
||||
});
|
||||
if (offer.status !== "ACTIVE") {
|
||||
throw new Error("Offer is not active");
|
||||
}
|
||||
// check that the offeror has sufficient balance and has given approval to the contract
|
||||
const currencyContract = getContract({
|
||||
address: offer.currencyContractAddress,
|
||||
chain: options.contract.chain,
|
||||
client: options.contract.client,
|
||||
});
|
||||
const [offerorBalance, offerorAllowance] = await Promise.all([
|
||||
balanceOf({
|
||||
contract: currencyContract,
|
||||
address: offer.offerorAddress,
|
||||
}),
|
||||
allowance({
|
||||
contract: currencyContract,
|
||||
owner: offer.offerorAddress,
|
||||
// spender is the marketplace contract
|
||||
spender: options.contract.address,
|
||||
}),
|
||||
]);
|
||||
if (offerorBalance < offer.totalPrice) {
|
||||
throw new Error("Offeror balance is insufficient");
|
||||
}
|
||||
if (offerorAllowance < offer.totalPrice) {
|
||||
throw new Error("Offeror allowance is insufficient");
|
||||
}
|
||||
// if we get here we can accept!
|
||||
return { offerId: options.offerId };
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import type { Address } from "abitype";
|
||||
import { isNativeTokenAddress } from "../../../../constants/addresses.js";
|
||||
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
|
||||
import { makeOffer as makeOfferGenerated } from "../../__generated__/IOffers/write/makeOffer.js";
|
||||
|
||||
export type MakeOfferParams = {
|
||||
/**
|
||||
* The address of the asset contract to offer on.
|
||||
*/
|
||||
assetContractAddress: Address;
|
||||
/**
|
||||
* The ID of the token to make an offer for.
|
||||
*/
|
||||
tokenId: bigint;
|
||||
/**
|
||||
* The address of the currency contract to make an offer with. (ERC20 or native token address)
|
||||
*/
|
||||
currencyContractAddress: Address;
|
||||
/**
|
||||
* The datetime when the offer expires.
|
||||
*/
|
||||
offerExpiresAt: Date;
|
||||
/**
|
||||
* The quantity of the asset to make an offer for.
|
||||
* @default 1
|
||||
*/
|
||||
quantity?: bigint;
|
||||
} & (
|
||||
| {
|
||||
/**
|
||||
* The total offer amount for the NFT(s) in wei.
|
||||
*/
|
||||
totalOfferWei: bigint;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* The total offer amount for the NFT(s) in the currency's "Ether" unit
|
||||
*/
|
||||
totalOffer: string;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Makes an offer for any asset (ERC721 or ERC1155).
|
||||
*
|
||||
* @param options - The options for making the offer.
|
||||
* @returns A transaction object that can be sent to make the offer.
|
||||
* @extension MARKETPLACE
|
||||
* @example
|
||||
* ```ts
|
||||
* import { makeOffer } from "thirdweb/extensions/marketplace";
|
||||
*
|
||||
* const offerTx = makeOffer({
|
||||
* contract,
|
||||
* assetContractAddress: "0x1234567890123456789012345678901234567890",
|
||||
* tokenId: 1n,
|
||||
* currencyContractAddress: "0x1234567890123456789012345678901234567890",
|
||||
* offerExpiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24),
|
||||
* totalOffer: "1.0",
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export function makeOffer(options: BaseTransactionOptions<MakeOfferParams>) {
|
||||
return makeOfferGenerated({
|
||||
contract: options.contract,
|
||||
asyncParams: async () => {
|
||||
const [normalizedPrice, currency] = await Promise.all([
|
||||
(async () => {
|
||||
// if we already have the bid amount in wei, use that
|
||||
if ("totalOfferWei" in options) {
|
||||
return options.totalOfferWei;
|
||||
}
|
||||
// otherwise load the utility function and convert the amount
|
||||
const { convertErc20Amount } = await import(
|
||||
"../../../../utils/extensions/convert-erc20-amount.js"
|
||||
);
|
||||
return await convertErc20Amount({
|
||||
amount: options.totalOffer,
|
||||
chain: options.contract.chain,
|
||||
erc20Address: options.currencyContractAddress,
|
||||
client: options.contract.client,
|
||||
});
|
||||
})(),
|
||||
(async () => {
|
||||
// if it's not a native token address offer -> simply return the currency address
|
||||
if (!isNativeTokenAddress(options.currencyContractAddress)) {
|
||||
return options.currencyContractAddress;
|
||||
}
|
||||
// otherwise determine the wrapped native token address for the chain
|
||||
|
||||
// TODO: add known wrapped native token addresses for each chain on the chain config
|
||||
|
||||
const { getDeployedInfraContract } = await import(
|
||||
"../../../../contract/deployment/utils/infra.js"
|
||||
);
|
||||
// predict the address for the wrapped token
|
||||
const WETH9 = await getDeployedInfraContract({
|
||||
chain: options.contract.chain,
|
||||
client: options.contract.client,
|
||||
constructorParams: [],
|
||||
contractId: "WETH9",
|
||||
});
|
||||
|
||||
if (!WETH9?.address) {
|
||||
throw new Error(
|
||||
"WETH9 contract not found on the chain, how did the marketplace get deployed in the first place?",
|
||||
);
|
||||
}
|
||||
return WETH9.address;
|
||||
})(),
|
||||
]);
|
||||
|
||||
const expirationTimestamp = BigInt(
|
||||
Math.floor(options.offerExpiresAt.getTime() / 1000),
|
||||
);
|
||||
|
||||
return {
|
||||
params: {
|
||||
assetContract: options.assetContractAddress,
|
||||
currency,
|
||||
expirationTimestamp,
|
||||
quantity: options.quantity ?? 1n,
|
||||
tokenId: options.tokenId,
|
||||
totalPrice: normalizedPrice,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
import type { Address } from "abitype";
|
||||
import type { NFT } from "../../utils/nft/parseNft.js";
|
||||
import type { GetBalanceResult } from "../erc20/read/getBalance.js";
|
||||
|
||||
export type ListingStatus =
|
||||
| "UNSET"
|
||||
| "CREATED"
|
||||
@@ -9,38 +5,3 @@ export type ListingStatus =
|
||||
| "CANCELLED"
|
||||
| "ACTIVE"
|
||||
| "EXPIRED";
|
||||
|
||||
export type DirectListing = {
|
||||
id: bigint;
|
||||
creatorAddress: Address;
|
||||
assetContractAddress: Address;
|
||||
tokenId: bigint;
|
||||
quantity: bigint;
|
||||
currencyContractAddress: Address;
|
||||
currencyValuePerToken: GetBalanceResult;
|
||||
pricePerToken: bigint;
|
||||
asset: NFT;
|
||||
startTimeInSeconds: bigint;
|
||||
endTimeInSeconds: bigint;
|
||||
isReservedListing: boolean;
|
||||
status: ListingStatus;
|
||||
};
|
||||
|
||||
export type EnglishAuction = {
|
||||
id: bigint;
|
||||
creatorAddress: Address;
|
||||
assetContractAddress: Address;
|
||||
tokenId: bigint;
|
||||
quantity: bigint;
|
||||
currencyContractAddress: Address;
|
||||
minimumBidAmount: bigint;
|
||||
minimumBidCurrencyValue: GetBalanceResult;
|
||||
buyoutBidAmount: bigint;
|
||||
buyoutCurrencyValue: GetBalanceResult;
|
||||
timeBufferInSeconds: bigint;
|
||||
bidBufferBps: bigint;
|
||||
startTimeInSeconds: bigint;
|
||||
endTimeInSeconds: bigint;
|
||||
asset: NFT;
|
||||
status: ListingStatus;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { getContract } from "../../contract/contract.js";
|
||||
import type { BaseTransactionOptions } from "../../transaction/types.js";
|
||||
import type { NFT } from "../../utils/nft/parseNft.js";
|
||||
import { toTokens } from "../../utils/units.js";
|
||||
import { getCurrencyMetadata } from "../erc20/read/getCurrencyMetadata.js";
|
||||
import { isERC721 } from "../erc721/read/isERC721.js";
|
||||
import { isERC1155 } from "../erc1155/read/isERC1155.js";
|
||||
import type { getListing } from "./__generated__/IDirectListings/read/getListing.js";
|
||||
import type { getAuction } from "./__generated__/IEnglishAuctions/read/getAuction.js";
|
||||
import type { DirectListing, EnglishAuction, ListingStatus } from "./types.js";
|
||||
import type { ListingStatus } from "./types.js";
|
||||
|
||||
type GetAssetParams = {
|
||||
tokenId: bigint;
|
||||
@@ -24,7 +19,7 @@ type GetAssetParams = {
|
||||
* const nft = await getNFTAsset({ contract, tokenId: 1n });
|
||||
* ```
|
||||
*/
|
||||
async function getNFTAsset(
|
||||
export async function getNFTAsset(
|
||||
options: BaseTransactionOptions<GetAssetParams>,
|
||||
): Promise<NFT> {
|
||||
const [erc721, erc1155] = await Promise.all([
|
||||
@@ -46,7 +41,7 @@ async function getNFTAsset(
|
||||
}
|
||||
}
|
||||
|
||||
function computeStatus(options: {
|
||||
export function computeStatus(options: {
|
||||
listingStatus: number;
|
||||
blockTimeStamp: bigint;
|
||||
startTimestamp: bigint;
|
||||
@@ -74,134 +69,6 @@ function computeStatus(options: {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function mapDirectListing(
|
||||
options: BaseTransactionOptions<{
|
||||
latestBlock: { timestamp: bigint };
|
||||
rawListing: Awaited<ReturnType<typeof getListing>>;
|
||||
}>,
|
||||
): Promise<DirectListing> {
|
||||
const { latestBlock, rawListing } = options;
|
||||
// process the listing
|
||||
const status = computeStatus({
|
||||
listingStatus: rawListing.status,
|
||||
blockTimeStamp: latestBlock.timestamp,
|
||||
startTimestamp: rawListing.startTimestamp,
|
||||
endTimestamp: rawListing.endTimestamp,
|
||||
});
|
||||
|
||||
const [currencyValuePerToken, nftAsset] = await Promise.all([
|
||||
getCurrencyMetadata({
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawListing.currency,
|
||||
}),
|
||||
}),
|
||||
getNFTAsset({
|
||||
...options,
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawListing.assetContract,
|
||||
}),
|
||||
tokenId: rawListing.tokenId,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
id: rawListing.listingId,
|
||||
creatorAddress: rawListing.listingCreator,
|
||||
assetContractAddress: rawListing.assetContract,
|
||||
tokenId: rawListing.tokenId,
|
||||
quantity: rawListing.quantity,
|
||||
currencyContractAddress: rawListing.currency,
|
||||
currencyValuePerToken: {
|
||||
...currencyValuePerToken,
|
||||
value: rawListing.pricePerToken,
|
||||
displayValue: toTokens(
|
||||
rawListing.pricePerToken,
|
||||
currencyValuePerToken.decimals,
|
||||
),
|
||||
},
|
||||
pricePerToken: rawListing.pricePerToken,
|
||||
asset: nftAsset,
|
||||
startTimeInSeconds: rawListing.startTimestamp,
|
||||
endTimeInSeconds: rawListing.endTimestamp,
|
||||
isReservedListing: rawListing.reserved,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function mapEnglishAuction(
|
||||
options: BaseTransactionOptions<{
|
||||
latestBlock: { timestamp: bigint };
|
||||
rawAuction: Awaited<ReturnType<typeof getAuction>>;
|
||||
}>,
|
||||
): Promise<EnglishAuction> {
|
||||
const { latestBlock, rawAuction } = options;
|
||||
// process the listing
|
||||
const status = computeStatus({
|
||||
listingStatus: rawAuction.status,
|
||||
blockTimeStamp: latestBlock.timestamp,
|
||||
startTimestamp: rawAuction.startTimestamp,
|
||||
endTimestamp: rawAuction.endTimestamp,
|
||||
});
|
||||
|
||||
const [auctionCurrencyMetadata, nftAsset] = await Promise.all([
|
||||
getCurrencyMetadata({
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawAuction.currency,
|
||||
}),
|
||||
}),
|
||||
getNFTAsset({
|
||||
...options,
|
||||
contract: getContract({
|
||||
...options.contract,
|
||||
address: rawAuction.assetContract,
|
||||
}),
|
||||
tokenId: rawAuction.tokenId,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
id: rawAuction.auctionId,
|
||||
creatorAddress: rawAuction.auctionCreator,
|
||||
assetContractAddress: rawAuction.assetContract,
|
||||
tokenId: rawAuction.tokenId,
|
||||
quantity: rawAuction.quantity,
|
||||
currencyContractAddress: rawAuction.currency,
|
||||
asset: nftAsset,
|
||||
startTimeInSeconds: rawAuction.startTimestamp,
|
||||
endTimeInSeconds: rawAuction.endTimestamp,
|
||||
status,
|
||||
minimumBidAmount: rawAuction.minimumBidAmount,
|
||||
minimumBidCurrencyValue: {
|
||||
...auctionCurrencyMetadata,
|
||||
value: rawAuction.minimumBidAmount,
|
||||
displayValue: toTokens(
|
||||
rawAuction.minimumBidAmount,
|
||||
auctionCurrencyMetadata.decimals,
|
||||
),
|
||||
},
|
||||
buyoutBidAmount: rawAuction.buyoutBidAmount,
|
||||
buyoutCurrencyValue: {
|
||||
...auctionCurrencyMetadata,
|
||||
value: rawAuction.buyoutBidAmount,
|
||||
displayValue: toTokens(
|
||||
rawAuction.buyoutBidAmount,
|
||||
auctionCurrencyMetadata.decimals,
|
||||
),
|
||||
},
|
||||
timeBufferInSeconds: rawAuction.timeBufferInSeconds,
|
||||
bidBufferBps: rawAuction.bidBufferBps,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -223,113 +90,3 @@ export async function getAllInBatches<const T>(
|
||||
|
||||
return await Promise.all(batches);
|
||||
}
|
||||
|
||||
export type IsListingValidParams = {
|
||||
listing: DirectListing;
|
||||
quantity?: bigint;
|
||||
};
|
||||
export type ValidReturn = { valid: true } | { valid: false; reason: string };
|
||||
|
||||
export async function isListingValid(
|
||||
options: BaseTransactionOptions<IsListingValidParams>,
|
||||
): Promise<ValidReturn> {
|
||||
const assetContract = getContract({
|
||||
...options.contract,
|
||||
address: options.listing.assetContractAddress,
|
||||
});
|
||||
|
||||
const [erc721, erc1155] = await Promise.all([
|
||||
isERC721({ contract: assetContract }),
|
||||
isERC1155({ contract: assetContract }),
|
||||
]);
|
||||
|
||||
// if the asset is an erc721 token
|
||||
if (erc721) {
|
||||
const [{ isApprovedForAll }, { getApproved }, { ownerOf }] =
|
||||
await Promise.all([
|
||||
import("../erc721/__generated__/IERC721A/read/isApprovedForAll.js"),
|
||||
import("../erc721/__generated__/IERC721A/read/getApproved.js"),
|
||||
import("../erc721/__generated__/IERC721A/read/ownerOf.js"),
|
||||
]);
|
||||
// check for token approval
|
||||
const [approvedForAll, approvedOperator, tokenOwner] = await Promise.all([
|
||||
isApprovedForAll({
|
||||
contract: assetContract,
|
||||
owner: options.listing.creatorAddress,
|
||||
// the marketplace contract address has to be approved to transfer the token
|
||||
operator: options.contract.address,
|
||||
}),
|
||||
getApproved({
|
||||
contract: assetContract,
|
||||
tokenId: options.listing.tokenId,
|
||||
}).catch(() => ""),
|
||||
ownerOf({
|
||||
contract: assetContract,
|
||||
tokenId: options.listing.tokenId,
|
||||
}),
|
||||
]);
|
||||
// if the marketplace is not approved for all and the marketplace contract is not the approved operator for the token
|
||||
// -> the listing is not valid
|
||||
if (!approvedForAll && approvedOperator !== options.contract.address) {
|
||||
return { valid: false, reason: "Asset not approved for marketplace." };
|
||||
}
|
||||
// if the token owner is not the creator of the listing
|
||||
// -> the listing is not valid
|
||||
if (tokenOwner !== options.listing.creatorAddress) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: "Listing creator no longer owns this token.",
|
||||
};
|
||||
}
|
||||
// otherwise the listing is valid
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
// if the asset is an erc1155 token
|
||||
if (erc1155) {
|
||||
const [{ isApprovedForAll }, { balanceOf }] = await Promise.all([
|
||||
import("../erc1155/__generated__/IERC1155/read/isApprovedForAll.js"),
|
||||
import("../erc1155/__generated__/IERC1155/read/balanceOf.js"),
|
||||
]);
|
||||
|
||||
const [approvedForAll, balance] = await Promise.all([
|
||||
isApprovedForAll({
|
||||
contract: assetContract,
|
||||
owner: options.listing.creatorAddress,
|
||||
// the marketplace contract address has to be approved to transfer the token
|
||||
operator: options.contract.address,
|
||||
}),
|
||||
balanceOf({
|
||||
contract: assetContract,
|
||||
owner: options.listing.creatorAddress,
|
||||
tokenId: options.listing.tokenId,
|
||||
}),
|
||||
]);
|
||||
|
||||
// if the marketplace is not approved for all
|
||||
// -> the listing is not valid
|
||||
if (!approvedForAll) {
|
||||
return { valid: false, reason: "Asset not approved for marketplace." };
|
||||
}
|
||||
// if the balance is less than the quantity the user is trying to purchase or the listing quantity
|
||||
// -> the listing is not valid
|
||||
const quantityWanted = options.quantity || options.listing.quantity;
|
||||
if (balance < quantityWanted) {
|
||||
return {
|
||||
valid: false,
|
||||
reason:
|
||||
"Seller does not have enough balance of token to fulfill order.",
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
// if the asset is neither ERC721 nor ERC1155
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
reason: "AssetContract must implement ERC 1155 or ERC 721.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// ADAPTED FROM https://github.com/merkletreejs/merkletreejs
|
||||
import { uint8ArrayToHex } from "../exports/thirdweb.js";
|
||||
import type { Hex } from "../utils/encoding/hex.js";
|
||||
import { type Hex, uint8ArrayToHex } from "../utils/encoding/hex.js";
|
||||
import { hexToBytes } from "../utils/encoding/to-bytes.js";
|
||||
import { keccak256 } from "../utils/hashing/keccak256.js";
|
||||
import {
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { LoginPayload } from "../../../../auth/core/types.js";
|
||||
import type { VerifyLoginPayloadParams } from "../../../../auth/core/verify-login-payload.js";
|
||||
import { useActiveWallet } from "../../../core/hooks/wallets/wallet-hooks.js";
|
||||
|
||||
/**
|
||||
* Options for Setting up SIWE (Sign in with Ethereum) Authentication
|
||||
* @auth
|
||||
*/
|
||||
export type SiweAuthOptions = {
|
||||
// we pass address and chainId and retrieve a login payload (we do not care how)
|
||||
|
||||
/**
|
||||
* Method to get the login payload for given address and chainId
|
||||
* @param params - The parameters to get the login payload for.
|
||||
*/
|
||||
getLoginPayload: (params: {
|
||||
address: string;
|
||||
chainId: number;
|
||||
}) => Promise<LoginPayload>;
|
||||
|
||||
// we pass the login payload and signature and the developer passes this to the auth server however they want
|
||||
|
||||
/**
|
||||
* Method to login with the signed login payload
|
||||
* @param params
|
||||
*/
|
||||
doLogin: (params: VerifyLoginPayloadParams) => Promise<void>;
|
||||
|
||||
// we call this internally when a user explicitly disconnects their wallet
|
||||
|
||||
/**
|
||||
* Method to logout the user
|
||||
*/
|
||||
doLogout: () => Promise<void>;
|
||||
|
||||
// the developer specifies how to check if the user is logged in, this is called internally by the component
|
||||
|
||||
/**
|
||||
* Method to check if the user is logged in or not
|
||||
* @param address
|
||||
*/
|
||||
isLoggedIn: (address: string) => Promise<boolean>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function useSiweAuth(authOptions?: SiweAuthOptions) {
|
||||
const activeWallet = useActiveWallet();
|
||||
const activeAccount = activeWallet?.getAccount();
|
||||
|
||||
const requiresAuth = !!authOptions;
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const isLoggedInQuery = useQuery({
|
||||
queryKey: ["siwe_auth", "isLoggedIn", activeAccount?.address],
|
||||
enabled: requiresAuth && !!activeAccount?.address,
|
||||
queryFn: () => {
|
||||
// these cases should never be hit but just in case...
|
||||
if (!authOptions || !activeAccount?.address) {
|
||||
return false;
|
||||
}
|
||||
return authOptions.isLoggedIn(activeAccount.address);
|
||||
},
|
||||
});
|
||||
|
||||
const loginMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!authOptions) {
|
||||
throw new Error("No auth options provided");
|
||||
}
|
||||
|
||||
if (!activeWallet) {
|
||||
throw new Error("No active wallet");
|
||||
}
|
||||
const chain = activeWallet.getChain();
|
||||
if (!chain) {
|
||||
throw new Error("No active chain");
|
||||
}
|
||||
if (!activeAccount) {
|
||||
throw new Error("No active account");
|
||||
}
|
||||
const [payload, { signLoginPayload }] = await Promise.all([
|
||||
authOptions.getLoginPayload({
|
||||
address: activeAccount.address,
|
||||
chainId: chain.id,
|
||||
}),
|
||||
// we lazy-load this because it's only needed when logging in
|
||||
import("../../../../auth/core/sign-login-payload.js"),
|
||||
]);
|
||||
|
||||
const signedPayload = await signLoginPayload({
|
||||
payload,
|
||||
account: activeAccount,
|
||||
});
|
||||
|
||||
return await authOptions.doLogin(signedPayload);
|
||||
},
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["siwe_auth", "isLoggedIn"],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const logoutMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!authOptions) {
|
||||
throw new Error("No auth options provided");
|
||||
}
|
||||
|
||||
return await authOptions.doLogout();
|
||||
},
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["siwe_auth", "isLoggedIn"],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
// is auth even enabled
|
||||
requiresAuth,
|
||||
|
||||
// login
|
||||
doLogin: loginMutation.mutateAsync,
|
||||
isLoggingIn: loginMutation.isPending,
|
||||
|
||||
// logout
|
||||
doLogout: logoutMutation.mutateAsync,
|
||||
isLoggingOut: logoutMutation.isPending,
|
||||
|
||||
// checking if logged in
|
||||
isLoggedIn: isLoggedInQuery.data,
|
||||
isLoading: isLoggedInQuery.isLoading,
|
||||
};
|
||||
}
|
||||
@@ -268,7 +268,7 @@ export function AutoConnect(props: AutoConnectProps) {
|
||||
|
||||
(async () => {
|
||||
isAutoConnecting.setValue(true);
|
||||
startAutoConnect();
|
||||
await startAutoConnect();
|
||||
isAutoConnecting.setValue(false);
|
||||
})();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type UseMutationResult, useMutation } from "@tanstack/react-query";
|
||||
import { sendBatchTransaction } from "../../../../exports/transaction.js";
|
||||
import { sendBatchTransaction } from "../../../../transaction/actions/send-batch-transaction.js";
|
||||
import type { WaitForReceiptOptions } from "../../../../transaction/actions/wait-for-tx-receipt.js";
|
||||
import type { PreparedTransaction } from "../../../../transaction/prepare-transaction.js";
|
||||
import { useActiveAccount } from "../wallets/wallet-hooks.js";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user