Compare commits

...
Author SHA1 Message Date
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>GitHub Action
a58d116314 Version Packages (#2989)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Action <[email protected]>
2024-05-10 15:35:51 -07:00
Jonas DanielsandGitHub ad24d83b51 Jd/fix releases (#2994) 2024-05-10 14:53:54 -07:00
Jonas DanielsandGitHub 9d142f53b4 Jd/fix releases (#2993) 2024-05-10 14:49:18 -07:00
Jonas DanielsandGitHub 74023c61c8 Jd/fix releases (#2992) 2024-05-10 14:42:20 -07:00
Jonas DanielsandGitHub 63b186cc94 Jd/fix releases (#2991) 2024-05-10 14:41:21 -07:00
Jonas DanielsandGitHub 9d410c6a54 Jd/fix releases (#2990) 2024-05-10 14:32:38 -07:00
Jonas DanielsandGitHub 02c03196ad chore: bump all to fix release (#2988) 2024-05-10 14:20:13 -07:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
a8667f8090 Version Packages (#2984)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-10 14:14:25 -07:00
Jonas DanielsandGitHub 691672f864 Jd/fix versioning 2 (#2987) 2024-05-10 14:03:04 -07:00
Jonas DanielsandGitHub cbdda89963 Jd/fix versioning 2 (#2986) 2024-05-10 14:01:58 -07:00
Jonas DanielsandGitHub 55081d8335 chore: fix release (#2985) 2024-05-10 13:52:56 -07:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ef1b7cdb90 chore(chains): update chains (#2982)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-11 08:33:47 +12:00
Joaquim VergesandGitHub 2a85f9c5ea fix: sync lockfile (#2983) 2024-05-11 08:27:32 +12:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
83cdef01d1 Version Packages (#2979)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-11 07:14:51 +12:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
d331921aaf chore(chains): update chains (#2973)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-10 11:50:09 -07:00
Manan TankandGitHub 579d5b5019 Minor cleanups for #2801 (#2981) 2024-05-10 17:57:20 +00:00
gregandGitHub 209825baca Adds @types/react dev dependency (#2978) 2024-05-10 09:46:31 +00:00
0aae34bdb8 Add Fiat onramp, Add PayEmbed (#2801)
Signed-off-by: Manan Tank <[email protected]>
Co-authored-by: Isaac Dubuque <[email protected]>
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
2024-05-10 15:57:37 +12:00
151 changed files with 8611 additions and 8741 deletions
+1 -2
View File
@@ -8,8 +8,7 @@
],
"commit": false,
"fixed": [
["@thirdweb-dev/react-core", "@thirdweb-dev/react"],
["@thirdweb-dev/react-native-compat", "@thirdweb-dev/react-native"]
["@thirdweb-dev/react-core", "@thirdweb-dev/react"]
],
"linked": [],
"access": "public",
+6 -2
View File
@@ -5,11 +5,15 @@
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"typescript.preferences.importModuleSpecifier": "relative",
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"typescript.preferences.autoImportFileExcludePatterns": ["/exports"],
"typescript.preferences.importModuleSpecifier": "relative"
}
+16
View File
@@ -1,5 +1,21 @@
# @thirdweb-dev/auth
## 4.1.77
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
- Updated dependencies [[`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60)]:
- @thirdweb-dev/wallets@2.5.19
## 4.1.76
### Patch Changes
- Updated dependencies []:
- @thirdweb-dev/wallets@2.5.18
## 4.1.75
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "4.1.75",
"version": "4.1.77",
"main": "dist/thirdweb-dev-auth.cjs.js",
"module": "dist/thirdweb-dev-auth.esm.js",
"browser": {
+12
View File
@@ -1,5 +1,17 @@
# @thirdweb-dev/chains
## 0.1.108
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
## 0.1.107
### Patch Changes
- [#2982](https://github.com/thirdweb-dev/js/pull/2982) [`ef1b7cd`](https://github.com/thirdweb-dev/js/commit/ef1b7cdb90aaa4c33cf0c7f619cab0d4d4acfccf) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
## 0.1.106
### Patch Changes
+31
View File
@@ -0,0 +1,31 @@
import type { Chain } from "../src/types";
export default {
"chain": "Silicon",
"chainId": 1414,
"explorers": [],
"faucets": [],
"icon": {
"url": "ipfs://QmTEnk2fosqbY6HQW5vySrLGbopJfeni9ThZ6R9sVefbnq",
"width": 800,
"height": 800,
"format": "png"
},
"name": "Silicon zkEVM Sepolia Testnet",
"nativeCurrency": {
"name": "Sepolia Ether",
"symbol": "ETH",
"decimals": 18
},
"networkId": 1414,
"parent": {
"type": "L2",
"chain": "eip155-11155111",
"bridges": []
},
"rpc": [],
"shortName": "silicon-sepolia-testnet",
"slug": "silicon-zkevm-sepolia-testnet",
"status": "incubating",
"testnet": true,
"title": "Silicon zkEVM Sepolia Testnet"
} as const satisfies Chain;
+13 -5
View File
@@ -12,6 +12,11 @@ export default {
"name": "Omni EVM Explorer on Blockscout",
"url": "https://omni-testnet.blockscout.com",
"standard": "EIP3091"
},
{
"name": "Omni EVM Explorer on Routescan",
"url": "https://testnet.omniscan.network",
"standard": "EIP3091"
}
],
"faucets": [],
@@ -21,17 +26,20 @@ export default {
}
],
"infoURL": "https://docs.omni.network",
"name": "Omni Testnet (incubating)",
"name": "Omni Testnet",
"nativeCurrency": {
"name": "Omni",
"symbol": "OMNI",
"decimals": 18
},
"networkId": 164,
"rpc": [],
"shortName": "omni_testnet_164",
"rpc": [
"https://164.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://testnet.omni.network"
],
"shortName": "omni_testnet",
"slip44": 1,
"slug": "omni-testnet-incubating",
"status": "incubating",
"slug": "omni-testnet",
"status": "active",
"testnet": true
} as const satisfies Chain;
+6 -14
View File
@@ -2,13 +2,7 @@ import type { Chain } from "../src/types";
export default {
"chain": "Omni",
"chainId": 165,
"explorers": [
{
"name": "Omni Explorer",
"url": "https://testnet.explorer.omni.network",
"standard": "EIP3091"
}
],
"explorers": [],
"faucets": [],
"features": [
{
@@ -16,19 +10,17 @@ export default {
}
],
"infoURL": "https://docs.omni.network/",
"name": "Omni Testnet",
"name": "Omni Testnet (Deprecated)",
"nativeCurrency": {
"name": "Omni",
"symbol": "OMNI",
"decimals": 18
},
"networkId": 165,
"rpc": [
"https://165.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://testnet.omni.network"
],
"shortName": "omni_testnet",
"rpc": [],
"shortName": "omni_testnet_deprecated",
"slip44": 1,
"slug": "omni-testnet",
"slug": "omni-testnet-deprecated",
"status": "deprecated",
"testnet": true
} as const satisfies Chain;
+31
View File
@@ -0,0 +1,31 @@
import type { Chain } from "../src/types";
export default {
"chain": "Silicon",
"chainId": 2355,
"explorers": [],
"faucets": [],
"icon": {
"url": "ipfs://QmTEnk2fosqbY6HQW5vySrLGbopJfeni9ThZ6R9sVefbnq",
"width": 800,
"height": 800,
"format": "png"
},
"name": "Silicon zkEVM",
"nativeCurrency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"networkId": 2355,
"parent": {
"type": "L2",
"chain": "eip155-1",
"bridges": []
},
"rpc": [],
"shortName": "silicon-zk",
"slug": "silicon-zkevm",
"status": "incubating",
"testnet": false,
"title": "Silicon zkEVM Mainnet"
} as const satisfies Chain;
+30
View File
@@ -0,0 +1,30 @@
import type { Chain } from "../src/types";
export default {
"chain": "Avalanche",
"chainId": 3070,
"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": "QaUser9054",
"nativeCurrency": {
"name": "QaUser9054 Token",
"symbol": "QFE",
"decimals": 18
},
"networkId": 3070,
"redFlags": [],
"rpc": [
"https://3070.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://subnets.avax.network/defi-kingdoms/dfk-chain/rpc"
],
"shortName": "QaUser9054",
"slug": "qauser9054",
"testnet": true
} as const satisfies Chain;
+30
View File
@@ -0,0 +1,30 @@
import type { Chain } from "../src/types";
export default {
"chain": "Avalanche",
"chainId": 59940,
"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": "QR0510s1",
"nativeCurrency": {
"name": "QR0510s1 Token",
"symbol": "RTQ",
"decimals": 18
},
"networkId": 59940,
"redFlags": [],
"rpc": [
"https://59940.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://subnets.avax.network/qr0510s1/testnet/rpc"
],
"shortName": "QR0510s1",
"slug": "qr0510s1",
"testnet": true
} as const satisfies Chain;
+30
View File
@@ -0,0 +1,30 @@
import type { Chain } from "../src/types";
export default {
"chain": "Avalanche",
"chainId": 767368,
"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": "Lamina1 Identity Testnet",
"nativeCurrency": {
"name": "Lamina1 Identity Testnet Token",
"symbol": "L1IDT",
"decimals": 18
},
"networkId": 767368,
"redFlags": [],
"rpc": [
"https://767368.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://subnets.avax.network/lamina1id/testnet/rpc"
],
"shortName": "Lamina1 Identity Testnet",
"slug": "lamina1-identity-testnet",
"testnet": true
} as const satisfies Chain;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/chains",
"version": "0.1.106",
"version": "0.1.108",
"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
+23
View File
@@ -1,5 +1,28 @@
# thirdweb
## 0.13.85
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
- Updated dependencies [[`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60)]:
- @thirdweb-dev/auth@4.1.77
- @thirdweb-dev/chains@0.1.108
- @thirdweb-dev/sdk@4.0.79
- @thirdweb-dev/storage@2.0.15
- @thirdweb-dev/wallets@2.5.19
## 0.13.84
### Patch Changes
- Updated dependencies [[`ef1b7cd`](https://github.com/thirdweb-dev/js/commit/ef1b7cdb90aaa4c33cf0c7f619cab0d4d4acfccf)]:
- @thirdweb-dev/chains@0.1.107
- @thirdweb-dev/sdk@4.0.78
- @thirdweb-dev/wallets@2.5.18
- @thirdweb-dev/auth@4.1.76
## 0.13.83
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@thirdweb-dev/cli",
"main": "dist/cli/index.js",
"version": "0.13.83",
"version": "0.13.85",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -1,5 +1,11 @@
# @thirdweb-dev/contracts-js
## 1.3.23
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
## 1.3.22
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/contracts-js",
"version": "1.3.22",
"version": "1.3.23",
"main": "dist/thirdweb-dev-contracts-js.cjs.js",
"module": "dist/thirdweb-dev-contracts-js.esm.js",
"exports": {
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/crypto
## 0.2.6
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
## 0.2.5
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/crypto",
"version": "0.2.5",
"version": "0.2.6",
"main": "dist/thirdweb-dev-crypto.cjs.js",
"module": "dist/thirdweb-dev-crypto.esm.js",
"exports": {
@@ -1,5 +1,11 @@
# @thirdweb-dev/generated-abis
## 0.0.2
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
## 0.0.1
### Patch Changes
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/generated-abis",
"version": "0.0.1",
"version": "0.0.2",
"main": "dist/thirdweb-dev-generated-abis.cjs.js",
"module": "dist/thirdweb-dev-generated-abis.esm.js",
"browser": {
@@ -23,7 +23,9 @@
},
"author": "thirdweb eng <[email protected]>",
"sideEffects": false,
"files": ["dist/"],
"files": [
"dist/"
],
"scripts": {
"push": "yalc push"
}
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/merkletree
## 0.2.6
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
## 0.2.5
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/merkletree",
"version": "0.2.5",
"version": "0.2.6",
"main": "dist/thirdweb-dev-merkletree.cjs.js",
"module": "dist/thirdweb-dev-merkletree.esm.js",
"exports": {
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/pay
## 0.1.8
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
## 0.1.7
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/pay",
"version": "0.1.7",
"version": "0.1.8",
"main": "dist/thirdweb-dev-pay.cjs.js",
"module": "dist/thirdweb-dev-pay.esm.js",
"exports": {
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/payments
## 1.0.5
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
## 1.0.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/payments",
"version": "1.0.4",
"version": "1.0.5",
"main": "dist/thirdweb-dev-payments.cjs.js",
"module": "dist/thirdweb-dev-payments.esm.js",
"types": "dist/thirdweb-dev-payments.cjs.d.ts",
+24
View File
@@ -1,5 +1,29 @@
# @thirdweb-dev/react-core
## 4.6.18
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
- Updated dependencies [[`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60)]:
- @thirdweb-dev/auth@4.1.77
- @thirdweb-dev/chains@0.1.108
- @thirdweb-dev/generated-abis@0.0.2
- @thirdweb-dev/sdk@4.0.79
- @thirdweb-dev/storage@2.0.15
- @thirdweb-dev/wallets@2.5.19
## 4.6.17
### Patch Changes
- Updated dependencies [[`ef1b7cd`](https://github.com/thirdweb-dev/js/commit/ef1b7cdb90aaa4c33cf0c7f619cab0d4d4acfccf)]:
- @thirdweb-dev/chains@0.1.107
- @thirdweb-dev/sdk@4.0.78
- @thirdweb-dev/wallets@2.5.18
- @thirdweb-dev/auth@4.1.76
## 4.6.16
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-core",
"version": "4.6.16",
"version": "4.6.18",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react-core",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+2 -2
View File
@@ -50,11 +50,11 @@
"devDependencies": {
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-native-community/eslint-config": "^3.2.0",
"@thirdweb-dev/tsconfig": "^0.1.7",
"@thirdweb-dev/tsconfig": "workspace:*",
"@types/react": "18.2.17",
"@types/react-native": "^0.71.13",
"eslint": "8.57.0",
"eslint-config-thirdweb": "^0.1.9",
"eslint-config-thirdweb": "workspace:*",
"eslint-plugin-i18next": "^6.0.3",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-inclusive-language": "^2.2.1",
+25
View File
@@ -1,5 +1,30 @@
# @thirdweb-dev/react
## 4.6.18
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
- Updated dependencies [[`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60)]:
- @thirdweb-dev/chains@0.1.108
- @thirdweb-dev/payments@1.0.5
- @thirdweb-dev/react-core@4.6.18
- @thirdweb-dev/sdk@4.0.79
- @thirdweb-dev/wallets@2.5.19
## 4.6.17
### Patch Changes
- [#2985](https://github.com/thirdweb-dev/js/pull/2985) [`55081d8`](https://github.com/thirdweb-dev/js/commit/55081d833585b8c7c24690abd87cabeee95cde33) Thanks [@jnsdls](https://github.com/jnsdls)! - thirdweb Pay UI minor improvements
- Updated dependencies [[`ef1b7cd`](https://github.com/thirdweb-dev/js/commit/ef1b7cdb90aaa4c33cf0c7f619cab0d4d4acfccf)]:
- @thirdweb-dev/chains@0.1.107
- @thirdweb-dev/sdk@4.0.78
- @thirdweb-dev/react-core@4.6.17
- @thirdweb-dev/wallets@2.5.18
## 4.6.16
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "4.6.16",
"version": "4.6.18",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -1,11 +1,12 @@
import { getBuyWithCryptoStatus } from "@thirdweb-dev/sdk";
import type { BuyWithCryptoStatus } from "@thirdweb-dev/sdk";
import { getBuyWithCryptoStatus, type BuyWithCryptoStatus } from "@thirdweb-dev/sdk";
import { wait } from "../../../../../utils/wait";
type ValidBuyWithCryptoStatus = Exclude<BuyWithCryptoStatus, { status: 'NOT_FOUND'}>
type SwapTxInfo = {
transactionHash: string;
status: BuyWithCryptoStatus["status"];
subStatus?: BuyWithCryptoStatus["subStatus"];
status: ValidBuyWithCryptoStatus["status"];
subStatus?: ValidBuyWithCryptoStatus["subStatus"];
source: {
symbol: string;
value: string;
@@ -24,13 +24,16 @@ import { BuyIcon } from "../icons/BuyIcon";
import { Text } from "../../../components/text";
import { CryptoIcon } from "../icons/CryptoIcon";
type ValidBuyWithCryptoStatus = Exclude<BuyWithCryptoStatus, { status: 'NOT_FOUND'}>
type TxStatusInfo = {
boughChainId: number;
transactionHash: string;
boughtTokenAmount: string;
boughtTokenSymbol: string;
status: BuyWithCryptoStatus["status"];
subStatus?: BuyWithCryptoStatus["subStatus"];
status: ValidBuyWithCryptoStatus["status"];
subStatus?: ValidBuyWithCryptoStatus["subStatus"];
};
const PAGE_SIZE = 10;
@@ -51,7 +54,11 @@ export function SwapTransactionsScreen(props: { onBack: () => void }) {
const txHashSet = new Set<string>();
_historyQuery.data?.page.forEach((tx) => {
txHashSet.add(tx.source.transactionHash);
if (tx.status !== "NOT_FOUND" && tx.status !== 'NONE') {
if (tx.source?.transactionHash) {
txHashSet.add(tx.source?.transactionHash);
}
}
});
// add in-memory pending transactions
@@ -76,15 +83,20 @@ export function SwapTransactionsScreen(props: { onBack: () => void }) {
// Add data from endpoint
_historyQuery.data?.page.forEach((tx) => {
txInfosToShow.push({
boughChainId: tx.destination?.token.chainId || tx.quote.toToken.chainId,
transactionHash: tx.source.transactionHash,
boughtTokenAmount: tx.destination?.amount || tx.quote.toAmount,
boughtTokenSymbol:
tx.destination?.token.symbol || tx.quote.toToken.symbol || "",
status: tx.status,
subStatus: tx.subStatus,
});
if (tx.status !== "NOT_FOUND" && tx.status !== 'NONE') {
if (tx.source?.transactionHash) {
txInfosToShow.push({
boughChainId:
tx.destination?.token.chainId || tx.quote.toToken.chainId,
transactionHash: tx.source?.transactionHash,
boughtTokenAmount: tx.destination?.amount || tx.quote.toAmount,
boughtTokenSymbol:
tx.destination?.token.symbol || tx.quote.toToken.symbol || "",
status: tx.status,
subStatus: tx.subStatus,
});
}
}
});
const activeChainId = useChainId();
@@ -367,8 +379,8 @@ const TxHashLink = /* @__PURE__ */ StyledAnchor(() => {
});
function getStatusMeta(
status: BuyWithCryptoStatus["status"],
subStatus?: BuyWithCryptoStatus["subStatus"],
status: ValidBuyWithCryptoStatus["status"],
subStatus?: ValidBuyWithCryptoStatus["subStatus"],
) {
if (subStatus === "WAITING_BRIDGE") {
return {
Binary file not shown.
+23
View File
@@ -1,5 +1,28 @@
# @thirdweb-dev/sdk
## 4.0.79
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
- Updated dependencies [[`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60)]:
- @thirdweb-dev/chains@0.1.108
- @thirdweb-dev/contracts-js@1.3.23
- @thirdweb-dev/crypto@0.2.6
- @thirdweb-dev/generated-abis@0.0.2
- @thirdweb-dev/merkletree@0.2.6
- @thirdweb-dev/storage@2.0.15
- thirdweb@5.18.1
## 4.0.78
### Patch Changes
- Updated dependencies [[`55081d8`](https://github.com/thirdweb-dev/js/commit/55081d833585b8c7c24690abd87cabeee95cde33), [`ef1b7cd`](https://github.com/thirdweb-dev/js/commit/ef1b7cdb90aaa4c33cf0c7f619cab0d4d4acfccf)]:
- thirdweb@5.18.0
- @thirdweb-dev/chains@0.1.107
## 4.0.77
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/sdk",
"version": "4.0.77",
"version": "4.0.79",
"description": "The main thirdweb SDK.",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/sdk",
"license": "Apache-2.0",
Binary file not shown.
+9
View File
@@ -1,5 +1,14 @@
# @thirdweb-dev/storage
## 2.0.15
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
- Updated dependencies [[`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60)]:
- @thirdweb-dev/crypto@0.2.6
## 2.0.14
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/storage",
"version": "2.0.14",
"version": "2.0.15",
"main": "dist/thirdweb-dev-storage.cjs.js",
"module": "dist/thirdweb-dev-storage.esm.js",
"exports": {
@@ -1,5 +1,29 @@
# @thirdweb-dev/unity-js-bridge
## 0.6.28
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
- Updated dependencies [[`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60)]:
- @thirdweb-dev/auth@4.1.77
- @thirdweb-dev/chains@0.1.108
- @thirdweb-dev/pay@0.1.8
- @thirdweb-dev/sdk@4.0.79
- @thirdweb-dev/storage@2.0.15
- @thirdweb-dev/wallets@2.5.19
## 0.6.27
### Patch Changes
- Updated dependencies [[`ef1b7cd`](https://github.com/thirdweb-dev/js/commit/ef1b7cdb90aaa4c33cf0c7f619cab0d4d4acfccf)]:
- @thirdweb-dev/chains@0.1.107
- @thirdweb-dev/sdk@4.0.78
- @thirdweb-dev/wallets@2.5.18
- @thirdweb-dev/auth@4.1.76
## 0.6.26
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/unity-js-bridge",
"version": "0.6.26",
"version": "0.6.28",
"main": "dist/thirdweb-unity-bridge.js",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
"license": "Apache-2.0",
+20
View File
@@ -1,5 +1,25 @@
# @thirdweb-dev/wallets
## 2.5.19
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
- Updated dependencies [[`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60)]:
- @thirdweb-dev/chains@0.1.108
- @thirdweb-dev/contracts-js@1.3.23
- @thirdweb-dev/crypto@0.2.6
- @thirdweb-dev/sdk@4.0.79
## 2.5.18
### Patch Changes
- Updated dependencies [[`ef1b7cd`](https://github.com/thirdweb-dev/js/commit/ef1b7cdb90aaa4c33cf0c7f619cab0d4d4acfccf)]:
- @thirdweb-dev/chains@0.1.107
- @thirdweb-dev/sdk@4.0.78
## 2.5.17
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/wallets",
"version": "2.5.17",
"version": "2.5.19",
"main": "dist/thirdweb-dev-wallets.cjs.js",
"module": "dist/thirdweb-dev-wallets.esm.js",
"browser": {
+10
View File
@@ -50,6 +50,11 @@
},
"_______ALL_PACKAGE_DEV_DEPENDENCIES_GO_IN_HERE_SO_CONSUMERS_DONT_END_UP_INSTALLING_THEM______": "_______ALL_PACKAGE_DEV_DEPENDENCIES_GO_IN_HERE_SO_CONSUMERS_DONT_END_UP_INSTALLING_THEM______",
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-transform-flow-strip-types": "^7.24.1",
"@babel/plugin-transform-private-methods": "^7.24.1",
"@babel/preset-env": "^7.24.5",
"@babel/preset-typescript": "^7.24.1",
"@biomejs/biome": "1.7.1",
"@codspeed/vitest-plugin": "3.1.0",
"@size-limit/preset-big-lib": "11.1.2",
@@ -60,6 +65,11 @@
"dotenv-mono": "1.3.14",
"ethers5": "npm:[email protected]",
"ethers6": "npm:[email protected]",
"eslint-plugin-i18next": "^6.0.3",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-inclusive-language": "^2.2.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-tsdoc": "^0.2.17",
"knip": "5.9.4",
"mitata": "0.1.11",
"react": "18.2.0",
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/service-utils
## 0.4.29
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
## 0.4.28
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/service-utils",
"version": "0.4.28",
"version": "0.4.29",
"main": "dist/thirdweb-dev-service-utils.cjs.js",
"module": "dist/thirdweb-dev-service-utils.esm.js",
"exports": {
+57
View File
@@ -1,5 +1,62 @@
# thirdweb
## 5.18.1
### Patch Changes
- [#2988](https://github.com/thirdweb-dev/js/pull/2988) [`02c0319`](https://github.com/thirdweb-dev/js/commit/02c03196ad38de888ff86311c68fa010018bda60) Thanks [@jnsdls](https://github.com/jnsdls)! - bump all to fix release
## 5.18.0
### Minor Changes
- [#2985](https://github.com/thirdweb-dev/js/pull/2985) [`55081d8`](https://github.com/thirdweb-dev/js/commit/55081d833585b8c7c24690abd87cabeee95cde33) Thanks [@jnsdls](https://github.com/jnsdls)! - ### "Credit Card" payment method added in thirdweb Pay for Fiat on-ramp
### `PayEmbed` component added to embed thirdweb Pay UI
```tsx
<PayEmbed
client={client}
style={{
width: "360px",
}}
/>
```
### thirdweb Pay UI customization available in `PayEmbed` and `ConnectButton`
`payOptions` prop in `PayEmbed` and `ConnectButton > detailsModal` allows you custimize :
- Enable/Disable payment methods
- Set default amount for Buy token
- Set Buy token/chain to be selected by default
- Set Source token/chain to be selected by default for Crypto payment method
- Disable editing for Buy token/chain/amount and Source token/chain
```tsx
<ConnectButton
client={client}
detailsModal={{
payOptions: yourOptions,
}}
/>
<PayEmbed
client={client}
detailsModal={{
payOptions: yourOptions,
}}
/>
```
### Fiat on-ramp functions and hooks added
- `getBuyWithFiatQuote`, `useBuyWithFiatQuote` to get a quote for buying crypto with fiat currency
- `getBuyWithFiatStatus`, `useBuyWithFiatStatus` to get status of "Buy with fiat" transaction
- `getBuyWithFiatHistory`, `useBuyWithFiatHistory` to get "Buy with fiat" transaction history
- `getPostOnRampQuote`, `usePostOnRampQuote` to get quote for swapping on-ramp token to destination token after doing on-ramp
- Add `getBuyHistory` and `useBuyHistory` to get both "Buy with Fiat" and "Buy with Crypto" transaction history in a single list
## 5.17.0
### Minor Changes
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "thirdweb",
"version": "5.17.0",
"version": "5.18.1",
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
@@ -241,6 +241,7 @@
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.6",
"@types/cross-spawn": "^6.0.6",
"@types/react": "18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/ui": "1.5.0",
"cross-spawn": "7.0.3",
+43 -4
View File
@@ -4,17 +4,56 @@ export {
type QuoteApprovalParams,
type QuoteTokenInfo,
type GetBuyWithCryptoQuoteParams,
} from "../pay/buyWithCrypto/actions/getQuote.js";
} from "../pay/buyWithCrypto/getQuote.js";
export {
getBuyWithCryptoStatus,
type BuyWithCryptoStatus,
type BuyWithCryptoTransaction,
type BuyWithCryptoTransactionDetails,
} from "../pay/buyWithCrypto/actions/getStatus.js";
} from "../pay/buyWithCrypto/getStatus.js";
export {
getBuyWithCryptoHistory,
type BuyWithCryptoHistoryData,
type BuyWithCryptoHistoryParams,
} from "../pay/buyWithCrypto/actions/getHistory.js";
} from "../pay/buyWithCrypto/getHistory.js";
// fiat ------------------------------------------------
export {
getBuyWithFiatQuote,
type BuyWithFiatQuote,
type GetBuyWithFiatQuoteParams,
} from "../pay/buyWithFiat/getQuote.js";
export {
getBuyWithFiatStatus,
type BuyWithFiatStatus,
type GetBuyWithFiatStatusParams,
} from "../pay/buyWithFiat/getStatus.js";
export {
getBuyWithFiatHistory,
type BuyWithFiatHistoryData,
type BuyWithFiatHistoryParams,
} from "../pay/buyWithFiat/getHistory.js";
export {
getPostOnRampQuote,
type GetPostOnRampQuoteParams,
} from "../pay/buyWithFiat/getPostOnRampQuote.js";
export {
getBuyHistory,
type BuyHistoryData,
type BuyHistoryParams,
} from "../pay/getBuyHistory.js";
export { isSwapRequiredPostOnramp } from "../pay/buyWithFiat/isSwapRequiredPostOnramp.js";
// types ------------------------------------------------
export type {
PayTokenInfo,
PayOnChainTransactionDetails,
} from "../pay/utils/commonTypes.js";
+21 -7
View File
@@ -32,19 +32,33 @@ export {
export { createContractQuery } from "../react/core/utils/createQuery.js";
export { useInvalidateContractQuery } from "../react/core/hooks/others/useInvalidateQueries.js";
// Buy with crypto
// pay
export {
useBuyWithCryptoQuote,
type BuyWithCryptoQuoteQueryParams,
type BuyWithCryptoQuoteQueryOptions,
} from "../react/core/hooks/pay/useBuyWithCryptoQuote.js";
export {
useBuyWithCryptoStatus,
type BuyWithCryptoStatusQueryParams,
} from "../react/core/hooks/pay/useBuyWithCryptoStatus.js";
export { useBuyWithCryptoStatus } from "../react/core/hooks/pay/useBuyWithCryptoStatus.js";
export {
useBuyWithCryptoHistory,
type BuyWithCryptoHistoryQueryParams,
type BuyWithCryptoHistoryQueryOptions,
} from "../react/core/hooks/pay/useBuyWithCryptoHistory.js";
export {
useBuyWithFiatQuote,
type BuyWithFiatQuoteQueryOptions,
} from "../react/core/hooks/pay/useBuyWithFiatQuote.js";
export { useBuyWithFiatStatus } from "../react/core/hooks/pay/useBuyWithFiatStatus.js";
export {
useBuyWithFiatHistory,
type BuyWithFiatHistoryQueryOptions,
} from "../react/core/hooks/pay/useBuyWithFiatHistory.js";
export {
useBuyHistory,
type BuyHistoryQueryOptions,
} from "../react/core/hooks/pay/useBuyHistory.js";
export {
usePostOnRampQuote,
type PostOnRampQuoteQueryOptions,
} from "../react/core/hooks/pay/usePostOnrampQuote.js";
import { useSendTransactionCore } from "../react/core/hooks/contract/useSendTransaction.js";
+28 -8
View File
@@ -80,20 +80,33 @@ export {
export { createContractQuery } from "../react/core/utils/createQuery.js";
export { useInvalidateContractQuery } from "../react/core/hooks/others/useInvalidateQueries.js";
// Buy with crypto
// pay
export {
useBuyWithCryptoQuote,
type BuyWithCryptoQuoteQueryParams,
type BuyWithCryptoQuoteQueryOptions,
} from "../react/core/hooks/pay/useBuyWithCryptoQuote.js";
export {
useBuyWithCryptoStatus,
type BuyWithCryptoStatusQueryParams,
} from "../react/core/hooks/pay/useBuyWithCryptoStatus.js";
export { useBuyWithCryptoStatus } from "../react/core/hooks/pay/useBuyWithCryptoStatus.js";
export {
useBuyWithCryptoHistory,
type BuyWithCryptoHistoryQueryParams,
type BuyWithCryptoHistoryQueryOptions,
} from "../react/core/hooks/pay/useBuyWithCryptoHistory.js";
export {
useBuyWithFiatQuote,
type BuyWithFiatQuoteQueryOptions,
} from "../react/core/hooks/pay/useBuyWithFiatQuote.js";
export { useBuyWithFiatStatus } from "../react/core/hooks/pay/useBuyWithFiatStatus.js";
export {
useBuyWithFiatHistory,
type BuyWithFiatHistoryQueryOptions,
} from "../react/core/hooks/pay/useBuyWithFiatHistory.js";
export {
useBuyHistory,
type BuyHistoryQueryOptions,
} from "../react/core/hooks/pay/useBuyHistory.js";
export {
usePostOnRampQuote,
type PostOnRampQuoteQueryOptions,
} from "../react/core/hooks/pay/usePostOnrampQuote.js";
export {
AutoConnect,
@@ -102,3 +115,10 @@ export {
// auth
export { type SiweAuthOptions } from "../react/core/hooks/auth/useSiweAuth.js";
export {
PayEmbed,
type PayEmbedProps,
type PayEmbedConnectOptions,
} from "../react/web/ui/PayEmbed.js";
export type { PayUIOptions } from "../react/web/ui/ConnectWallet/ConnectButtonProps.js";
+8 -4
View File
@@ -156,20 +156,24 @@ export {
type QuoteApprovalParams,
type QuoteTokenInfo,
type GetBuyWithCryptoQuoteParams,
} from "../pay/buyWithCrypto/actions/getQuote.js";
} from "../pay/buyWithCrypto/getQuote.js";
export {
getBuyWithCryptoStatus,
type BuyWithCryptoStatus,
type BuyWithCryptoTransaction,
type BuyWithCryptoTransactionDetails,
} from "../pay/buyWithCrypto/actions/getStatus.js";
} from "../pay/buyWithCrypto/getStatus.js";
export {
getBuyWithCryptoHistory,
type BuyWithCryptoHistoryData,
type BuyWithCryptoHistoryParams,
} from "../pay/buyWithCrypto/actions/getHistory.js";
} from "../pay/buyWithCrypto/getHistory.js";
export type {
PayOnChainTransactionDetails,
PayTokenInfo,
} from "../pay/utils/commonTypes.js";
// ------------------------------------------------
// encoding
@@ -1,5 +1,5 @@
import type { ThirdwebClient } from "../../../client/client.js";
import { getClientFetch } from "../../../utils/fetch.js";
import type { ThirdwebClient } from "../../client/client.js";
import { getClientFetch } from "../../utils/fetch.js";
import { getPayBuyWithCryptoHistoryEndpoint } from "../utils/definitions.js";
import type { BuyWithCryptoStatus } from "./getStatus.js";
@@ -1,14 +1,14 @@
import type { Hash } from "viem";
import { defineChain } from "../../../chains/utils.js";
import type { ThirdwebClient } from "../../../client/client.js";
import { getContract } from "../../../contract/contract.js";
import { defineChain } from "../../chains/utils.js";
import type { ThirdwebClient } from "../../client/client.js";
import { getContract } from "../../contract/contract.js";
import {
type ApproveParams,
approve,
} from "../../../extensions/erc20/write/approve.js";
import type { PrepareTransactionOptions } from "../../../transaction/prepare-transaction.js";
import type { BaseTransactionOptions } from "../../../transaction/types.js";
import { getClientFetch } from "../../../utils/fetch.js";
} from "../../extensions/erc20/write/approve.js";
import type { PrepareTransactionOptions } from "../../transaction/prepare-transaction.js";
import type { BaseTransactionOptions } from "../../transaction/types.js";
import { getClientFetch } from "../../utils/fetch.js";
import { getPayBuyWithCryptoQuoteEndpoint } from "../utils/definitions.js";
// TODO: add JSDoc description for all properties
@@ -27,6 +27,15 @@ export type GetBuyWithCryptoQuoteParams = {
*/
client: ThirdwebClient;
/**
* This is only relevant if the buy-with-crypto transaction is part of buy-with-fiat flow.
*
* When a swap is required after an onramp transaction, the intentId is used to link the buy-with-crypto transaction to the onramp transaction.
* Refer to [`getPostOnRampQuote`](https://portal.thirdweb.com/references/typescript/v5/getPostOnRampQuote) for more information.`
*
*/
intentId?: string;
/**
* The address of the wallet from which the tokens will be sent.
*/
@@ -240,6 +249,10 @@ export async function getBuyWithCryptoQuote(
queryParams.append("maxSlippageBPS", params.maxSlippageBPS.toString());
}
if (params.intentId) {
queryParams.append("intentId", params.intentId);
}
const queryString = queryParams.toString();
const url = `${getPayBuyWithCryptoQuoteEndpoint()}?${queryString}`;
@@ -1,23 +1,16 @@
import type { ThirdwebClient } from "../../../client/client.js";
import { getClientFetch } from "../../../utils/fetch.js";
import type { ThirdwebClient } from "../../client/client.js";
import { getClientFetch } from "../../utils/fetch.js";
import type {
PayOnChainTransactionDetails,
PayTokenInfo,
} from "../utils/commonTypes.js";
import { getPayBuyWithCryptoStatusUrl } from "../utils/definitions.js";
import type { QuoteTokenInfo } from "./getQuote.js";
// TODO: add JSDoc description for all properties
export type BuyWithCryptoTransactionDetails = {
transactionHash: string;
token: QuoteTokenInfo;
amountWei: string;
amount: string;
amountUSDCents: number;
completedAt?: string; // ISO DATE
explorerLink?: string;
};
export type BuyWithCryptoQuoteSummary = {
fromToken: QuoteTokenInfo;
toToken: QuoteTokenInfo;
fromToken: PayTokenInfo;
toToken: PayTokenInfo;
fromAmountWei: string;
fromAmount: string;
@@ -46,12 +39,7 @@ export type BuyWithCryptoTransaction = {
transactionHash: string;
};
export type BuyWithCryptoStatuses =
| "NOT_FOUND"
| "NONE"
| "PENDING"
| "FAILED"
| "COMPLETED";
export type BuyWithCryptoStatuses = "NONE" | "PENDING" | "FAILED" | "COMPLETED";
export type BuyWithCryptoSubStatuses =
| "NONE"
@@ -67,18 +55,27 @@ export type SwapType = "SAME_CHAIN" | "CROSS_CHAIN";
* The object returned by the [`getBuyWithCryptoStatus`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoStatus) function to represent the status of a quoted transaction
* @buyCrypto
*/
export type BuyWithCryptoStatus = {
quote: BuyWithCryptoQuoteSummary;
swapType: SwapType;
source: BuyWithCryptoTransactionDetails;
destination?: BuyWithCryptoTransactionDetails;
status: BuyWithCryptoStatuses;
subStatus: BuyWithCryptoSubStatuses;
fromAddress: string;
toAddress: string;
failureMessage?: string;
bridge?: string;
};
export type BuyWithCryptoStatus =
| {
status: "NOT_FOUND";
}
| {
quote: BuyWithCryptoQuoteSummary;
swapType: SwapType;
source?: PayOnChainTransactionDetails;
destination?: PayOnChainTransactionDetails;
status: BuyWithCryptoStatuses;
subStatus: BuyWithCryptoSubStatuses;
fromAddress: string;
toAddress: string;
failureMessage?: string;
bridge?: string;
};
export type ValidBuyWithCryptoStatus = Exclude<
BuyWithCryptoStatus,
{ status: "NOT_FOUND" }
>;
/**
* Gets the status of a buy with crypto transaction
@@ -86,7 +83,7 @@ export type BuyWithCryptoStatus = {
* @example
*
* ```ts
* import { sendTransaction, prepareTransaction } from "thirdweb";
* import { sendTransaction } from "thirdweb";
* import { getBuyWithCryptoStatus, getBuyWithCryptoQuote } from "thirdweb/pay";
*
* // get a quote between two tokens
@@ -94,23 +91,26 @@ export type BuyWithCryptoStatus = {
*
* // if approval is required, send the approval transaction
* if (quote.approval) {
* const preparedApproval = prepareTransaction(quote.approval);
* await sendTransaction({
* transaction,
* wallet,
* const txResult = await sendTransaction({
* transaction: quote.approval,
* account: account, // account from connected wallet
* });
*
* await waitForReceipt(txResult);
* }
*
* // send the quoted transaction
* const preparedTransaction = prepareTransaction(quote.transactionRequest);
* const transactionResult = await sendTransaction({
* transaction,
* wallet,
* const swapTxResult = await sendTransaction({
* transaction: quote.transactionRequest,
* account: account, // account from connected wallet
* });
*
* await waitForReceipt(swapTxResult);
*
* // keep polling the status of the quoted transaction until it returns a success or failure status
* const status = await getBuyWithCryptoStatus({
* client,
* transactionHash: transactionResult.transactionHash,
* transactionHash: swapTxResult.transactionHash,
* }});
* ```
* @returns Object of type [`BuyWithCryptoStatus`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoStatus)
@@ -130,7 +130,7 @@ export async function getBuyWithCryptoStatus(
const response = await getClientFetch(buyWithCryptoTransaction.client)(url);
// Assuming the response directly matches the SwapResponse interface
// Assuming the response directly matches the BuyWithCryptoStatus interface
if (!response.ok) {
response.body?.cancel();
throw new Error(`HTTP error! status: ${response.status}`);
@@ -1,31 +0,0 @@
import { getThirdwebDomains } from "../../../utils/domains.js";
/**
* Constructs the endpoint to get the status of a quote.
* @param client - The Thirdweb client containing the baseUrl config
* @internal
*/
export const getPayBuyWithCryptoStatusUrl = () =>
`https://${getThirdwebDomains().pay}/buy-with-crypto/status/v1`;
/**
* Constructs the endpoint to get a pay quote.
* @param client - The Thirdweb client containing the baseUrl config
* @internal
*/
export const getPayBuyWithCryptoQuoteEndpoint = () =>
`https://${getThirdwebDomains().pay}/buy-with-crypto/quote/v1`;
/**
* Constructs the endpoint to get a wallet address swap history.
* @param client - The Thirdweb client containing the baseUrl config
* @internal
*/
export const getPayBuyWithCryptoHistoryEndpoint = () =>
`https://${getThirdwebDomains().pay}/buy-with-crypto/history/v1`;
/**
* Constructs the endpoint to get the pay endpoint
* @internal
*/
export const getPayChainsEndpoint = () =>
`https://${getThirdwebDomains().pay}/chains`;
@@ -0,0 +1,90 @@
import type { ThirdwebClient } from "../../client/client.js";
import { getClientFetch } from "../../utils/fetch.js";
import { getPayBuyWithFiatHistoryEndpoint } from "../utils/definitions.js";
import type { BuyWithFiatStatus } from "./getStatus.js";
/**
* The parameters for [`getBuyWithFiatHistory`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatHistory) function
* @buyCrypto
*/
export type BuyWithFiatHistoryParams = {
/**
* A client is the entry point to the thirdweb SDK. It is required for all other actions.
*
* You can create a client using the `createThirdwebClient` function.
* Refer to the [Creating a Client](https://portal.thirdweb.com/typescript/v5/client) documentation for more information.
*/
client: ThirdwebClient;
/**
* The address of the wallet to get the wallet history for
*/
walletAddress: string;
/**
* The number of results to return in a single page. The default value is `10`.
*/
count: number;
/**
* index of the first result to return. The default value is `0`.
*
* If you want to start the list from nth item, you can set the start value to (n-1).
*/
start: number;
};
/**
* The results for [`getBuyWithFiatHistory`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatHistory) function
* @buyFiat
*/
export type BuyWithFiatHistoryData = {
page: BuyWithFiatStatus[];
hasNextPage: boolean;
};
/**
* Get the "Buy with fiat" transaction history for a given wallet address
* @param params Object of type [`BuyWithFiatHistoryParams`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatHistoryParams)
* @example
*
* ```ts
* import { createThirdwebClient } from "thirdweb";
* import { getBuyWithFiatHistory } from "thirdweb/pay";
*
* const client = createThirdwebClient({ clientId: "..." });
*
* // get the 10 latest "Buy with fiat" transactions dony by the wallet
* const history = await getBuyWithFiatHistory({
* client: client,
* walletAddress: '0x...',
* start: 0,
* count: 10,
* })
* ```
* @returns Object of type [`BuyWithFiatHistoryData`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatHistoryData)
* @buyFiat
*/
export async function getBuyWithFiatHistory(
params: BuyWithFiatHistoryParams,
): Promise<BuyWithFiatHistoryData> {
try {
const queryParams = new URLSearchParams();
queryParams.append("walletAddress", params.walletAddress);
queryParams.append("start", params.start.toString());
queryParams.append("count", params.count.toString());
const queryString = queryParams.toString();
const url = `${getPayBuyWithFiatHistoryEndpoint()}?${queryString}`;
const response = await getClientFetch(params.client)(url);
// Assuming the response directly matches the BuyWithFiatStatus response interface
if (!response.ok) {
response.body?.cancel();
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: BuyWithFiatHistoryData = (await response.json()).result;
return data;
} catch (error) {
throw new Error(`Fetch failed: ${error}`);
}
}
@@ -0,0 +1,77 @@
import type { ThirdwebClient } from "../../client/client.js";
import {
type BuyWithCryptoQuote,
getBuyWithCryptoQuote,
} from "../buyWithCrypto/getQuote.js";
import type { BuyWithFiatStatus } from "./getStatus.js";
/**
* The parameters for [`getPostOnRampQuote`](https://portal.thirdweb.com/references/typescript/v5/getPostOnRampQuote) function
*/
export type GetPostOnRampQuoteParams = {
/**
* A client is the entry point to the thirdweb SDK. It is required for all other actions.
*
* You can create a client using the `createThirdwebClient` function.
* Refer to the [Creating a Client](https://portal.thirdweb.com/typescript/v5/client) documentation for more information.
*/
client: ThirdwebClient;
/**
* The "Buy with fiat" transaction status object returned by [`getBuyWithFiatStatus`](https://portal.thirdweb.com/typescript/v5/getBuyWithFiatStatus) function
*/
buyWithFiatStatus: BuyWithFiatStatus;
};
/**
* When buying a token with fiat currency - It only involes doing on-ramp if the on-ramp provider supports buying the given destination token directly.
*
* If the on-ramp provider does not support buying the destination token directly, user can be sent an intermediate token with fiat currency from the on-ramp provider which
* can be swapped to destination token onchain.
*
* `getPostOnRampQuote` function is used to get the quote for swapping the on-ramp token to destination token.
*
* When you get a "Buy with Fiat" status of type "CRYPTO_SWAP_REQUIRED" from the [`getBuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatStatus) function,
* you can use `getPostOnRampQuote` function to get the quote of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote) for swapping the on-ramp token to destination token
*
* Once you have the quote, you can start the Swap process by following the same steps as mentioned in the [`getBuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoQuote) documentation.
*
* @param params - object of type [`GetPostOnRampQuoteParams`](https://portal.thirdweb.com/references/typescript/v5/GetPostOnRampQuoteParams)
* @returns Object of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote) which contains the information about the quote such as processing fees, estimated time, converted token amounts, etc.
* @example
* ```ts
* import { getPostOnRampQuote, getBuyWithFiatStatus } from "thirdweb/pay";
*
* // previous steps
* const fiatQuote = await getBuyWithFiatQuote(fiatQuoteParams);
* window.open(fiatQuote.onRampLink, "_blank");
* const buyWithFiatStatus = await getBuyWithFiatStatus({ client, intentId }); // keep calling this until status is "settled" state
*
* // when a swap is required after onramp
* if (buyWithFiatStatus.status === "CRYPTO_SWAP_REQUIRED") {
* const buyWithCryptoQuote = await getPostOnRampQuote({
* client,
* buyWithFiatStatus
* });
* }
* ```
* @buyFiat
*/
export async function getPostOnRampQuote({
client,
buyWithFiatStatus,
}: GetPostOnRampQuoteParams): Promise<BuyWithCryptoQuote> {
if (buyWithFiatStatus.status === "NOT_FOUND") {
throw new Error("Invalid buyWithFiatStatus");
}
return getBuyWithCryptoQuote({
client,
intentId: buyWithFiatStatus.intentId,
fromAddress: buyWithFiatStatus.toAddress,
fromChainId: buyWithFiatStatus.quote.onRampToken.chainId,
fromTokenAddress: buyWithFiatStatus.quote.onRampToken.tokenAddress,
toChainId: buyWithFiatStatus.quote.toToken.chainId,
toTokenAddress: buyWithFiatStatus.quote.toToken.tokenAddress,
toAmount: buyWithFiatStatus.quote.estimatedToTokenAmount,
});
}
@@ -0,0 +1,283 @@
import type { ThirdwebClient } from "../../client/client.js";
import { getClientFetch } from "../../utils/fetch.js";
import { getPayBuyWithFiatQuoteEndpoint } from "../utils/definitions.js";
/**
* Parameters for [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote) function
*/
export type GetBuyWithFiatQuoteParams = {
/**
* A client is the entry point to the thirdweb SDK. It is required for all other actions.
*
* You can create a client using the `createThirdwebClient` function.
* Refer to the [Creating a Client](https://portal.thirdweb.com/typescript/v5/client) documentation for more information.
*/
client: ThirdwebClient;
/**
* The address of the wallet to which the tokens will be sent.
*/
toAddress: string;
/**
* Chain id of the token to buy.
*/
toChainId: number;
/**
* Token address of the token to buy.
*/
toTokenAddress: string;
/**
* Symbol of the fiat currency to buy the token with.
*
* Currently, only `USD` is supported.
*/
fromCurrencySymbol: "USD";
/**
* The maximum slippage in basis points (bps) allowed for the transaction.
* For example, if you want to allow a maximum slippage of 0.5%, you should specify `50` bps.
*/
maxSlippageBPS?: number;
/**
* The amount of fiat currency to spend to buy the token.
* This is useful if you want to buy whatever amount of token you can get for a certain amount of fiat currency.
*
* If you want a certain amount of token, you can provide `toAmount` instead of `fromAmount`.
*/
fromAmount?: string;
/**
* The amount of token to buy
* This is useful if you want to get a certain amount of token.
*
* If you want to buy however much token you can get for a certain amount of fiat currency, you can provide `fromAmount` instead of `toAmount`.
*/
toAmount?: string;
/**
* Whether to use on-ramp provider in test mode for testing purpose or not.
*
* Defaults to `false`
*/
isTestMode?: boolean;
};
/**
* The response object returned by the [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote) function.
*
* This includes various information for buying a token using a fiat currency:
* - on-ramp provider UI link
* - The estimated time for the transaction to complete.
* - The on-ramp and destination token information.
* - Processing fees
*/
export type BuyWithFiatQuote = {
/**
* Estimated time for the transaction to complete in seconds.
*/
estimatedDurationSeconds: number;
/**
* Minimum amount of token that is expected to be received in units.
*/
estimatedToAmountMin: string;
/**
* Minimum amount of token that is expected to be received in wei.
*/
estimatedToAmountMinWei: string;
/**
* Amount of token that is expected to be received in units.
*
* (estimatedToAmountMinWei - maxSlippageWei)
*/
toAmountMinWei: string;
/**
* Amount of token that is expected to be received in wei.
*
* (estimatedToAmountMin - maxSlippageWei)
*/
toAmountMin: string;
/**
* fiat currency used to buy the token - excluding the fees.
*/
fromCurrency: {
amount: string;
amountUnits: string;
decimals: number;
currencySymbol: string;
};
/**
* Fiat currency used to buy the token - including the fees.
*/
fromCurrencyWithFees: {
amount: string;
amountUnits: string;
decimals: number;
currencySymbol: string;
};
/**
* Token information for the desired token. (token the user wants to buy)
*/
toToken: {
symbol?: string | undefined;
priceUSDCents?: number | undefined;
name?: string | undefined;
chainId: number;
tokenAddress: string;
decimals: number;
};
/**
* Address of the wallet to which the tokens will be sent.
*/
toAddress: string;
/**
* The maximum slippage in basis points (bps) allowed for the transaction.
*/
maxSlippageBPS: number;
/**
* Id of transaction
*/
intentId: string;
/**
* Array of processing fees for the transaction.
*
* This includes the processing fees for on-ramp and swap (if required).
*/
processingFees: {
amount: string;
amountUnits: string;
decimals: number;
currencySymbol: string;
feeType: "ON_RAMP" | "NETWORK";
}[];
/**
* Token that will be sent to the user's wallet address by the on-ramp provider.
*
* If the token is same as `toToken` - the user can directly buy the token from the on-ramp provider.
* If not, the user will receive this token and a swap is required to convert it `toToken`.
*/
onRampToken: {
amount: string;
amountWei: string;
amountUSDCents: number;
token: {
chainId: number;
decimals: number;
name: string;
priceUSDCents: number;
symbol: string;
tokenAddress: string;
};
};
/**
* Link to the on-ramp provider UI that will prompt the user to buy the token with fiat currency.
*
* This link should be opened in a new tab.
* @example
* ```ts
* window.open(quote.onRampLink, "_blank");
* ```
*
*/
onRampLink: string;
};
/**
* Get a quote of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote) to buy given token with fiat currency.
* This quote contains the information about the swap such as token amounts, processing fees, estimated time etc.
*
* ### Rendering the On-Ramp provider UI
* Once you have the `quote`, you can open the `quote.onRampLink` in a new tab - This will prompt the user to buy the token with fiat currency
*
* ### Determining the steps required
* If `quote.onRampToken.token` is same as `quote.toToken` ( same chain + same token address ) - This means that the token can be directly bought from the on-ramp provider.
* But if they are different, On-ramp provider will send the `quote.onRampToken` to the user's wallet address and a swap is required to swap it to the desired token onchain.
*
* You can use the [`isSwapRequiredPostOnramp`](https://portal.thirdweb.com/references/typescript/v5/isSwapRequiredPostOnramp) utility function to check if a swap is required after the on-ramp is done.
*
* ### Polling for the status
* Once you open the `quote.onRampLink` in a new tab, you can start polling for the status using [`getBuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatStatus) to get the status of the transaction.
*
* `getBuyWithFiatStatus` returns a status object of type [`BuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatStatus).
*
* - If no swap is required - the status will become `"ON_RAMP_TRANSFER_COMPLETED"` once the on-ramp provider has sent the desired token to the user's wallet address. Once you receive this status, the process is complete.
* - If a swap is required - the status will become `"CRYPTO_SWAP_REQUIRED"` once the on-ramp provider has sent the tokens to the user's wallet address. Once you receive this status, you need to start the swap process.
*
* ### Swap Process
* On receiving the `"CRYPTO_SWAP_REQUIRED"` status, you can use the [`getPostOnRampQuote`](https://portal.thirdweb.com/references/typescript/v5/getPostOnRampQuote) function to get the quote for the swap of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote).
*
* Once you have this quote - You can follow the same steps as mentioned in the [`getBuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoQuote) documentation to perform the swap.
*
* @param params - object of type [`GetBuyWithFiatQuoteParams`](https://portal.thirdweb.com/references/typescript/v5/GetBuyWithFiatQuoteParams)
* @returns Object of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote) which contains the information about the quote such as processing fees, estimated time, converted token amounts, etc.
* @example
* Get a quote for buying 10 USDC on polygon chain (chainId: 137) with USD fiat currency:
*
* ```ts
* import { getBuyWithFiatQuote } from "thirdweb/pay";
*
* const quote = await getBuyWithFiatQuote({
* client: client, // thirdweb client
* fromCurrencySymbol: "USD", // fiat currency symbol
* toChainId: 137, // polygon chain id
* toAmount: "10", // amount of USDC to buy
* toTokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" // USDC token address in polygon chain
* toAddress: "0x...", // user's wallet address
* isTestMode: false, // whether to use onramp in test mode for testing purpose (defaults to false)
* });
*
* window.open(quote.onRampLink, "_blank");
* ```
* @buyFiat
*/
export async function getBuyWithFiatQuote(
params: GetBuyWithFiatQuoteParams,
): Promise<BuyWithFiatQuote> {
try {
const queryParams = new URLSearchParams({
toAddress: params.toAddress,
fromCurrencySymbol: params.fromCurrencySymbol,
toChainId: params.toChainId.toString(),
toTokenAddress: params.toTokenAddress.toLowerCase(),
});
if (params.fromAmount) {
queryParams.append("fromAmount", params.fromAmount);
}
if (params.toAmount) {
queryParams.append("toAmount", params.toAmount);
}
if (params.maxSlippageBPS) {
queryParams.append("maxSlippageBPS", params.maxSlippageBPS.toString());
}
if (params.isTestMode) {
queryParams.append("isTestMode", params.isTestMode.toString());
}
const queryString = queryParams.toString();
const url = `${getPayBuyWithFiatQuoteEndpoint()}?${queryString}`;
const response = await getClientFetch(params.client)(url);
// Assuming the response directly matches the SwapResponse interface
if (!response.ok) {
const errorObj = await response.json();
if (errorObj && "error" in errorObj) {
throw errorObj;
}
throw new Error(`HTTP error! status: ${response.status}`);
}
return (await response.json()).result;
} catch (error) {
console.error("Fetch error:", error);
throw error;
}
}
@@ -0,0 +1,186 @@
import type { ThirdwebClient } from "../../client/client.js";
import { getClientFetch } from "../../utils/fetch.js";
import type {
PayOnChainTransactionDetails,
PayTokenInfo,
} from "../utils/commonTypes.js";
import { getPayBuyWithFiatStatusEndpoint } from "../utils/definitions.js";
/**
* Parameters for the [`getBuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatStatus) function
*/
export type GetBuyWithFiatStatusParams = {
/**
* A client is the entry point to the thirdweb SDK. It is required for all other actions.
*
* You can create a client using the `createThirdwebClient` function.
* Refer to the [Creating a Client](https://portal.thirdweb.com/typescript/v5/client) documentation for more information.
*/
client: ThirdwebClient;
/**
* Intent ID of the "Buy with fiat" transaction. You can get the intent ID from the quote object returned by the [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote) function
*/
intentId: string;
};
export type ValidBuyWithFiatStatus = Exclude<
BuyWithFiatStatus,
{ status: "NOT_FOUND" }
>;
/**
* The returned object from [`getBuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatStatus) function
*
* If the in invalid intentId is provided, the object will have a status of "NOT_FOUND" and no other fields.
*/
export type BuyWithFiatStatus =
| {
status: "NOT_FOUND";
}
| {
/**
* Intent ID of the "Buy with fiat" transaction. You can get the intent ID from the quote object returned by the [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote) function
*/
intentId: string;
/**
* The status of the transaction
* - `NONE` - No status
* - `PENDING_PAYMENT` - Payment is not done yet in the on-ramp provider
* - `PAYMENT_FAILED` - Payment failed in the on-ramp provider
* - `PENDING_ON_RAMP_TRANSFER` - Payment is done but the on-ramp provider is yet to transfer the tokens to the user's wallet
* - `ON_RAMP_TRANSFER_IN_PROGRESS` - On-ramp provider is transferring the tokens to the user's wallet
* - `ON_RAMP_TRANSFER_COMPLETED` - On-ramp provider has transferred the tokens to the user's wallet
* - `ON_RAMP_TRANSFER_FAILED` - On-ramp provider failed to transfer the tokens to the user's wallet
* - `CRYPTO_SWAP_REQUIRED` - On-ramp provider has sent the tokens to the user's wallet but a swap is required to convert it to the desired token
* - `CRYPTO_SWAP_IN_PROGRESS` - Swap is in progress
* - `CRYPTO_SWAP_COMPLETED` - Swap is completed and the user has received the desired token
* - `CRYPTO_SWAP_FALLBACK` - Swap failed and the user has received a fallback token which is not the desired token
*/
status:
| "NONE"
| "PENDING_PAYMENT"
| "PAYMENT_FAILED"
| "PENDING_ON_RAMP_TRANSFER"
| "ON_RAMP_TRANSFER_IN_PROGRESS"
| "ON_RAMP_TRANSFER_COMPLETED"
| "ON_RAMP_TRANSFER_FAILED"
| "CRYPTO_SWAP_REQUIRED"
| "CRYPTO_SWAP_COMPLETED"
| "CRYPTO_SWAP_FALLBACK"
| "CRYPTO_SWAP_IN_PROGRESS"
| "CRYPTO_SWAP_FAILED";
/**
* The wallet address to which the tokens are sent to
*/
toAddress: string;
/**
* The quote object for the transaction
*/
quote: {
estimatedOnRampAmount: string;
estimatedOnRampAmountWei: string;
estimatedToTokenAmount: string;
estimatedToTokenAmountWei: string;
fromCurrency: {
amount: string;
amountUnits: string;
decimals: number;
currencySymbol: string;
};
fromCurrencyWithFees: {
amount: string;
amountUnits: string;
decimals: number;
currencySymbol: string;
};
onRampToken: PayTokenInfo;
toToken: PayTokenInfo;
estimatedDurationSeconds?: number;
createdAt: string;
};
/**
* The on-ramp transaction details
*
* This field is only present when on-ramp transaction is completed or failed
*/
source?: PayOnChainTransactionDetails;
/**
* The destination transaction details
*
* This field is only present when swap transaction is completed or failed
*/
destination?: PayOnChainTransactionDetails;
/**
* Message indicating the reason for failure
*/
failureMessage?: string;
};
/**
* Once you get a `quote` from [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote)
* and open the `quote.onRampLink` in a new tab, you can start polling for the transaction status using `getBuyWithFiatStatus`
*
* You should keep calling this function at regular intervals while the status is in one of the pending states such as - "PENDING_PAYMENT", "PENDING_ON_RAMP_TRANSFER", "ON_RAMP_TRANSFER_IN_PROGRESS", "CRYPTO_SWAP_IN_PROGRESS" etc..
*
* If `quote.onRampToken` is same as `quote.toToken` (same chain + same token address) - This means that the token can be directly bought from the on-ramp provider.
* But if they are different - On-ramp provider will send the `quote.onRampToken` to the user's wallet address and a swap is required to convert it to the desired token.
* You can use the [`isSwapRequiredPostOnramp`](https://portal.thirdweb.com/references/typescript/v5/isSwapRequiredPostOnramp) utility function to check if a swap is required after the on-ramp is done.
*
* #### When no swap is required
* If there is no swap required - the status will become `"ON_RAMP_TRANSFER_COMPLETED"` once the on-ramp provider has sent the tokens to the user's wallet address.
* Once you receive this status, the process is complete.
*
* ### When a swap is required
* If a swap is required - the status will become `"CRYPTO_SWAP_REQUIRED"` once the on-ramp provider has sent the tokens to the user's wallet address.
* Once you receive this status, you need to start the swap process.
*
* On receiving the `"CRYPTO_SWAP_REQUIRED"` status, you can use the [`getPostOnRampQuote`](https://portal.thirdweb.com/references/typescript/v5/getPostOnRampQuote) function to get the quote for the swap of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote).
*
* Once you have this quote - You can follow the same steps as mentioned in the [`getBuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoQuote) documentation to perform the swap.
*
* @param params - Object of type [`GetBuyWithFiatStatusParams`](https://portal.thirdweb.com/references/typescript/v5/GetBuyWithFiatStatusParams)
* @example
* ```ts
* // step 1 - get a quote
* const fiatQuote = await getBuyWithFiatQuote(fiatQuoteParams)
*
* // step 2 - open the on-ramp provider UI
* window.open(quote.onRampLink, "_blank");
*
* // step 3 - keep calling getBuyWithFiatStatus while the status is in one of the pending states
* const fiatStatus = await getBuyWithFiatStatus({
* client,
* intentId: fiatQuote.intentId,
* })
*
* // when the fiatStatus.status is "ON_RAMP_TRANSFER_COMPLETED" - the process is complete
* // when the fiatStatus.status is "CRYPTO_SWAP_REQUIRED" - start the swap process
* ```
* @buyFiat
*/
export async function getBuyWithFiatStatus(
params: GetBuyWithFiatStatusParams,
): Promise<BuyWithFiatStatus> {
try {
const queryParams = new URLSearchParams({
intentId: params.intentId,
});
const queryString = queryParams.toString();
const url = `${getPayBuyWithFiatStatusEndpoint()}?${queryString}`;
const response = await getClientFetch(params.client)(url);
if (!response.ok) {
response.body?.cancel();
throw new Error(`HTTP error! status: ${response.status}`);
}
return (await response.json()).result;
} catch (error) {
console.error("Fetch error:", error);
throw new Error(`Fetch failed: ${error}`);
}
}
@@ -0,0 +1,28 @@
import { getAddress } from "../../utils/address.js";
import type { BuyWithFiatQuote } from "./getQuote.js";
/**
* Check if a Swap is required after on-ramp when buying a token with fiat currency.
*
* If `quote.toToken` and `quote.onRampToken` are the same (same token and chain),
* it means on-ramp provider can directly send the desired token to the user's wallet and no swap is required.
*
* If `quote.toToken` and `quote.onRampToken` are different (different token or chain), A swap is required to swap the on-ramp token to the desired token.
*
* @param buyWithFiatQuote - The quote of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote) returned
* by the [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote) function.
* @buyFiat
*/
export function isSwapRequiredPostOnramp(
buyWithFiatQuote: Pick<BuyWithFiatQuote, "toToken" | "onRampToken">,
) {
const sameChain =
buyWithFiatQuote.toToken.chainId ===
buyWithFiatQuote.onRampToken.token.chainId;
const sameToken =
getAddress(buyWithFiatQuote.toToken.tokenAddress) ===
getAddress(buyWithFiatQuote.onRampToken.token.tokenAddress);
return !(sameChain && sameToken);
}
+101
View File
@@ -0,0 +1,101 @@
import type { ThirdwebClient } from "../client/client.js";
import { getClientFetch } from "../utils/fetch.js";
import type { BuyWithCryptoStatus } from "./buyWithCrypto/getStatus.js";
import type { BuyWithFiatStatus } from "./buyWithFiat/getStatus.js";
import { getPayBuyHistoryEndpoint } from "./utils/definitions.js";
/**
* The parameters for [`getBuyHistory`](https://portal.thirdweb.com/references/typescript/v5/getBuyHistory) function
*/
export type BuyHistoryParams = {
/**
* A client is the entry point to the thirdweb SDK. It is required for all other actions.
*
* You can create a client using the `createThirdwebClient` function.
* Refer to the [Creating a Client](https://portal.thirdweb.com/typescript/v5/client) documentation for more information.
*/
client: ThirdwebClient;
/**
* The wallet address to get the buy history for.
*/
walletAddress: string;
/**
* The number of results to return.
*
* The default value is `10`.
*/
count: number;
/**
* Index of the first result to return. The default value is `0`.
*/
start: number;
};
/**
* The result for [`getBuyHistory`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoHistory) function
*
* It includes both "Buy with Crypto" and "Buy with Fiat" transactions
*/
export type BuyHistoryData = {
/**
* The list of buy transactions.
*/
page: Array<
| {
buyWithFiatStatus: BuyWithFiatStatus;
}
| {
buyWithCryptoStatus: BuyWithCryptoStatus;
}
>;
/**
* Whether there are more pages of results.
*/
hasNextPage: boolean;
};
/**
* Get Buy transaction history for a given wallet address.
*
* This includes both "Buy with Cryto" and "Buy with Fiat" transactions
*
* @param params Object of type [`BuyHistoryParams`](https://portal.thirdweb.com/references/typescript/v5/BuyHistoryParams)
* @example
* ```ts
* import { createThirdwebClient } from "thirdweb";
* import { getBuyHistory } from "thirdweb/pay";
*
* const client = createThirdwebClient({ clientId: "..." });
*
* const history = await getBuyHistory({
* client,
* walletAddress: "0x...",
* })
* ```
*/
export async function getBuyHistory(
params: BuyHistoryParams,
): Promise<BuyHistoryData> {
try {
const queryParams = new URLSearchParams();
queryParams.append("walletAddress", params.walletAddress);
queryParams.append("start", params.start.toString());
queryParams.append("count", params.count.toString());
const queryString = queryParams.toString();
const url = `${getPayBuyHistoryEndpoint()}?${queryString}`;
const response = await getClientFetch(params.client)(url);
// Assuming the response directly matches the SwapResponse interface
if (!response.ok) {
response.body?.cancel();
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: BuyHistoryData = (await response.json()).result;
return data;
} catch (error) {
throw new Error(`Fetch failed: ${error}`);
}
}
@@ -0,0 +1,18 @@
export type PayTokenInfo = {
chainId: number;
tokenAddress: string;
decimals: number;
priceUSDCents: number;
name?: string;
symbol?: string;
};
export type PayOnChainTransactionDetails = {
transactionHash: string;
token: PayTokenInfo;
amountWei: string;
amount: string;
amountUSDCents: number;
completedAt?: string; // ISO DATE
explorerLink?: string;
};
@@ -0,0 +1,64 @@
import { getThirdwebDomains } from "../../utils/domains.js";
/**
* Endpoint to get the status of a "Buy with Crypto" quote.
* @internal
*/
export const getPayBuyWithCryptoStatusUrl = () =>
`https://${getThirdwebDomains().pay}/buy-with-crypto/status/v1`;
/**
* Endpoint to get "Buy with Crypto" quote.
* @internal
*/
export const getPayBuyWithCryptoQuoteEndpoint = () =>
`https://${getThirdwebDomains().pay}/buy-with-crypto/quote/v1`;
/**
* Endpoint to get a "Buy with Fiat" quote.
* @internal
*/
export const getPayBuyWithFiatQuoteEndpoint = () =>
`https://${getThirdwebDomains().pay}/buy-with-fiat/quote/v1`;
/**
* Endpoint to get the status of a "Buy with Fiat" transaction status.
* @internal
*/
export const getPayBuyWithFiatStatusEndpoint = () =>
`https://${getThirdwebDomains().pay}/buy-with-fiat/status/v1`;
/**
* Endpoint to get history of "Buy with Fiat" transactions for given wallet address.
* @internal
*/
export const getPayBuyWithFiatHistoryEndpoint = () =>
`https://${getThirdwebDomains().pay}/buy-with-fiat/history/v1`;
/**
* Endpoint to get a "Buy with Crypto" transaction history for a given wallet address.
* @internal
*/
export const getPayBuyWithCryptoHistoryEndpoint = () =>
`https://${getThirdwebDomains().pay}/buy-with-crypto/history/v1`;
/**
* Endpoint to get a list of supported destination chains and tokens for thirdweb pay.
* @internal
*/
export const getPaySupportedDestinations = () =>
`https://${getThirdwebDomains().pay}/destination-tokens/v1`;
/**
* Endpoint to get a list of supported source chains + tokens for thirdweb pay.
* @internal
*/
export const getPaySupportedSources = () =>
`https://${getThirdwebDomains().pay}/buy-with-crypto/source-tokens/v1`;
/**
* Endpoint to get buy history for a given wallet address.
* This includes both "Buy with Crypto" and "Buy with Fiat" transactions.
* @internal
*/
export const getPayBuyHistoryEndpoint = () =>
`https://${getThirdwebDomains().pay}/wallet/history/v1`;
@@ -9,7 +9,7 @@ import {
type GetWalletBalanceResult,
getWalletBalance,
} from "../../../../wallets/utils/getWalletBalance.js";
import { fetchSwapSupportedChains } from "../../../web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.js";
import { fetchBuySupportedDestinations } from "../../../web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.js";
import { useActiveAccount } from "../wallets/wallet-hooks.js";
type ShowModalData = {
@@ -70,12 +70,10 @@ export function useSendTransactionCore(
(async () => {
try {
const swapSupportedChains = await fetchSwapSupportedChains(
tx.client,
);
const destinations = await fetchBuySupportedDestinations(tx.client);
const isBuySupported = swapSupportedChains.find(
(c) => c.id === tx.chain.id,
const isBuySupported = destinations.find(
(c) => c.chain.id === tx.chain.id,
);
// buy not supported, can't show modal - send tx directly
@@ -125,9 +123,8 @@ export function useSendTransactionCore(
}
export async function getTotalTxCostForBuy(tx: PreparedTransaction) {
// Must pass 0 otherwise it will throw on some chains
const gasCost = await estimateGasCost({
transaction: { ...tx, value: 0n },
transaction: tx,
});
const bufferCost = gasCost.wei / 10n;
@@ -0,0 +1,57 @@
import { useQuery } from "@tanstack/react-query";
import type { Chain } from "../../../../chains/types.js";
import type { ThirdwebClient } from "../../../../client/client.js";
import { getContract } from "../../../../contract/contract.js";
type GetTokenInfoOptions = {
client: ThirdwebClient;
chain: Chain;
tokenAddress?: string;
};
type GetTokenInfoResult = {
name: string;
symbol: string;
decimals: number;
};
/**
* @internal
*/
export function useTokenInfo(options: GetTokenInfoOptions) {
const { chain, tokenAddress, client } = options;
return useQuery({
queryKey: ["tokenInfo", chain?.id || -1, { tokenAddress }] as const,
queryFn: async () => {
// erc20 case
if (tokenAddress) {
const { getCurrencyMetadata } = await import(
"../../../../extensions/erc20/read/getCurrencyMetadata.js"
);
const result: GetTokenInfoResult = await getCurrencyMetadata({
contract: getContract({ client, chain, address: tokenAddress }),
});
return result;
}
const { getChainDecimals, getChainNativeCurrencyName, getChainSymbol } =
await import("../../../../chains/utils.js");
const [nativeSymbol, nativeDecimals, nativeName] = await Promise.all([
getChainSymbol(chain),
getChainDecimals(chain),
getChainNativeCurrencyName(chain),
]);
const result: GetTokenInfoResult = {
decimals: nativeDecimals,
symbol: nativeSymbol,
name: nativeName,
};
return result;
},
enabled: !!chain && !!client,
});
}
@@ -0,0 +1,54 @@
import {
type UseQueryOptions,
type UseQueryResult,
useQuery,
} from "@tanstack/react-query";
import {
type BuyHistoryData,
type BuyHistoryParams,
getBuyHistory,
} from "../../../../pay/getBuyHistory.js";
/**
* @internal
*/
export type BuyHistoryQueryOptions = Omit<
UseQueryOptions<BuyHistoryData>,
"queryFn" | "queryKey" | "enabled"
>;
/**
* Hook to get the history of Buy transactions for a given wallet - This includes both "buy with crypto" and "buy with fiat" transactions.
*
* This hook is a React Query wrapper of the [`getBuyHistory`](https://portal.thirdweb.com/references/typescript/v5/getBuyHistory) function.
* You can also use that function directly
*
* @param params - object of type [`BuyHistoryParams`](https://portal.thirdweb.com/references/typescript/v5/BuyHistoryParams)
* @param queryParams - options to configure the react query
* @returns A React Query object which contains the data of type [`BuyHistoryData`](https://portal.thirdweb.com/references/typescript/v5/BuyHistoryData)
* @example
* ```tsx
* import { useBuyHistory } from "thirdweb/react";
*
* function Component() {
* const buyHistoryQuery = useBuyHistory(params);
* return <div> ... </div>
* }
* ```
*/
export function useBuyHistory(
params?: BuyHistoryParams,
queryParams?: BuyHistoryQueryOptions,
): UseQueryResult<BuyHistoryData> {
return useQuery({
...queryParams,
queryKey: ["getBuyHistory", params],
queryFn: () => {
if (!params) {
throw new Error("params are required");
}
return getBuyHistory(params);
},
enabled: !!params,
});
}
@@ -7,20 +7,22 @@ import {
type BuyWithCryptoHistoryData,
type BuyWithCryptoHistoryParams,
getBuyWithCryptoHistory,
} from "../../../../pay/buyWithCrypto/actions/getHistory.js";
} from "../../../../pay/buyWithCrypto/getHistory.js";
export type BuyWithCryptoHistoryQueryParams = BuyWithCryptoHistoryParams;
export type BuyWithCryptoQuoteQueryOptions = Omit<
/**
* @internal
*/
export type BuyWithCryptoHistoryQueryOptions = Omit<
UseQueryOptions<BuyWithCryptoHistoryData>,
"queryFn" | "queryKey" | "enabled"
>;
/**
* Hook to get the history of purchases a given wallet has performed.
* Hook to get the "Buy with crypto" transaction history for a given wallet address.
*
* This hook is a React Query wrapper of the [`getBuyWithCryptoHistory`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoHistory) function.
* You can also use that function directly
* @param buyWithCryptoHistoryParams - object of type [`BuyWithCryptoHistoryParams`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoHistoryParams)
* @param params - object of type [`BuyWithCryptoHistoryParams`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoHistoryParams)
* @param queryParams - options to configure the react query
* @returns A React Query object which contains the data of type [`BuyWithCryptoHistoryData`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoHistoryData)
* @example
@@ -29,31 +31,24 @@ export type BuyWithCryptoQuoteQueryOptions = Omit<
*
* function Component() {
* const buyWithCryptoHistory = useBuyWithCryptoHistory(params);
* return <pre>{JSON.stringify(buyWithCryptoHistory.data, null, 2)}</pre>
* return <div> ... </div>
* }
* ```
* @buyCrypto
*/
export function useBuyWithCryptoHistory(
buyWithCryptoHistoryParams?: BuyWithCryptoHistoryQueryParams,
queryParams?: BuyWithCryptoQuoteQueryOptions,
params?: BuyWithCryptoHistoryParams,
queryParams?: BuyWithCryptoHistoryQueryOptions,
): UseQueryResult<BuyWithCryptoHistoryData> {
return useQuery({
...queryParams,
queryKey: ["buyWithCryptoHistory", buyWithCryptoHistoryParams],
queryKey: ["getBuyWithCryptoHistory", params],
queryFn: () => {
if (!buyWithCryptoHistoryParams) {
if (!params) {
throw new Error("Swap params are required");
}
if (!buyWithCryptoHistoryParams?.client) {
throw new Error("Client is required");
}
return getBuyWithCryptoHistory({
...buyWithCryptoHistoryParams,
client: buyWithCryptoHistoryParams.client,
});
return getBuyWithCryptoHistory(params);
},
enabled: !!buyWithCryptoHistoryParams,
enabled: !!params,
});
}
@@ -7,15 +7,20 @@ import {
type BuyWithCryptoQuote,
type GetBuyWithCryptoQuoteParams,
getBuyWithCryptoQuote,
} from "../../../../pay/buyWithCrypto/actions/getQuote.js";
} from "../../../../pay/buyWithCrypto/getQuote.js";
/**
* @internal
*/
export type BuyWithCryptoQuoteQueryOptions = Omit<
UseQueryOptions<BuyWithCryptoQuote>,
"queryFn" | "queryKey" | "enabled"
>;
export type BuyWithCryptoQuoteQueryParams = GetBuyWithCryptoQuoteParams;
/**
* Hook to get a quote of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote) for buying tokens with crypto.
* Hook to get a price quote for performing a "Buy with crypto" transaction that allows users to buy a token with another token - aka a swap.
*
* The price quote is an object of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote).
* This quote contains the information about the purchase such as token amounts, processing fees, estimated time etc.
*
* This hook is a React Query wrapper of the [`getBuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoQuote) function.
@@ -23,16 +28,16 @@ export type BuyWithCryptoQuoteQueryParams = GetBuyWithCryptoQuoteParams;
*
* Once you have the quote, you can use the [`useSendTransaction`](https://portal.thirdweb.com/references/typescript/v5/useSendTransaction) function to send the purchase
* and [`useBuyWithCryptoStatus`](https://portal.thirdweb.com/references/typescript/v5/useBuyWithCryptoStatus) function to get the status of the swap transaction.
* @param buyWithCryptoParams - object of type [`BuyWithCryptoQuoteQueryParams`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuoteQueryParams)
* @param params - object of type [`BuyWithCryptoQuoteQueryParams`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuoteQueryParams)
* @param queryParams - options to configure the react query
* @returns A React Query object which contains the data of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote)
* @example
* ```tsx
* import { useSendTransaction, useBuyWithCryptoQuote, useBuyWithCryptoStatus, type BuyWithCryptoStatusQueryParams } from "thirdweb/react";
* import { useBuyWithCryptoQuote, useBuyWithCryptoStatus, type BuyWithCryptoStatusQueryParams, useActiveAccount } from "thirdweb/react";
* import { sendTransaction } from 'thirdweb';
*
* function Component() {
* const buyWithCryptoQuoteQuery = useBuyWithCryptoQuote(swapParams);
* const sendTransactionMutation = useSendTransaction();
* const [buyTxHash, setBuyTxHash] = useState<BuyWithCryptoStatusQueryParams | undefined>();
* const buyWithCryptoStatusQuery = useBuyWithCryptoStatus(buyTxHash ? {
* client,
@@ -40,16 +45,23 @@ export type BuyWithCryptoQuoteQueryParams = GetBuyWithCryptoQuoteParams;
* }: undefined);
*
* async function handleBuyWithCrypto() {
* const account = useActiveAccount();
*
* // if approval is required
* if (buyWithCryptoQuoteQuery.data.approval) {
* const approveTx = await sendTransactionMutation.mutateAsync(swapQuote.data.approval);
* const approveTx = await sendTransaction({
* transaction: swapQuote.data.approval,
* account: account,
* });
* await waitForApproval(approveTx);
* }
*
* // send the transaction to buy crypto
* // this promise is resolved when user confirms the transaction in the wallet and the transaction is sent to the blockchain
* const buyTx = await sendTransactionMutation.mutateAsync(swapQuote.data.transactionRequest);
* const buyTx = await sendTransaction({
* transaction: swapQuote.data.transactionRequest,
* account: account,
* });
* await waitForApproval(buyTx);
*
* // set buyTx.transactionHash to poll the status of the swap transaction
@@ -62,26 +74,30 @@ export type BuyWithCryptoQuoteQueryParams = GetBuyWithCryptoQuoteParams;
* @buyCrypto
*/
export function useBuyWithCryptoQuote(
buyWithCryptoParams?: BuyWithCryptoQuoteQueryParams,
params?: GetBuyWithCryptoQuoteParams,
queryParams?: BuyWithCryptoQuoteQueryOptions,
): UseQueryResult<BuyWithCryptoQuote> {
return useQuery({
...queryParams,
queryKey: ["buyWithCryptoQuote", buyWithCryptoParams],
queryKey: ["buyWithCryptoQuote", params],
queryFn: () => {
if (!buyWithCryptoParams) {
if (!params) {
throw new Error("Swap params are required");
}
if (!buyWithCryptoParams?.client) {
throw new Error("Client is required in swap params");
}
return getBuyWithCryptoQuote({
// typescript limitation with discriminated unions are collapsed
...(buyWithCryptoParams as GetBuyWithCryptoQuoteParams),
client: buyWithCryptoParams.client,
});
return getBuyWithCryptoQuote(params);
},
enabled: !!params,
retry(failureCount, error) {
if (failureCount > 3) {
return false;
}
if (error.message.includes("Minimum purchase")) {
return false;
}
return true;
},
enabled: !!buyWithCryptoParams,
});
}
@@ -1,47 +1,48 @@
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import {
type BuyWithCryptoStatus,
type BuyWithCryptoTransaction,
getBuyWithCryptoStatus,
} from "../../../../pay/buyWithCrypto/actions/getStatus.js";
// TODO: use the estimate to vary the polling interval
const DEFAULT_POLL_INTERVAL = 5000;
export type BuyWithCryptoStatusQueryParams = BuyWithCryptoTransaction;
} from "../../../../pay/buyWithCrypto/getStatus.js";
/**
* A hook to get a status of swap transaction.
* A hook to get a status of a "Buy with crypto" transaction to determine if the transaction is completed, failed or pending.
*
* This hook is a React Query wrapper of the [`getBuyWithCryptoStatus`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoStatus) function.
* You can also use that function directly.
* @param buyWithCryptoStatusParams - object of type [`BuyWithCryptoTransaction`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoTransaction)
* @param params - object of type [`BuyWithCryptoTransaction`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoTransaction)
* @returns A react query object which contains the data of type [`BuyWithCryptoStatus`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoStatus)
* @example
* ```tsx
* import { useSendTransaction, useBuyWithCryptoQuote, useBuyWithCryptoStatus, type BuyWithCryptoStatusQueryParams } from "thirdweb/react";
* import { useSendTransaction, useBuyWithCryptoQuote, useBuyWithCryptoStatus, type BuyWithCryptoStatusQueryParams, useActiveAccount } from "thirdweb/react";
* import { sendTransaction } from 'thirdweb';
*
* function Component() {
* const buyWithCryptoQuoteQuery = useBuyWithCryptoQuote(swapParams);
* const sendTransactionMutation = useSendTransaction();
* const [buyTxHash, setBuyTxHash] = useState<BuyWithCryptoStatusQueryParams | undefined>();
* const buyWithCryptoStatusQuery = useBuyWithCryptoStatus(buyTxHash ? {
* client,
* transactionHash: buyTxHash,
* }: undefined);
* const account = useActiveAccount();
*
* async function handleBuyWithCrypto() {
*
* // if approval is required
* if (buyWithCryptoQuoteQuery.data.approval) {
* const approveTx = await sendTransactionMutation.mutateAsync(swapQuote.data.approval);
* const approveTx = await sendTransaction({
* account: account,
* transaction: swapQuote.data.approval,
* });
* await waitForApproval(approveTx);
* }
*
* // send the transaction to buy crypto
* // this promise is resolved when user confirms the transaction in the wallet and the transaction is sent to the blockchain
* const buyTx = await sendTransactionMutation.mutateAsync(swapQuote.data.transactionRequest);
* const buyTx = await sendTransactionMutation.mutateAsync({
* transaction: swapQuote.data.transactionRequest,
* account: account,
* });
* await waitForApproval(buyTx);
*
* // set buyTx.transactionHash to poll the status of the swap transaction
@@ -53,40 +54,17 @@ export type BuyWithCryptoStatusQueryParams = BuyWithCryptoTransaction;
* ```
* @buyCrypto
*/
export function useBuyWithCryptoStatus(
buyWithCryptoStatusParams?: BuyWithCryptoStatusQueryParams,
) {
const [refetchInterval, setRefetchInterval] = useState<number>(
DEFAULT_POLL_INTERVAL,
);
export function useBuyWithCryptoStatus(params?: BuyWithCryptoTransaction) {
return useQuery<BuyWithCryptoStatus, Error>({
queryKey: [
"swapStatus",
buyWithCryptoStatusParams?.transactionHash,
] as const,
queryKey: ["getBuyWithCryptoStatus", params?.transactionHash] as const,
queryFn: async () => {
if (!buyWithCryptoStatusParams) {
throw new Error("Missing swap status params");
if (!params) {
throw new Error("No params");
}
if (!buyWithCryptoStatusParams?.client) {
throw new Error("Missing client in swap status params");
}
const swapStatus_ = await getBuyWithCryptoStatus({
...buyWithCryptoStatusParams,
client: buyWithCryptoStatusParams.client,
});
if (
swapStatus_.status === "COMPLETED" ||
swapStatus_.status === "FAILED"
) {
setRefetchInterval(0);
}
return swapStatus_;
return getBuyWithCryptoStatus(params);
},
enabled: !!buyWithCryptoStatusParams,
refetchInterval: refetchInterval,
enabled: !!params,
refetchInterval: 5000,
refetchIntervalInBackground: true,
retry: true,
});
@@ -0,0 +1,54 @@
import {
type UseQueryOptions,
type UseQueryResult,
useQuery,
} from "@tanstack/react-query";
import {
type BuyWithFiatHistoryData,
type BuyWithFiatHistoryParams,
getBuyWithFiatHistory,
} from "../../../../pay/buyWithFiat/getHistory.js";
/**
* @internal
*/
export type BuyWithFiatHistoryQueryOptions = Omit<
UseQueryOptions<BuyWithFiatHistoryData>,
"queryFn" | "queryKey" | "enabled"
>;
/**
* Hook to get the "Buy with Fiat" transaction history for a given wallet address.
*
* This hook is a React Query wrapper of the [`getBuyWithFiatHistory`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatHistory) function.
* You can also use that function directly
* @param params - object of type [`BuyWithFiatHistoryParams`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatHistoryParams)
* @param queryParams - options to configure the react query
* @returns A React Query object which contains the data of type [`BuyWithFiatHistoryData`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatHistoryData)
* @example
* ```tsx
* import { useBuyWithFiatHistory } from "thirdweb/react";
*
* function Component() {
* const historyQuery = useBuyWithFiatHistory(params);
* return <div> ... </div>
* }
* ```
* @buyFiat
*/
export function useBuyWithFiatHistory(
params?: BuyWithFiatHistoryParams,
queryParams?: BuyWithFiatHistoryQueryOptions,
): UseQueryResult<BuyWithFiatHistoryData> {
return useQuery({
...queryParams,
queryKey: ["buyWithFiatHistory", params],
queryFn: () => {
if (!params) {
throw new Error("params are required");
}
return getBuyWithFiatHistory(params);
},
enabled: !!params,
});
}
@@ -0,0 +1,95 @@
import {
type UseQueryOptions,
type UseQueryResult,
useQuery,
} from "@tanstack/react-query";
import {
type BuyWithFiatQuote,
type GetBuyWithFiatQuoteParams,
getBuyWithFiatQuote,
} from "../../../../pay/buyWithFiat/getQuote.js";
/**
* @internal
*/
export type BuyWithFiatQuoteQueryOptions = Omit<
UseQueryOptions<BuyWithFiatQuote>,
"queryFn" | "queryKey" | "enabled"
>;
/**
* Hook to get a price quote for performing a "Buy with Fiat" transaction that allows users to buy a token with fiat currency.
*
* The price quote is an object of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote).
* This quote contains the information about the purchase such as token amounts, processing fees, estimated time etc.
*
* This hook is a React Query wrapper of the [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote) function.
* You can also use that function directly
*
* Once you have the `quote`, you can open a new window with `quote.onRampLink` to allow the user to buy the token with fiat currency.
* and [`useBuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/useBuyWithFiatStatus) function to start polling for the status of this transaction.
*
* @param params - object of type [`GetBuyWithFiatQuoteParams`](https://portal.thirdweb.com/references/typescript/v5/GetBuyWithFiatQuoteParams)
* @param queryParams - options to configure the react query
* @returns A React Query object which contains the data of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote)
* @example
* ```ts
* import { NATIVE_TOKEN_ADDRESS } from "thirdweb";
* import { base } from "thirdweb/chains";
* import { useBuyWithFiatQuote } from "thirdweb/react";
*
* // get a quote for buying 0.01 base native token with USD fiat currency
* function Example() {
* const quote = useBuyWithFiatQuote({
* client: client, // thirdweb client
* fromCurrencySymbol: "USD", // fiat currency symbol
* toChainId: base.id, // base chain id
* toAmount: "0.01", // amount of token to buy
* toTokenAddress: NATIVE_TOKEN_ADDRESS, // native token
* toAddress: "0x...", // user's wallet address
* });
*
* return (
* <div>
* {quote.data && (
* <a href={quote.data.onRampLink} target="_blank">
* open onramp provider
* </a>
* )}
* </div>
* );
* }
* ```
* @buyFiat
*/
export function useBuyWithFiatQuote(
params?: GetBuyWithFiatQuoteParams,
queryOptions?: BuyWithFiatQuoteQueryOptions,
): UseQueryResult<BuyWithFiatQuote> {
return useQuery({
...queryOptions,
queryKey: ["useBuyWithFiatQuote", params],
queryFn: async () => {
if (!params) {
throw new Error("No params provided");
}
return getBuyWithFiatQuote(params);
},
enabled: !!params,
retry(failureCount, error) {
if (failureCount > 3) {
return false;
}
try {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
if ((error as any).error.code === "MINIMUM_PURCHASE_AMOUNT") {
return false;
}
} catch {
return true;
}
return true;
},
});
}
@@ -0,0 +1,52 @@
import { type UseQueryResult, useQuery } from "@tanstack/react-query";
import {
type BuyWithFiatStatus,
type GetBuyWithFiatStatusParams,
getBuyWithFiatStatus,
} from "../../../../pay/buyWithFiat/getStatus.js";
/**
* A hook to get a status of a "Buy with Fiat" transaction to determine if the transaction is completed, failed or pending.
*
* This hook is a React Query wrapper of the [`getBuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoStatus) function.
* You can also use that function directly.
*
* `useBuyWithFiatStatus` refetches the status using `getBuyWithFiatStatus` every 5 seconds.
*
* @param params - object of type [`GetBuyWithFiatStatusParams`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoTransaction)
* @returns A react query object which contains the data of type [`BuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoStatus)
* @example
* ```tsx
* import { useBuyWithFiatStatus } from "thirdweb/react";
* import { client } from "./client";
*
* function Example() {
* const fiatStatus = useBuyWithFiatStatus({
* client: client, // thirdweb client
* intentId: "....", // get the intentId from quote ( quote.intentId )
* });
*
* console.log(fiatStatus.data);
*
* return <div>...</div>;
* }
* ```
* @buyFiat
*/
export function useBuyWithFiatStatus(
params?: GetBuyWithFiatStatusParams,
): UseQueryResult<BuyWithFiatStatus> {
return useQuery({
queryKey: ["useBuyWithFiatStatus", params],
queryFn: async () => {
if (!params) {
throw new Error("No params provided");
}
return getBuyWithFiatStatus(params);
},
enabled: !!params,
refetchInterval: 5000,
refetchIntervalInBackground: true,
retry: true,
});
}
@@ -0,0 +1,52 @@
import {
type UseQueryOptions,
type UseQueryResult,
useQuery,
} from "@tanstack/react-query";
import type { BuyWithCryptoQuote } from "../../../../pay/buyWithCrypto/getQuote.js";
import {
type GetPostOnRampQuoteParams,
getPostOnRampQuote,
} from "../../../../pay/buyWithFiat/getPostOnRampQuote.js";
/**
* @internal
*/
export type PostOnRampQuoteQueryOptions = Omit<
UseQueryOptions<BuyWithCryptoQuote>,
"queryFn" | "queryKey" | "enabled"
>;
/**
* When buying a token with fiat currency - It only involes doing on-ramp if the on-ramp provider supports buying the given destination token directly.
*
* If the on-ramp provider does not support buying the destination token directly, user can be sent an intermediate token with fiat currency from the on-ramp provider which
* can be swapped to destination token onchain.
*
* `usePostOnRampQuote` hook is used to get the quote for swapping the on-ramp token to destination token.
*
* When you get a "Buy with Fiat" status of type `"CRYPTO_SWAP_REQUIRED"` from the [`useBuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/useBuyWithFiatStatus) hook,
* you can use `usePostOnRampQuote` hook to get the quote of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote) for swapping the on-ramp token to destination token to complete the step-2 of the process.
*
* Once you have the quote, you can start the Swap process by following the same steps as mentioned in the [`useBuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/useBuyWithCryptoQuote) documentation.
*
* @param params - object of type [`GetPostOnRampQuoteParams`](https://portal.thirdweb.com/references/typescript/v5/GetPostOnRampQuoteParams)
* @returns Object of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote) which contains the information about the quote such as processing fees, estimated time, converted token amounts, etc.
* @buyFiat
*/
export function usePostOnRampQuote(
params?: GetPostOnRampQuoteParams,
queryOptions?: PostOnRampQuoteQueryOptions,
): UseQueryResult<BuyWithCryptoQuote> {
return useQuery({
...queryOptions,
queryKey: ["getPostOnRampQuote", params],
queryFn: async () => {
if (!params) {
throw new Error("No params provided");
}
return getPostOnRampQuote(params);
},
enabled: !!params,
});
}
@@ -0,0 +1,12 @@
import type { QueryClient } from "@tanstack/react-query";
export function invalidateWalletBalance(
queryClient: QueryClient,
chainId?: number,
) {
return queryClient.invalidateQueries({
// invalidate any walletBalance queries for this chainId
// TODO: add wallet address in here if we can get it somehow
queryKey: ["walletBalance", chainId] as const,
});
}
@@ -8,6 +8,7 @@ import {
import { isBaseTransactionOptions } from "../../../transaction/types.js";
import { isObjectWithKeys } from "../../../utils/type-guards.js";
import { SetRootElementContext } from "./RootElementContext.js";
import { invalidateWalletBalance } from "./invalidateWalletBalance.js";
/**
* The ThirdwebProvider is component is a provider component that sets up the React Query client.
@@ -58,14 +59,10 @@ export function ThirdwebProvider(props: React.PropsWithChildren) {
variables.__contract?.address,
] as const,
}),
queryClient.invalidateQueries({
// invalidate any walletBalance queries for this chainId
// TODO: add wallet address in here if we can get it somehow
queryKey: [
"walletBalance",
variables.__contract?.chain.id,
] as const,
}),
invalidateWalletBalance(
queryClient,
variables.__contract?.chain.id,
),
]);
});
}
@@ -1,4 +1,5 @@
import { useContext, useState } from "react";
import { CheckCircledIcon } from "@radix-ui/react-icons";
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import type { ThirdwebClient } from "../../../client/client.js";
import type { GaslessOptions } from "../../../transaction/actions/gasless/types.js";
import type { PreparedTransaction } from "../../../transaction/prepare-transaction.js";
@@ -6,16 +7,20 @@ import type { Wallet } from "../../../wallets/interfaces/wallet.js";
import { useSendTransactionCore } from "../../core/hooks/contract/useSendTransaction.js";
import { useActiveWallet } from "../../core/hooks/wallets/wallet-hooks.js";
import { SetRootElementContext } from "../../core/providers/RootElementContext.js";
import {
type SupportedTokens,
defaultTokens,
} from "../ui/ConnectWallet/defaultTokens.js";
import type { PayUIOptions } from "../ui/ConnectWallet/ConnectButtonProps.js";
import type { SupportedTokens } from "../ui/ConnectWallet/defaultTokens.js";
import { AccentFailIcon } from "../ui/ConnectWallet/icons/AccentFailIcon.js";
import { useConnectLocale } from "../ui/ConnectWallet/locale/getConnectLocale.js";
import { BuyScreen } from "../ui/ConnectWallet/screens/Buy/SwapScreen.js";
import { SwapTransactionsScreen } from "../ui/ConnectWallet/screens/SwapTransactionsScreen.js";
import { LazyBuyScreen } from "../ui/ConnectWallet/screens/Buy/LazyBuyScreen.js";
import { BuyTxHistory } from "../ui/ConnectWallet/screens/Buy/tx-history/BuyTxHistory.js";
import { Modal } from "../ui/components/Modal.js";
import { Spacer } from "../ui/components/Spacer.js";
import { Spinner } from "../ui/components/Spinner.js";
import { Container, ModalHeader } from "../ui/components/basic.js";
import { Button } from "../ui/components/buttons.js";
import { Text } from "../ui/components/text.js";
import { CustomThemeProvider } from "../ui/design-system/CustomThemeProvider.js";
import type { Theme } from "../ui/design-system/index.js";
import { type Theme, iconSize } from "../ui/design-system/index.js";
import type { LocaleId } from "../ui/types.js";
import { LoadingScreen } from "../wallets/shared/LoadingScreen.js";
@@ -44,6 +49,12 @@ export type SendTransactionPayModalConfig =
locale?: LocaleId;
supportedTokens?: SupportedTokens;
theme?: Theme | "light" | "dark";
buyWithCrypto?: false;
buyWithFiat?:
| false
| {
testMode?: boolean;
};
}
| false;
@@ -52,26 +63,10 @@ export type SendTransactionPayModalConfig =
*/
export type SendTransactionConfig = {
/**
* Configuration for the "Pay Modal" that opens when the user doesn't have enough funds to send a transaction.
* Set `payModal: false` to disable the "Pay Modal" popup
*
* This configuration object includes the following properties to configure the "Pay Modal" UI:
*
* ### `locale`
* The language to use for the "Pay Modal" UI. Defaults to `"en_US"`.
*
* ### `supportedTokens`
* An object of type [`SupportedTokens`](https://portal.thirdweb.com/references/typescript/v5/SupportedTokens) to configure the tokens to show for a chain.
*
* ### `theme`
* The theme to use for the "Pay Modal" UI. Defaults to `"dark"`.
*
* It can be set to `"light"` or `"dark"` or an object of type [`Theme`](https://portal.thirdweb.com/references/typescript/v5/Theme) for a custom theme.
*
* Refer to [`lightTheme`](https://portal.thirdweb.com/references/typescript/v5/lightTheme)
* or [`darkTheme`](https://portal.thirdweb.com/references/typescript/v5/darkTheme) helper functions to use the default light or dark theme and customize it.
* Refer to [`SendTransactionPayModalConfig`](https://portal.thirdweb.com/references/typescript/v5/SendTransactionPayModalConfig) for more details.
*/
payModal?: SendTransactionPayModalConfig;
/**
* Configuration for gasless transactions.
* Refer to [`GaslessOptions`](https://portal.thirdweb.com/references/typescript/v5/GaslessOptions) for more details.
@@ -133,11 +128,15 @@ export function useSendTransaction(config: SendTransactionConfig = {}) {
}}
client={data.tx.client}
localeId={payModal?.locale || "en_US"}
supportedTokens={payModal?.supportedTokens || defaultTokens}
supportedTokens={payModal?.supportedTokens}
theme={payModal?.theme || "dark"}
txCostWei={data.totalCostWei}
walletBalanceWei={data.walletBalance.value}
nativeTokenSymbol={data.walletBalance.symbol}
payOptions={{
buyWithCrypto: payModal?.buyWithCrypto,
buyWithFiat: payModal?.buyWithFiat,
}}
/>,
);
},
@@ -150,12 +149,13 @@ type ModalProps = {
onClose: () => void;
client: ThirdwebClient;
localeId: LocaleId;
supportedTokens: SupportedTokens;
supportedTokens?: SupportedTokens;
theme: Theme | "light" | "dark";
txCostWei: bigint;
walletBalanceWei: bigint;
nativeTokenSymbol: string;
tx: PreparedTransaction;
payOptions: PayUIOptions;
};
function TxModal(props: ModalProps) {
@@ -178,29 +178,38 @@ function TxModal(props: ModalProps) {
function ModalContent(props: ModalProps) {
const localeQuery = useConnectLocale(props.localeId);
const [screen, setScreen] = useState<"buy" | "tx-history">("buy");
const [screen, setScreen] = useState<"buy" | "tx-history" | "execute-tx">(
"buy",
);
if (!localeQuery.data) {
return <LoadingScreen />;
}
if (screen === "execute-tx") {
return <ExecutingTxScreen tx={props.tx} closeModal={props.onClose} />;
}
if (screen === "tx-history") {
return (
<SwapTransactionsScreen
<BuyTxHistory
client={props.client}
onBack={() => {
props.onClose();
setScreen("buy");
}}
onDone={() => {
setScreen("execute-tx");
}}
isBuyForTx={true}
isEmbed={false}
/>
);
}
return (
<BuyScreen
<LazyBuyScreen
isEmbed={false}
client={props.client}
onBack={() => {
props.onClose();
}}
onViewPendingTx={() => {
setScreen("tx-history");
}}
@@ -212,6 +221,86 @@ function ModalContent(props: ModalProps) {
tx: props.tx,
tokenSymbol: props.nativeTokenSymbol,
}}
theme={typeof props.theme === "string" ? props.theme : props.theme.type}
payOptions={props.payOptions}
onDone={() => {
setScreen("execute-tx");
}}
/>
);
}
function ExecutingTxScreen(props: {
tx: PreparedTransaction;
closeModal: () => void;
}) {
const sendTxCore = useSendTransactionCore();
const [status, setStatus] = useState<"loading" | "failed" | "sent">(
"loading",
);
const sendTx = useCallback(async () => {
setStatus("loading");
try {
await sendTxCore.mutateAsync(props.tx);
setStatus("sent");
} catch (e) {
console.error(e);
setStatus("failed");
}
}, [sendTxCore, props.tx]);
const done = useRef(false);
useEffect(() => {
if (done.current) {
return;
}
done.current = true;
sendTx();
}, [sendTx]);
return (
<Container p="lg">
<ModalHeader title="Transaction" />
<Spacer y="xxl" />
<Spacer y="xxl" />
<Container flex="row" center="x">
{status === "loading" && <Spinner size="3xl" color="accentText" />}
{status === "failed" && <AccentFailIcon size={iconSize["3xl"]} />}
{status === "sent" && (
<Container color="success" flex="row" center="both">
<CheckCircledIcon
width={iconSize["3xl"]}
height={iconSize["3xl"]}
/>
</Container>
)}
</Container>
<Spacer y="lg" />
<Text color="primaryText" center size="lg">
{status === "loading" && "Sending transaction"}
{status === "failed" && "Transaction failed"}
{status === "sent" && "Transaction sent"}
</Text>
<Spacer y="xxl" />
<Spacer y="xxl" />
{status === "failed" && (
<Button variant="accent" fullWidth onClick={sendTx}>
Try Again
</Button>
)}
{status === "sent" && (
<Button variant="accent" fullWidth onClick={props.closeModal}>
Done
</Button>
)}
</Container>
);
}
@@ -158,7 +158,7 @@ function ConnectButtonInner(
const supportedTokens = useMemo(() => {
if (!props.supportedTokens) {
return defaultTokens;
return undefined;
}
const tokens = { ...defaultTokens };
@@ -10,6 +10,72 @@ import type { NetworkSelectorProps } from "./NetworkSelector.js";
import type { SupportedTokens } from "./defaultTokens.js";
import type { WelcomeScreen } from "./screens/types.js";
export type PayUIOptions = {
/**
* Prefill the Buy Token amount, chain and/or token.
* You can also disable the edits for the prefilled values using `allowEdits` - By default all are editable
*
* For example, if you want to allow changing the amount, but disable changing the token and chain,
* you can set `allowEdits` to `{ amount: true, token: false, chain: false }`
*
* If no `token` object is not specified, native token will be prefilled by default
*/
prefillBuy?: {
chain: Chain;
token?: {
name: string;
symbol: string;
address: string;
icon?: string;
};
amount?: string;
allowEdits?: {
amount: boolean;
token: boolean;
chain: boolean;
};
};
/**
* Configure options for buying tokens using other token ( aka Swap )
*
* By default, the "Crypto" option is enabled. You can disable it by setting `buyWithCrypto` to `false`
*
* You can prefill the source token and chain using `prefillSource`
* You can also disable the edits for the prefilled values by setting `prefillSource.allowEdits` - By default all are editable
*
* For example, if you want to allow selecting chain and but disable selecting token, you can set `allowEdits` to `{ token: false, chain: true }`
*/
buyWithCrypto?:
| false
| {
prefillSource?: {
chain: Chain;
token?: {
name: string;
symbol: string;
address: string;
icon?: string;
};
allowEdits?: {
token: boolean;
chain: boolean;
};
};
};
/**
* By default "Credit card" option is enabled. you can disable it by setting `buyWithFiat` to `false`
*
* You can also enable the test mode for the on-ramp provider to test on-ramp without using real credit card.
*/
buyWithFiat?:
| {
testMode?: boolean;
}
| false;
};
/**
* Options for configuring the `ConnectButton`'s Connect Button
* @connectWallet
@@ -109,6 +175,13 @@ export type ConnectButton_detailsModalOptions = {
* ```
*/
footer?: (props: { close: () => void }) => JSX.Element;
/**
* Configure options for thirdweb Pay.
*
* thirdweb Pay allows users to buy tokens using crypto or fiat currency.
*/
payOptions?: PayUIOptions;
};
/**
@@ -9,7 +9,7 @@ import {
TextAlignJustifyIcon,
} from "@radix-ui/react-icons";
import { useQuery } from "@tanstack/react-query";
import { useEffect, useState, useSyncExternalStore } from "react";
import { useEffect, useState } from "react";
import { ethereum } from "../../../../chains/chain-definitions/ethereum.js";
import type { Chain } from "../../../../chains/types.js";
import { getContract } from "../../../../contract/contract.js";
@@ -44,7 +44,7 @@ import { Button, IconButton } from "../components/buttons.js";
import { Link, Text } from "../components/text.js";
import { useCustomTheme } from "../design-system/CustomThemeProvider.js";
import { fadeInAnimation } from "../design-system/animations.js";
import { StyledButton, StyledDiv } from "../design-system/elements.js";
import { StyledButton } from "../design-system/elements.js";
import {
type Theme,
fontSize,
@@ -64,11 +64,12 @@ import type { SupportedTokens } from "./defaultTokens.js";
import { FundsIcon } from "./icons/FundsIcon.js";
import { SmartWalletBadgeIcon } from "./icons/SmartAccountBadgeIcon.js";
import { WalletIcon } from "./icons/WalletIcon.js";
import { BuyScreen } from "./screens/Buy/SwapScreen.js";
import { swapTransactionsStore } from "./screens/Buy/swap/pendingSwapTx.js";
import { genericTokenIcon } from "./icons/dataUris.js";
import { LazyBuyScreen } from "./screens/Buy/LazyBuyScreen.js";
import { BuyTxHistory } from "./screens/Buy/tx-history/BuyTxHistory.js";
import { ReceiveFunds } from "./screens/ReceiveFunds.js";
import { SendFunds } from "./screens/SendFunds.js";
import { SwapTransactionsScreen } from "./screens/SwapTransactionsScreen.js";
import { ViewFunds } from "./screens/ViewFunds.js";
const TW_CONNECTED_WALLET = "tw-connected-wallet";
@@ -81,7 +82,8 @@ type WalletDetailsModalScreen =
| "receive"
| "buy"
| "network-switcher"
| "pending-tx";
| "pending-tx"
| "view-funds";
/**
* @internal
@@ -91,23 +93,18 @@ export const ConnectedWalletDetails: React.FC<{
detailsButton?: ConnectButton_detailsButtonOptions;
detailsModal?: ConnectButton_detailsModalOptions;
theme: "light" | "dark" | Theme;
supportedTokens: SupportedTokens;
supportedTokens?: SupportedTokens;
chains: Chain[];
chain?: Chain;
switchButton: ConnectButtonProps["switchButton"];
}> = (props) => {
const { connectLocale: locale, client } = useConnectUI();
const activeWallet = useActiveWallet();
const activeAccount = useActiveAccount();
const walletChain = useActiveWalletChain();
const chainQuery = useChainQuery(walletChain);
const { disconnect } = useDisconnect();
const swapTxs = useSyncExternalStore(
swapTransactionsStore.subscribe,
swapTransactionsStore.getValue,
);
const pendingSwapTxs = swapTxs.filter((tx) => tx.status === "PENDING");
// prefetch chains metadata with low concurrency
useChainsQuery(props.chains, 5);
@@ -480,12 +477,26 @@ export const ConnectedWalletDetails: React.FC<{
<TextAlignJustifyIcon width={iconSize.md} height={iconSize.md} />
<Container flex="row" gap="xs" center="y">
<Text color="primaryText">{locale.transactions}</Text>
{pendingSwapTxs && pendingSwapTxs.length > 0 && (
<BadgeCount>{pendingSwapTxs.length}</BadgeCount>
)}
</Container>
</MenuButton>
<MenuButton
onClick={() => {
setScreen("view-funds");
}}
style={{
fontSize: fontSize.sm,
}}
>
<Img
width={iconSize.md}
height={iconSize.md}
src={genericTokenIcon}
client={client}
/>
<Text color="primaryText">View Funds</Text>
</MenuButton>
{/* Switch to Personal Wallet */}
{/* {personalWallet &&
!props.detailsModal?.hideSwitchToPersonalWallet && (
@@ -585,9 +596,14 @@ export const ConnectedWalletDetails: React.FC<{
if (screen === "pending-tx") {
content = (
<SwapTransactionsScreen
<BuyTxHistory
isBuyForTx={false}
isEmbed={false}
onBack={() => setScreen("main")}
client={client}
onDone={() => {
setIsOpen(false);
}}
/>
);
}
@@ -629,6 +645,19 @@ export const ConnectedWalletDetails: React.FC<{
// );
// }
// send funds
else if (screen === "view-funds") {
content = (
<ViewFunds
supportedTokens={props.supportedTokens}
onBack={() => {
setScreen("main");
}}
client={client}
/>
);
}
// send funds
else if (screen === "send") {
content = (
@@ -653,15 +682,21 @@ export const ConnectedWalletDetails: React.FC<{
);
}
// swap tokens
// thirdweb pay
else if (screen === "buy") {
content = (
<BuyScreen
<LazyBuyScreen
isEmbed={false}
client={client}
onBack={() => setScreen("main")}
supportedTokens={props.supportedTokens}
onViewPendingTx={() => setScreen("pending-tx")}
connectLocale={locale}
payOptions={props.detailsModal?.payOptions || {}}
theme={typeof props.theme === "string" ? props.theme : props.theme.type}
onDone={() => {
setIsOpen(false);
}}
/>
);
}
@@ -751,22 +786,6 @@ const MenuButton = /* @__PURE__ */ StyledButton(() => {
};
});
const BadgeCount = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
background: theme.colors.primaryButtonBg,
color: theme.colors.primaryButtonText,
fontSize: fontSize.sm,
fontWeight: 500,
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
minWidth: "22px",
minHeight: "22px",
};
});
const MenuLink = /* @__PURE__ */ (() => MenuButton.withComponent("a"))();
const StyledChevronRightIcon = /* @__PURE__ */ styled(
@@ -2,10 +2,10 @@
import { useEffect, useMemo } from "react";
import type { Chain } from "../../../../../chains/types.js";
import type { ThirdwebClient } from "../../../../../client/client.js";
import type { SiweAuthOptions } from "../../../../../exports/react.js";
import type { Wallet } from "../../../../../wallets/interfaces/wallet.js";
import type { SmartWalletOptions } from "../../../../../wallets/smart/types.js";
import type { AppMetadata } from "../../../../../wallets/types.js";
import type { SiweAuthOptions } from "../../../../core/hooks/auth/useSiweAuth.js";
import { useSiweAuth } from "../../../../core/hooks/auth/useSiweAuth.js";
import { AutoConnect } from "../../../../core/hooks/connection/useAutoConnect.js";
import {
@@ -2,7 +2,7 @@ export type TokenInfo = {
name: string;
symbol: string;
address: string;
icon: string;
icon?: string;
};
const wrappedEthIcon =
@@ -0,0 +1,28 @@
import type { IconFC } from "../types.js";
export const CADIcon: IconFC = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={props.size}
height={props.size}
viewBox="0 0 512 512"
aria-hidden="true"
>
<mask id="a">
<circle cx="256" cy="256" r="256" fill="#fff" />
</mask>
<g mask="url(#a)">
<path
fill="#d80027"
d="M0 0v512h144l112-64 112 64h144V0H368L256 64 144 0Z"
/>
<path fill="#eee" d="M144 0h224v512H144Z" />
<path
fill="#d80027"
d="m301 289 44-22-22-11v-22l-45 22 23-44h-23l-22-34-22 33h-23l23 45-45-22v22l-22 11 45 22-12 23h45v33h22v-33h45z"
/>
</g>
</svg>
);
};
@@ -0,0 +1,24 @@
import type { IconFC } from "../types.js";
export const EURIcon: IconFC = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={props.size}
height={props.size}
viewBox="0 0 512 512"
aria-hidden="true"
>
<mask id="a">
<circle cx="256" cy="256" r="256" fill="#fff" />
</mask>
<g mask="url(#a)">
<path fill="#0052b4" d="M0 0h512v512H0z" />
<path
fill="#ffda44"
d="m256 100.2 8.3 25.5H291l-21.7 15.7 8.3 25.6-21.7-15.8-21.7 15.8 8.3-25.6-21.7-15.7h26.8zm-110.2 45.6 24 12.2 18.9-19-4.2 26.5 23.9 12.2-26.5 4.2-4.2 26.5-12.2-24-26.5 4.3 19-19zM100.2 256l25.5-8.3V221l15.7 21.7 25.6-8.3-15.8 21.7 15.8 21.7-25.6-8.3-15.7 21.7v-26.8zm45.6 110.2 12.2-24-19-18.9 26.5 4.2 12.2-23.9 4.2 26.5 26.5 4.2-24 12.2 4.3 26.5-19-19zM256 411.8l-8.3-25.5H221l21.7-15.7-8.3-25.6 21.7 15.8 21.7-15.8-8.3 25.6 21.7 15.7h-26.8zm110.2-45.6-24-12.2-18.9 19 4.2-26.5-23.9-12.2 26.5-4.2 4.2-26.5 12.2 24 26.5-4.3-19 19zM411.8 256l-25.5 8.3V291l-15.7-21.7-25.6 8.3 15.8-21.7-15.8-21.7 25.6 8.3 15.7-21.7v26.8zm-45.6-110.2-12.2 24 19 18.9-26.5-4.2-12.2 23.9-4.2-26.5-26.5-4.2 24-12.2-4.3-26.5 19 19z"
/>
</g>
</svg>
);
};
@@ -0,0 +1,75 @@
import type { IconFC } from "../types.js";
export const GBPIcon: IconFC = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width={props.size}
height={props.size}
viewBox="0 0 512 512"
aria-hidden="true"
>
<circle fill="#F0F0F0" cx="256" cy="256" r="256" />
<g>
<path
fill="#0052B4"
d="M52.92,100.142c-20.109,26.163-35.272,56.318-44.101,89.077h133.178L52.92,100.142z"
/>
<path
fill="#0052B4"
d="M503.181,189.219c-8.829-32.758-23.993-62.913-44.101-89.076l-89.075,89.076H503.181z"
/>
<path
fill="#0052B4"
d="M8.819,322.784c8.83,32.758,23.993,62.913,44.101,89.075l89.074-89.075L8.819,322.784L8.819,322.784 z"
/>
<path
fill="#0052B4"
d="M411.858,52.921c-26.163-20.109-56.317-35.272-89.076-44.102v133.177L411.858,52.921z"
/>
<path
fill="#0052B4"
d="M100.142,459.079c26.163,20.109,56.318,35.272,89.076,44.102V370.005L100.142,459.079z"
/>
<path
fill="#0052B4"
d="M189.217,8.819c-32.758,8.83-62.913,23.993-89.075,44.101l89.075,89.075V8.819z"
/>
<path
fill="#0052B4"
d="M322.783,503.181c32.758-8.83,62.913-23.993,89.075-44.101l-89.075-89.075V503.181z"
/>
<path
fill="#0052B4"
d="M370.005,322.784l89.075,89.076c20.108-26.162,35.272-56.318,44.101-89.076H370.005z"
/>
</g>
<g>
<path
fill="#D80027"
d="M509.833,222.609h-220.44h-0.001V2.167C278.461,0.744,267.317,0,256,0 c-11.319,0-22.461,0.744-33.391,2.167v220.44v0.001H2.167C0.744,233.539,0,244.683,0,256c0,11.319,0.744,22.461,2.167,33.391 h220.44h0.001v220.442C233.539,511.256,244.681,512,256,512c11.317,0,22.461-0.743,33.391-2.167v-220.44v-0.001h220.442 C511.256,278.461,512,267.319,512,256C512,244.683,511.256,233.539,509.833,222.609z"
/>
<path
fill="#D80027"
d="M322.783,322.784L322.783,322.784L437.019,437.02c5.254-5.252,10.266-10.743,15.048-16.435 l-97.802-97.802h-31.482V322.784z"
/>
<path
fill="#D80027"
d="M189.217,322.784h-0.002L74.98,437.019c5.252,5.254,10.743,10.266,16.435,15.048l97.802-97.804 V322.784z"
/>
<path
fill="#D80027"
d="M189.217,189.219v-0.002L74.981,74.98c-5.254,5.252-10.266,10.743-15.048,16.435l97.803,97.803 H189.217z"
/>
<path
fill="#D80027"
d="M322.783,189.219L322.783,189.219L437.02,74.981c-5.252-5.254-10.743-10.266-16.435-15.047 l-97.802,97.803V189.219z"
/>
</g>
</svg>
);
};
@@ -0,0 +1,43 @@
import type { IconFC } from "../types.js";
/**
* @internal
*/
export const USDIcon: IconFC = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width={props.size}
height={props.size}
x="0px"
y="0px"
viewBox="0 0 512 512"
aria-hidden="true"
>
<circle fill="#F0F0F0" cx="256" cy="256" r="256" />
<g>
<path
fill="#D80027"
d="M244.87,256H512c0-23.106-3.08-45.49-8.819-66.783H244.87V256z"
/>
<path
fill="#D80027"
d="M244.87,122.435h229.556c-15.671-25.572-35.708-48.175-59.07-66.783H244.87V122.435z"
/>
<path
fill="#D80027"
d="M256,512c60.249,0,115.626-20.824,159.356-55.652H96.644C140.374,491.176,195.751,512,256,512z"
/>
<path
fill="#D80027"
d="M37.574,389.565h436.852c12.581-20.529,22.338-42.969,28.755-66.783H8.819 C15.236,346.596,24.993,369.036,37.574,389.565z"
/>
</g>
<path
fill="#0052B4"
d="M118.584,39.978h23.329l-21.7,15.765l8.289,25.509l-21.699-15.765L85.104,81.252l7.16-22.037 C73.158,75.13,56.412,93.776,42.612,114.552h7.475l-13.813,10.035c-2.152,3.59-4.216,7.237-6.194,10.938l6.596,20.301l-12.306-8.941 c-3.059,6.481-5.857,13.108-8.372,19.873l7.267,22.368h26.822l-21.7,15.765l8.289,25.509l-21.699-15.765l-12.998,9.444 C0.678,234.537,0,245.189,0,256h256c0-141.384,0-158.052,0-256C205.428,0,158.285,14.67,118.584,39.978z M128.502,230.4 l-21.699-15.765L85.104,230.4l8.289-25.509l-21.7-15.765h26.822l8.288-25.509l8.288,25.509h26.822l-21.7,15.765L128.502,230.4z M120.213,130.317l8.289,25.509l-21.699-15.765l-21.699,15.765l8.289-25.509l-21.7-15.765h26.822l8.288-25.509l8.288,25.509h26.822 L120.213,130.317z M220.328,230.4l-21.699-15.765L176.93,230.4l8.289-25.509l-21.7-15.765h26.822l8.288-25.509l8.288,25.509h26.822 l-21.7,15.765L220.328,230.4z M212.039,130.317l8.289,25.509l-21.699-15.765l-21.699,15.765l8.289-25.509l-21.7-15.765h26.822 l8.288-25.509l8.288,25.509h26.822L212.039,130.317z M212.039,55.743l8.289,25.509l-21.699-15.765L176.93,81.252l8.289-25.509 l-21.7-15.765h26.822l8.288-25.509l8.288,25.509h26.822L212.039,55.743z"
/>
</svg>
);
};
@@ -1,112 +0,0 @@
import styled from "@emotion/styled";
import { useMemo, useState } from "react";
import type { ThirdwebClient } from "../../../../../../client/client.js";
import { isAddress } from "../../../../../../utils/address.js";
import type {
Account,
Wallet,
} from "../../../../../../wallets/interfaces/wallet.js";
import { Spacer } from "../../../components/Spacer.js";
import { Container } from "../../../components/basic.js";
import { Button } from "../../../components/buttons.js";
import { Input } from "../../../components/formElements.js";
import { Text } from "../../../components/text.js";
import { useCustomTheme } from "../../../design-system/CustomThemeProvider.js";
import { AccountSelectorButton } from "./AccountSelectorButton.js";
/**
* @internal
*/
export function AccountSelectionScreen(props: {
activeWallet: Wallet;
activeAccount: Account;
onSelect: (address: string) => void;
client: ThirdwebClient;
}) {
const [address, setAddress] = useState<string>("");
const isValidAddress = useMemo(() => isAddress(address), [address]);
const showError = !!address && !isValidAddress;
return (
<div>
<Text size="lg" color="primaryText">
Send to
</Text>
<Spacer y="lg" />
<Container
flex="row"
center="y"
style={{
flexWrap: "nowrap",
height: "50px",
}}
>
<StyledInput
data-is-error={showError}
value={address}
placeholder="Enter wallet address"
variant="outline"
onChange={(e) => setAddress(e.target.value)}
/>
<Button
variant="accent"
disabled={!isValidAddress}
style={{
height: "100%",
minWidth: "100px",
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
}}
onClick={() => {
props.onSelect(address);
}}
>
Confirm
</Button>
</Container>
{showError && (
<>
<Spacer y="xxs" />
<Text color="danger" size="sm">
Invalid address
</Text>
</>
)}
<Spacer y="xl" />
<Text size="sm">Connected</Text>
<Spacer y="xs" />
<AccountSelectorButton
client={props.client}
address={props.activeAccount.address}
activeAccount={props.activeAccount}
activeWallet={props.activeWallet}
onClick={() => {
props.onSelect(props.activeAccount.address);
}}
/>
</div>
);
}
const StyledInput = /* @__PURE__ */ styled(Input)(() => {
const theme = useCustomTheme();
return {
border: `1.5px solid ${theme.colors.borderColor}`,
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
height: "100%",
boxSizing: "border-box",
boxShadow: "none",
borderRight: "none",
"&:focus": {
boxShadow: "none",
borderColor: theme.colors.accentText,
},
"&[data-is-error='true']": {
boxShadow: "none",
borderColor: theme.colors.danger,
},
};
});
@@ -1,55 +0,0 @@
import { ChevronDownIcon } from "@radix-ui/react-icons";
import type { ThirdwebClient } from "../../../../../../client/client.js";
import type {
Account,
Wallet,
} from "../../../../../../wallets/interfaces/wallet.js";
import { shortenString } from "../../../../../core/utils/addresses.js";
import { WalletImage } from "../../../components/WalletImage.js";
import { Container } from "../../../components/basic.js";
import { Text } from "../../../components/text.js";
import { iconSize } from "../../../design-system/index.js";
import { WalletIcon } from "../../icons/WalletIcon.js";
import { SecondaryButton } from "./buttons.js";
/**
*
* @internal
*/
export function AccountSelectorButton(props: {
onClick: () => void;
activeWallet: Wallet;
activeAccount: Account;
address: string;
chevron?: boolean;
client: ThirdwebClient;
}) {
return (
<SecondaryButton variant="secondary" fullWidth onClick={props.onClick}>
{props.activeAccount.address === props.address ? (
<WalletImage
id={props.activeWallet.id}
size={iconSize.md}
client={props.client}
/>
) : (
<Container color="secondaryText" flex="row" center="both">
<WalletIcon size={iconSize.md} />
</Container>
)}
<Text size="sm" color="primaryText">
{shortenString(props.address, false)}
</Text>
{props.chevron && (
<ChevronDownIcon
width={iconSize.sm}
height={iconSize.sm}
style={{
marginLeft: "auto",
}}
/>
)}
</SecondaryButton>
);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
import { ClockIcon } from "@radix-ui/react-icons";
import { Skeleton } from "../../../components/Skeleton.js";
import { Container } from "../../../components/basic.js";
import { Button } from "../../../components/buttons.js";
import { Text } from "../../../components/text.js";
import { fontSize, iconSize, radius } from "../../../design-system/index.js";
import type { IconFC } from "../../icons/types.js";
import { formatSeconds } from "./swap/formatSeconds.js";
export function EstimatedTimeAndFees(props: {
estimatedSeconds?: number | undefined;
quoteIsLoading: boolean;
onViewFees: () => void;
}) {
const { estimatedSeconds, quoteIsLoading } = props;
return (
<Container
bg="tertiaryBg"
flex="row"
borderColor="borderColor"
style={{
borderRadius: radius.md,
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
justifyContent: "space-between",
alignItems: "center",
borderWidth: "1px",
borderStyle: "solid",
}}
>
<Container flex="row" center="y" gap="xxs" color="accentText" p="sm">
<ClockIcon width={iconSize.sm} height={iconSize.sm} />
{quoteIsLoading ? (
<Skeleton height={fontSize.xs} width="50px" color="borderColor" />
) : (
<Text size="xs" color="secondaryText">
{estimatedSeconds !== undefined
? `~${formatSeconds(estimatedSeconds)}`
: "--"}
</Text>
)}
</Container>
<Button variant="ghost" onClick={props.onViewFees} gap="xs">
<Container color="accentText" flex="row" center="both">
<ViewFeeIcon size={iconSize.sm} />
</Container>
<Text size="xs" color="secondaryText">
View Fees
</Text>
</Button>
</Container>
);
}
const ViewFeeIcon: IconFC = (props) => {
return (
<svg
width={props.size}
height={props.size}
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M9.5 1.5H2.5C1.94772 1.5 1.5 1.94772 1.5 2.5V9.5C1.5 10.0523 1.94772 10.5 2.5 10.5H9.5C10.0523 10.5 10.5 10.0523 10.5 9.5V2.5C10.5 1.94772 10.0523 1.5 9.5 1.5Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.5 7.5L7.5 4.5"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
@@ -0,0 +1,13 @@
import { Suspense, lazy } from "react";
import { LoadingScreen } from "../../../../wallets/shared/LoadingScreen.js";
import type { BuyScreenProps } from "./BuyScreen.js";
const BuyScreen = lazy(() => import("./BuyScreen.js"));
export function LazyBuyScreen(props: BuyScreenProps) {
return (
<Suspense fallback={<LoadingScreen />}>
<BuyScreen {...props} />
</Suspense>
);
}
@@ -0,0 +1,103 @@
import styled from "@emotion/styled";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import type { ThirdwebClient } from "../../../../../../client/client.js";
import { formatNumber } from "../../../../../../utils/formatNumber.js";
import { Skeleton } from "../../../components/Skeleton.js";
import { Container } from "../../../components/basic.js";
import { Button } from "../../../components/buttons.js";
import { Text } from "../../../components/text.js";
import {
fontSize,
iconSize,
radius,
spacing,
} from "../../../design-system/index.js";
import type { CurrencyMeta } from "./fiat/currencies.js";
/**
* Shows an amount "value" and renders the selected token and chain
* It also renders the buttons to select the token and chain
* It also renders the balance of active wallet for the selected token in selected chain
* @internal
*/
export function PayWithCreditCard(props: {
value?: string;
isLoading: boolean;
client: ThirdwebClient;
currency: CurrencyMeta;
onSelectCurrency: () => void;
disableCurrencySelection?: boolean;
}) {
return (
<Container
bg="tertiaryBg"
borderColor="borderColor"
flex="row"
style={{
borderRadius: radius.md,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
borderWidth: "1px",
borderStyle: "solid",
borderBottom: "none",
flexWrap: "nowrap",
justifyContent: "space-between",
alignItems: "center",
}}
>
{/* Left */}
<CurrencyButton
disabled={props.disableCurrencySelection}
variant="ghost"
onClick={props.onSelectCurrency}
style={{
minHeight: "64px",
justifyContent: "flex-start",
minWidth: "50%",
}}
gap="sm"
>
<props.currency.icon size={iconSize.md} />
<Container flex="row" center="y" gap="xxs">
<Text color="primaryText">{props.currency.shorthand}</Text>
{!props.disableCurrencySelection && (
<ChevronDownIcon width={iconSize.sm} height={iconSize.sm} />
)}
</Container>
</CurrencyButton>
{/* Right */}
<div
style={{
flexGrow: 1,
flexShrink: 1,
display: "flex",
flexDirection: "column",
alignItems: "flex-end",
gap: spacing.xxs,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
justifyContent: "center",
paddingRight: spacing.sm,
}}
>
{props.isLoading ? (
<Skeleton width="100px" height={fontSize.lg} />
) : (
<Text size="lg" color={props.value ? "primaryText" : "secondaryText"}>
{props.value ? `${formatNumber(Number(props.value), 4)}` : "--"}
</Text>
)}
</div>
</Container>
);
}
const CurrencyButton = /* @__PURE__ */ styled(Button)(() => {
return {
"&[disabled]:hover": {
borderColor: "transparent",
},
};
});
@@ -4,13 +4,15 @@ import { Container } from "../../../components/basic.js";
import { Button } from "../../../components/buttons.js";
import { Text } from "../../../components/text.js";
import { useCustomTheme } from "../../../design-system/CustomThemeProvider.js";
import { StyledDiv } from "../../../design-system/elements.js";
import { fontSize, radius, spacing } from "../../../design-system/index.js";
import { fontSize, spacing } from "../../../design-system/index.js";
/**
* @internal
*/
export function PaymentSelection() {
export function PaymentSelection(props: {
selected: "crypto" | "creditCard";
onSelect: (method: "crypto" | "creditCard") => void;
}) {
return (
<div>
<Text size="sm">Pay with </Text>
@@ -22,28 +24,25 @@ export function PaymentSelection() {
gridGap: spacing.sm,
}}
>
<CheckButton isChecked={true} variant="outline">
<Container gap="xxs" flex="row" center="y">
Crypto
</Container>
</CheckButton>
<div
style={{
position: "relative",
<CheckButton
isChecked={props.selected === "creditCard"}
variant="outline"
onClick={() => {
props.onSelect("creditCard");
}}
>
<CheckButton
variant="outline"
isChecked={false}
style={{
opacity: 0.5,
}}
disabled
>
Credit Card
</CheckButton>
<FloatingBadge> Coming Soon </FloatingBadge>
</div>
Credit Card
</CheckButton>
<CheckButton
isChecked={props.selected === "crypto"}
variant="outline"
onClick={() => {
props.onSelect("crypto");
}}
>
Crypto
</CheckButton>
</Container>
</div>
);
@@ -59,6 +58,11 @@ const CheckButton = /* @__PURE__ */ styled(Button)(
borderColor: props.isChecked
? theme.colors.accentText
: theme.colors.borderColor,
"&:hover": {
borderColor: props.isChecked
? theme.colors.accentText
: theme.colors.secondaryText,
},
gap: spacing.xs,
paddingInline: spacing.xxs,
paddingBlock: spacing.sm,
@@ -66,19 +70,3 @@ const CheckButton = /* @__PURE__ */ styled(Button)(
};
},
);
const FloatingBadge = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
position: "absolute",
top: 0,
right: 0,
transform: "translate(10%, -60%)",
backgroundColor: theme.colors.secondaryButtonBg,
paddingBlock: "3px",
paddingInline: spacing.xs,
fontSize: fontSize.xs,
borderRadius: radius.sm,
color: theme.colors.accentText,
};
});
@@ -0,0 +1,94 @@
import { keyframes } from "@emotion/react";
import { CheckIcon } from "@radix-ui/react-icons";
import { Container } from "../../../components/basic.js";
import { StyledDiv } from "../../../design-system/elements.js";
import { fontSize, iconSize } from "../../../design-system/index.js";
export function StepIcon(props: {
isDone: boolean;
isActive: boolean;
}) {
return (
<Container
flex="row"
center="both"
color={
props.isDone
? "success"
: props.isActive
? "accentText"
: "secondaryText"
}
>
<Circle>
{props.isDone ? (
<CheckIcon width={iconSize.sm} height={iconSize.sm} />
) : (
<PulsingDot data-active={props.isActive} />
)}
</Circle>
</Container>
);
}
export function Step(props: {
isDone: boolean;
label: string;
isActive: boolean;
}) {
return (
<Container
flex="row"
center="y"
gap="xs"
style={{
fontSize: fontSize.sm,
}}
color={
props.isDone
? "success"
: props.isActive
? "accentText"
: "secondaryText"
}
>
<StepIcon isDone={props.isDone} isActive={props.isActive} />
{props.label}
</Container>
);
}
const pulseAnimation = keyframes`
0% {
opacity: 1;
transform: scale(0.5);
}
100% {
opacity: 0;
transform: scale(1.5);
}
`;
const PulsingDot = /* @__PURE__ */ StyledDiv(() => {
return {
background: "currentColor",
width: "9px",
height: "9px",
borderRadius: "50%",
'&[data-active="true"]': {
animation: `${pulseAnimation} 1s infinite`,
},
};
});
const Circle = /* @__PURE__ */ StyledDiv(() => {
return {
border: "1px solid currentColor",
width: "20px",
height: "20px",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
};
});
@@ -1,800 +0,0 @@
import { ClockIcon, CrossCircledIcon } from "@radix-ui/react-icons";
import { useEffect, useMemo, useState } from "react";
import { polygon } from "../../../../../../chains/chain-definitions/polygon.js";
import type { Chain } from "../../../../../../chains/types.js";
import type { ThirdwebClient } from "../../../../../../client/client.js";
import { NATIVE_TOKEN_ADDRESS } from "../../../../../../constants/addresses.js";
import type { BuyWithCryptoQuote } from "../../../../../../pay/buyWithCrypto/actions/getQuote.js";
import type { PreparedTransaction } from "../../../../../../transaction/prepare-transaction.js";
import { formatNumber } from "../../../../../../utils/formatNumber.js";
import { toEther } from "../../../../../../utils/units.js";
import type {
Account,
Wallet,
} from "../../../../../../wallets/interfaces/wallet.js";
import { getTotalTxCostForBuy } from "../../../../../core/hooks/contract/useSendTransaction.js";
import {
useChainQuery,
useChainsQuery,
} from "../../../../../core/hooks/others/useChainQuery.js";
import { useWalletBalance } from "../../../../../core/hooks/others/useWalletBalance.js";
import {
type BuyWithCryptoQuoteQueryParams,
useBuyWithCryptoQuote,
} from "../../../../../core/hooks/pay/useBuyWithCryptoQuote.js";
import {
useActiveAccount,
useActiveWallet,
useActiveWalletChain,
useSwitchActiveWalletChain,
} from "../../../../../core/hooks/wallets/wallet-hooks.js";
import { wait } from "../../../../../core/utils/wait.js";
import { LoadingScreen } from "../../../../wallets/shared/LoadingScreen.js";
import {
Drawer,
DrawerOverlay,
useDrawer,
} from "../../../components/Drawer.js";
import { DynamicHeight } from "../../../components/DynamicHeight.js";
import { Skeleton } from "../../../components/Skeleton.js";
import { Spacer } from "../../../components/Spacer.js";
import { Spinner } from "../../../components/Spinner.js";
import { TokenIcon } from "../../../components/TokenIcon.js";
import { Container, Line, ModalHeader } from "../../../components/basic.js";
import { Button } from "../../../components/buttons.js";
import { Text } from "../../../components/text.js";
import { fontSize, iconSize, radius } from "../../../design-system/index.js";
import { useDebouncedValue } from "../../../hooks/useDebouncedValue.js";
import type { SupportedTokens } from "../../defaultTokens.js";
import type { IconFC } from "../../icons/types.js";
import type { ConnectLocale } from "../../locale/types.js";
import { TokenSelector } from "../TokenSelector.js";
import {
type ERC20OrNativeToken,
NATIVE_TOKEN,
isNativeToken,
} from "../nativeToken.js";
import { AccountSelectionScreen } from "./AccountSelectionScreen.js";
import { AccountSelectorButton } from "./AccountSelectorButton.js";
import { PaymentSelection } from "./PaymentSelection.js";
import { FeesButton } from "./buttons.js";
import { BuyTokenInput } from "./swap/BuyTokenInput.js";
import { SwapConfirmationScreen } from "./swap/ConfirmationScreen.js";
import { SwapFees } from "./swap/Fees.js";
import { PayWithCrypto } from "./swap/PayWithCrypto.js";
import { formatSeconds } from "./swap/formatSeconds.js";
import { useSwapSupportedChains } from "./swap/useSwapSupportedChains.js";
// NOTE: Must not use useConnectUI here because this UI can be used outside connect ui
type BuyForTx = {
cost: bigint;
balance: bigint;
tx: PreparedTransaction;
tokenSymbol: string;
};
/**
* @internal
*/
export function BuyScreen(props: {
onBack: () => void;
supportedTokens: SupportedTokens;
onViewPendingTx: () => void;
client: ThirdwebClient;
connectLocale: ConnectLocale;
buyForTx?: BuyForTx;
}) {
const activeChain = useActiveWalletChain();
const activeWallet = useActiveWallet();
const account = useActiveAccount();
const supportedChainsQuery = useSwapSupportedChains(props.client);
if (!activeChain || !account || !activeWallet || !supportedChainsQuery.data) {
return <LoadingScreen />;
}
return (
<BuyScreenContent
{...props}
activeChain={activeChain}
activeWallet={activeWallet}
account={account}
onViewPendingTx={props.onViewPendingTx}
supportedChains={supportedChainsQuery.data}
buyForTx={props.buyForTx}
/>
);
}
type Screen =
| "main"
| "select-from-token"
| "select-to-token"
| "confirmation"
| "usd-confirmation"
| "kado-iframe";
type DrawerScreen = "fees" | "address" | undefined;
/**
*
* @internal
*/
export function BuyScreenContent(props: {
client: ThirdwebClient;
onBack: () => void;
supportedTokens: SupportedTokens;
activeChain: Chain;
activeWallet: Wallet;
account: Account;
onViewPendingTx: () => void;
supportedChains: Chain[];
connectLocale: ConnectLocale;
buyForTx?: BuyForTx;
}) {
const {
activeChain,
account,
client,
activeWallet,
supportedChains,
connectLocale,
} = props;
const [isSwitching, setIsSwitching] = useState(false);
const switchActiveWalletChain = useSwitchActiveWalletChain();
const [method] = useState<"crypto" | "creditCard">("crypto");
// prefetch chains metadata
useChainsQuery(supportedChains || [], 50);
// screens
const [screen, setScreen] = useState<Screen>("main");
const [drawerScreen, setDrawerScreen] = useState<DrawerScreen>();
const { drawerRef, drawerOverlayRef, onClose } = useDrawer();
const closeDrawer = () => {
onClose(() => {
setDrawerScreen(undefined);
});
};
const initialTokenAmount = props.buyForTx
? formatNumber(
Number(toEther(props.buyForTx.cost - props.buyForTx.balance)),
4,
)
: undefined;
// token amount
const [tokenAmount, setTokenAmount] = useState<string>(
initialTokenAmount ? String(initialTokenAmount) : "",
);
// once the user edits the tokenInput or confirms the Buy - stop updating the token amount
const [stopUpdatingTokenAmount, setStopUpdatingTokenAmount] = useState(
!props.buyForTx,
);
const [amountNeeded, setAmountNeeded] = useState<bigint | undefined>(
props.buyForTx?.cost,
);
// update amount needed every 30 seconds
// also update the token amount if allowed
// ( Can't use useQuery because tx can't be added to queryKey )
useEffect(() => {
const buyTx = props.buyForTx;
if (!buyTx || stopUpdatingTokenAmount) {
return;
}
let mounted = true;
async function pollTxCost() {
if (!buyTx || !mounted) {
return;
}
try {
const totalCost = await getTotalTxCostForBuy(buyTx.tx);
if (!mounted) {
return;
}
setAmountNeeded(totalCost);
if (totalCost > buyTx.balance) {
const _tokenAmount = String(
formatNumber(Number(toEther(totalCost - buyTx.balance)), 4),
);
setTokenAmount(_tokenAmount);
}
} catch {
// no op
}
await wait(30000);
pollTxCost();
}
pollTxCost();
return () => {
mounted = false;
};
}, [props.buyForTx, stopUpdatingTokenAmount]);
const [hasEditedAmount, setHasEditedAmount] = useState(false);
const isMiniScreen = props.buyForTx ? false : !hasEditedAmount;
const isChainSupported = useMemo(
() => supportedChains?.find((c) => c.id === activeChain.id),
[activeChain.id, supportedChains],
);
// selected chain
const defaultChain = isChainSupported ? activeChain : polygon;
const [fromChain, setFromChain] = useState<Chain>(
props.buyForTx ? props.buyForTx.tx.chain : defaultChain,
);
const [toChain, setToChain] = useState<Chain>(
props.buyForTx ? props.buyForTx.tx.chain : defaultChain,
);
const [address, setAddress] = useState<string>(account.address);
// selected tokens
const [fromToken, setFromToken] = useState<ERC20OrNativeToken>(
(props.buyForTx ? props.supportedTokens[toChain.id]?.[0] : undefined) ||
NATIVE_TOKEN,
);
const [toToken, setToToken] = useState<ERC20OrNativeToken>(
props.buyForTx
? NATIVE_TOKEN
: props.supportedTokens[toChain.id]?.[0] || NATIVE_TOKEN,
);
const deferredTokenAmount = useDebouncedValue(tokenAmount, 300);
const fromTokenBalanceQuery = useWalletBalance({
address: account.address,
chain: fromChain,
tokenAddress: isNativeToken(fromToken) ? undefined : fromToken.address,
client,
});
// when a quote is finalized ( approve sent if required or swap sent )
// we save it here to stop refetching the quote query
const [finalizedQuote, setFinalizedQuote] = useState<
BuyWithCryptoQuote | undefined
>();
const buyWithCryptoParams: BuyWithCryptoQuoteQueryParams | undefined =
deferredTokenAmount &&
!finalizedQuote &&
!(fromChain.id === toChain.id && fromToken === toToken)
? {
// wallet
fromAddress: address,
// from token
fromChainId: fromChain.id,
fromTokenAddress: isNativeToken(fromToken)
? NATIVE_TOKEN_ADDRESS
: fromToken.address,
toChainId: toChain.id,
// to
toTokenAddress: isNativeToken(toToken)
? NATIVE_TOKEN_ADDRESS
: toToken.address,
toAmount: deferredTokenAmount,
client,
}
: undefined;
const buyWithCryptoQuoteQuery = useBuyWithCryptoQuote(buyWithCryptoParams, {
// refetch every 30 seconds
staleTime: 30 * 1000,
refetchInterval: 30 * 1000,
gcTime: 30 * 1000,
});
if (screen === "select-from-token") {
return (
<TokenSelector
onBack={() => setScreen("main")}
tokenList={
(fromChain?.id ? props.supportedTokens[fromChain.id] : undefined) ||
[]
}
onTokenSelect={(tokenInfo) => {
setFromToken(tokenInfo);
setScreen("main");
}}
chain={fromChain}
chainSelection={{
chains: supportedChains,
select: (c) => setFromChain(c),
}}
connectLocale={connectLocale}
client={client}
/>
);
}
if (screen === "select-to-token") {
return (
<TokenSelector
onBack={() => setScreen("main")}
tokenList={
(toChain?.id ? props.supportedTokens[toChain.id] : undefined) || []
}
onTokenSelect={(tokenInfo) => {
setToToken(tokenInfo);
setScreen("main");
}}
chain={toChain}
chainSelection={{
chains: supportedChains,
select: (c) => setToChain(c),
}}
connectLocale={connectLocale}
client={client}
/>
);
}
const swapQuote = buyWithCryptoQuoteQuery.data;
const isSwapQuoteError = buyWithCryptoQuoteQuery.isError;
const getErrorMessage = () => {
const defaultMessage = "Unable to get price quote";
try {
if (buyWithCryptoQuoteQuery.error instanceof Error) {
if (buyWithCryptoQuoteQuery.error.message.includes("Minimum")) {
const msg = buyWithCryptoQuoteQuery.error.message;
return msg.replace("Fetch failed: Error: ", "");
}
}
return defaultMessage;
} catch {
return defaultMessage;
}
};
const sourceTokenAmount = swapQuote?.swapDetails.fromAmount || "";
const quoteToConfirm = finalizedQuote || buyWithCryptoQuoteQuery.data;
if (screen === "confirmation" && quoteToConfirm) {
return (
<SwapConfirmationScreen
client={client}
onBack={() => {
// remove finalized quote when going back
setFinalizedQuote(undefined);
setStopUpdatingTokenAmount(true);
setScreen("main");
}}
buyWithCryptoQuote={quoteToConfirm}
onQuoteFinalized={(_quote) => {
setFinalizedQuote(_quote);
}}
fromAmount={quoteToConfirm.swapDetails.fromAmount}
toAmount={tokenAmount}
fromChain={fromChain}
toChain={toChain}
account={account}
fromToken={fromToken}
toToken={toToken}
onViewPendingTx={props.onViewPendingTx}
/>
);
}
const isNotEnoughBalance =
!!sourceTokenAmount &&
!!fromTokenBalanceQuery.data &&
Number(fromTokenBalanceQuery.data.displayValue) < Number(sourceTokenAmount);
const disableContinue = !swapQuote || isNotEnoughBalance;
const switchChainRequired = props.activeChain.id !== fromChain.id;
return (
<Container animate="fadein">
{/* biome-ignore lint/a11y/useKeyWithClickEvents: <explanation> */}
<div
onClick={(e) => {
if (
drawerScreen &&
drawerRef.current &&
!drawerRef.current.contains(e.target as Node)
) {
e.preventDefault();
e.stopPropagation();
closeDrawer();
}
}}
>
{/* Drawer */}
{drawerScreen && (
<>
<DrawerOverlay ref={drawerOverlayRef} />
<Drawer ref={drawerRef} close={closeDrawer}>
<DynamicHeight>
{drawerScreen === "address" && (
<AccountSelectionScreen
onSelect={(v) => {
setAddress(v);
closeDrawer();
}}
activeAccount={account}
activeWallet={props.activeWallet}
client={client}
/>
)}
{method === "crypto" && (
<>
{drawerScreen === "fees" && (
<div>
<Text size="lg" color="primaryText">
Fees
</Text>
<Spacer y="lg" />
{swapQuote && (
<SwapFees quote={swapQuote} align="left" />
)}
</div>
)}
</>
)}
</DynamicHeight>
</Drawer>
</>
)}
<Container
p="lg"
style={{
paddingBottom: 0,
}}
>
<ModalHeader
title={
props.buyForTx
? `Not enough ${props.buyForTx.tokenSymbol}`
: "Buy"
}
onBack={props.onBack}
/>
<Spacer y="lg" />
{isMiniScreen && <Spacer y="xl" />}
{amountNeeded && props.buyForTx ? (
<BuyForTxUI
amountNeeded={String(
formatNumber(Number(toEther(amountNeeded)), 4),
)}
buyForTx={props.buyForTx}
client={client}
/>
) : null}
{/* To */}
<BuyTokenInput
value={tokenAmount}
onChange={async (value) => {
setHasEditedAmount(true);
setStopUpdatingTokenAmount(true);
setTokenAmount(value);
}}
token={toToken}
chain={toChain}
onSelectToken={() => setScreen("select-to-token")}
client={props.client}
hideTokenSelector={!!props.buyForTx}
/>
</Container>
<Spacer y="md" />
<Container px="lg">
{!isMiniScreen && (
<div>
<PaymentSelection />
<Spacer y="md" />
{method === "crypto" && (
<>
<PayWithCrypto
value={sourceTokenAmount}
onSelectToken={() => {
setScreen("select-from-token");
}}
chain={fromChain}
token={fromToken}
isLoading={
buyWithCryptoQuoteQuery.isLoading && !sourceTokenAmount
}
client={client}
/>
<SecondaryInfo
quoteIsLoading={buyWithCryptoQuoteQuery.isLoading}
estimatedSeconds={
buyWithCryptoQuoteQuery.data?.swapDetails.estimated
.durationSeconds
}
onViewFees={() => setDrawerScreen("fees")}
/>
</>
)}
<Spacer y="md" />
{/* Send To */}
{!props.buyForTx && (
<>
<Container>
<Text size="sm">Send To</Text>
<Spacer y="xxs" />
<AccountSelectorButton
address={address}
activeAccount={account}
activeWallet={activeWallet}
onClick={() => {
setDrawerScreen("address");
}}
chevron
client={client}
/>
</Container>
<Spacer y="md" />
</>
)}
<Container flex="column" gap="md">
{method === "crypto" && isSwapQuoteError && (
<div>
<Container
flex="row"
gap="xxs"
center="both"
color="danger"
>
<CrossCircledIcon
width={iconSize.sm}
height={iconSize.sm}
/>
<Text color="danger" size="sm">
{getErrorMessage()}
</Text>
</Container>
<Spacer y="md" />
</div>
)}
</Container>
</div>
)}
{method === "crypto" && (
<>
{switchChainRequired && (
<Button
fullWidth
variant="accent"
disabled={isMiniScreen}
data-disabled={isMiniScreen}
gap="sm"
onClick={async () => {
setIsSwitching(true);
try {
await switchActiveWalletChain(fromChain);
} catch {}
setIsSwitching(false);
}}
>
{!isMiniScreen ? (
<>
{isSwitching && (
<Spinner size="sm" color="accentButtonText" />
)}
{isSwitching ? "Switching" : "Switch Network"}
</>
) : (
"Continue"
)}
</Button>
)}
{!switchChainRequired && (
<Button
variant={disableContinue ? "outline" : "accent"}
fullWidth
data-disabled={disableContinue}
disabled={disableContinue}
onClick={async () => {
if (!disableContinue) {
setScreen("confirmation");
}
}}
gap="sm"
>
{isNotEnoughBalance ? "Not Enough Funds" : "Continue"}
</Button>
)}
</>
)}
{method === "creditCard" && (
<Button
variant="accent"
fullWidth
onClick={async () => {
setScreen("kado-iframe");
}}
gap="sm"
>
Continue
</Button>
)}
</Container>
<Spacer y="lg" />
</div>
</Container>
);
}
const ViewFeeIcon: IconFC = (props) => {
return (
<svg
width={props.size}
height={props.size}
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M9.5 1.5H2.5C1.94772 1.5 1.5 1.94772 1.5 2.5V9.5C1.5 10.0523 1.94772 10.5 2.5 10.5H9.5C10.0523 10.5 10.5 10.0523 10.5 9.5V2.5C10.5 1.94772 10.0523 1.5 9.5 1.5Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.5 7.5L7.5 4.5"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
function SecondaryInfo(props: {
estimatedSeconds?: number | undefined;
quoteIsLoading: boolean;
onViewFees: () => void;
}) {
const { estimatedSeconds, quoteIsLoading } = props;
return (
<Container
bg="tertiaryBg"
flex="row"
borderColor="borderColor"
style={{
borderRadius: radius.md,
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
justifyContent: "space-between",
alignItems: "center",
borderWidth: "1px",
borderStyle: "solid",
}}
>
<Container flex="row" center="y" gap="xxs" color="accentText" p="sm">
<ClockIcon width={iconSize.sm} height={iconSize.sm} />
{quoteIsLoading ? (
<Skeleton height={fontSize.xs} width="50px" color="borderColor" />
) : (
<Text size="xs" color="secondaryText">
{estimatedSeconds !== undefined
? `~${formatSeconds(estimatedSeconds)}`
: "--"}
</Text>
)}
</Container>
<FeesButton variant="secondary" onClick={props.onViewFees}>
<Container color="accentText" flex="row" center="both">
<ViewFeeIcon size={iconSize.sm} />
</Container>
<Text size="xs" color="secondaryText">
View Fees
</Text>
</FeesButton>
</Container>
);
}
function BuyForTxUI(props: {
amountNeeded: string;
buyForTx: BuyForTx;
client: ThirdwebClient;
}) {
const chainQuery = useChainQuery(props.buyForTx.tx.chain);
return (
<Container>
<Spacer y="xs" />
<Container
flex="row"
style={{
justifyContent: "space-between",
}}
>
<Text size="sm">Amount Needed</Text>
<Container
flex="column"
style={{
alignItems: "flex-end",
}}
>
<Container flex="row" gap="xs" center="y">
<Text color="primaryText" size="sm">
{props.amountNeeded} {props.buyForTx.tokenSymbol}
</Text>
<TokenIcon
chain={props.buyForTx.tx.chain}
client={props.client}
size="sm"
token={NATIVE_TOKEN}
/>
</Container>
<Spacer y="xxs" />
{chainQuery.data ? (
<Text size="sm"> {chainQuery.data.name}</Text>
) : (
<Skeleton height={fontSize.sm} width="50px" />
)}
</Container>
</Container>
<Spacer y="md" />
<Line />
<Spacer y="md" />
<Container
flex="row"
style={{
justifyContent: "space-between",
}}
>
<Text size="sm">Your Balance</Text>
<Container flex="row" gap="xs">
<Text color="primaryText" size="sm">
{formatNumber(Number(toEther(props.buyForTx.balance)), 4)}{" "}
{props.buyForTx.tokenSymbol}
</Text>
<TokenIcon
chain={props.buyForTx.tx.chain}
client={props.client}
size="sm"
token={NATIVE_TOKEN}
/>
</Container>
</Container>
<Spacer y="md" />
<Line />
<Spacer y="lg" />
<Text center size="sm">
Purchase
</Text>
<Spacer y="xxs" />
</Container>
);
}

Some files were not shown because too many files have changed in this diff Show More