Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3871068aa6 | ||
|
|
d00ca7fd2a | ||
|
|
9ee3c21eac | ||
|
|
ed3afd418c | ||
|
|
b90fa2cc29 | ||
|
|
2ab517aa49 | ||
|
|
e46dd0254f | ||
|
|
1a5627f72d | ||
|
|
b808e942fd | ||
|
|
7fdb1715e4 |
@@ -1,5 +1,12 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 4.1.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e46dd025`](https://github.com/thirdweb-dev/js/commit/e46dd0254f157c3c4d50fe3e4f44e2e24eaf34a8)]:
|
||||
- @thirdweb-dev/wallets@2.3.3
|
||||
|
||||
## 4.1.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "4.1.19",
|
||||
"version": "4.1.20",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"browser": {
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.13.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b808e942`](https://github.com/thirdweb-dev/js/commit/b808e942fdaa81b5de61dcb3f2083081cb99ea11), [`e46dd025`](https://github.com/thirdweb-dev/js/commit/e46dd0254f157c3c4d50fe3e4f44e2e24eaf34a8)]:
|
||||
- @thirdweb-dev/sdk@4.0.24
|
||||
- @thirdweb-dev/wallets@2.3.3
|
||||
- @thirdweb-dev/auth@4.1.20
|
||||
|
||||
## 0.13.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.13.27",
|
||||
"version": "0.13.28",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-typescript",
|
||||
["@babel/preset-env", { "targets": "defaults, not ie 11" }]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-flow-strip-types",
|
||||
"@babel/plugin-transform-private-methods",
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# folders
|
||||
dist/
|
||||
|
||||
# files
|
||||
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["thirdweb"],
|
||||
plugins: ["better-tree-shaking"],
|
||||
rules: {
|
||||
"better-tree-shaking/no-top-level-side-effects": "error",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "eventemitter3",
|
||||
importNames: ["EventEmitter"],
|
||||
message:
|
||||
"Do not use named import for importing EventEmitter, Use default import instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
# @thirdweb-dev/payments
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2115](https://github.com/thirdweb-dev/js/pull/2115) [`d00ca7fd`](https://github.com/thirdweb-dev/js/commit/d00ca7fd2a0fa3e694bb37d1f486d3d4cf1ad79e) Thanks [@IDubuque](https://github.com/IDubuque)! - Update exports with new payments interface
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- [#1913](https://github.com/thirdweb-dev/js/pull/1913) [`ed3afd41`](https://github.com/thirdweb-dev/js/commit/ed3afd418cb6e0d711814a1c52afb302460751ba) Thanks [@IDubuque](https://github.com/IDubuque)! - The `@thirdweb-dev/payments` package is a continuation of the `@paperxyz/js-client-sdk`, aligning with the new branding and expanded capabilities under thirdweb. It inherits the core functionality of Paper's developer-first NFT checkout solution.
|
||||
|
||||
### Changed
|
||||
|
||||
- Rebranded package from `@paperxyz/js-client-sdk` to `@thirdweb-dev/payments`. Users of the old package should migrate to this new package name.
|
||||
- Changed PaperSdkError typename to PaymentsSdkError
|
||||
@@ -0,0 +1,14 @@
|
||||
<p align="center">
|
||||
<br />
|
||||
<a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/packages/sdk/logo.svg?raw=true" width="200" alt=""/></a>
|
||||
<br />
|
||||
</p>
|
||||
<h1 align="center">thirdweb Checkouts SDK</h1>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/@thirdweb-dev/payments"><img src="https://img.shields.io/npm/v/@thirdweb-dev/payments?color=red&label=npm&logo=npm" alt="npm version"/></a>
|
||||
<a href="https://github.com/thirdweb-dev/js/actions/workflows/build-test-lint.yml"><img alt="Build Status" src="https://github.com/thirdweb-dev/js/actions/workflows/build-test-lint.yml/badge.svg"/></a>
|
||||
<a href="https://discord.gg/thirdweb"><img alt="Join our Discord!" src="https://img.shields.io/discord/834227967404146718.svg?color=7289da&label=discord&logo=discord&style=flat"/></a>
|
||||
|
||||
</p>
|
||||
<p align="center"><strong>web3 Checkouts library to integrate any NFT checkout into your applications.</strong></p>
|
||||
<br />
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/payments",
|
||||
"version": "1.0.1",
|
||||
"main": "dist/thirdweb-dev-payments.cjs.js",
|
||||
"module": "dist/thirdweb-dev-payments.esm.js",
|
||||
"types": "dist/thirdweb-dev-payments.cjs.d.ts",
|
||||
"browser": {
|
||||
"./dist/thirdweb-dev-payments.esm.js": "./dist/thirdweb-dev-payments.browser.esm.js"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"module": {
|
||||
"browser": "./dist/thirdweb-dev-payments.browser.esm.js",
|
||||
"default": "./dist/thirdweb-dev-payments.esm.js"
|
||||
},
|
||||
"default": "./dist/thirdweb-dev-payments.cjs.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/payments",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/thirdweb-dev/js/issues"
|
||||
},
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"preconstruct": {
|
||||
"entrypoints": [
|
||||
"index.ts"
|
||||
],
|
||||
"exports": {
|
||||
"envConditions": [
|
||||
"browser"
|
||||
]
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"ethers": "^5.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@thirdweb-dev/tsconfig": "workspace:*",
|
||||
"eslint-config-thirdweb": "workspace:*",
|
||||
"typescript": "^5.1.6",
|
||||
"@preconstruct/cli": "2.7.0"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --write 'src/**/*'",
|
||||
"lint": "eslint src/",
|
||||
"fix": "eslint src/ --fix",
|
||||
"clean": "rm -rf dist/",
|
||||
"build": "tsc && preconstruct build",
|
||||
"preconstruct:fix": "preconstruct fix",
|
||||
"push": "yalc push"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// UNCHANGED: MERGED FROM sdk-common-utilities/blockchain
|
||||
export const ChainToPublicRpc: Record<Chain, string> = {
|
||||
Ethereum: "https://ethereum.rpc.thirdweb.com",
|
||||
Goerli: "https://goerli.rpc.thirdweb.com",
|
||||
Mumbai: "https://mumbai.rpc.thirdweb.com",
|
||||
Polygon: "https://polygon.rpc.thirdweb.com",
|
||||
Avalanche: "https://avalanche.rpc.thirdweb.com",
|
||||
Optimism: "https://optimism.rpc.thirdweb.com",
|
||||
OptimismGoerli: "https://optimism-goerli.rpc.thirdweb.com",
|
||||
BSC: "https://binance.rpc.thirdweb.com",
|
||||
BSCTestnet: "https://binance-testnet.rpc.thirdweb.com",
|
||||
ArbitrumOne: "https://arbitrum.rpc.thirdweb.com",
|
||||
ArbitrumGoerli: "https://arbitrum-goerli.rpc.thirdweb.com",
|
||||
Fantom: "https://fantom.rpc.thirdweb.com",
|
||||
FantomTestnet: "https://fantom-testnet.rpc.thirdweb.com",
|
||||
Sepolia: "https://sepolia.rpc.thirdweb.com",
|
||||
AvalancheFuji: "https://avalanche-fuji.rpc.thirdweb.com",
|
||||
};
|
||||
|
||||
export const ChainIdToChain: Record<number, Chain> = {
|
||||
1: "Ethereum",
|
||||
5: "Goerli",
|
||||
80001: "Mumbai",
|
||||
137: "Polygon",
|
||||
43114: "Avalanche",
|
||||
10: "Optimism",
|
||||
420: "OptimismGoerli",
|
||||
56: "BSC",
|
||||
97: "BSCTestnet",
|
||||
42161: "ArbitrumOne",
|
||||
421613: "ArbitrumGoerli",
|
||||
250: "Fantom",
|
||||
4002: "FantomTestnet",
|
||||
11155111: "Sepolia",
|
||||
43113: "AvalancheFuji",
|
||||
};
|
||||
|
||||
// General Embedded wallet types
|
||||
export type Chain =
|
||||
| "Polygon"
|
||||
| "Mumbai"
|
||||
| "Goerli"
|
||||
| "Ethereum"
|
||||
| "Avalanche"
|
||||
| "Optimism"
|
||||
| "OptimismGoerli"
|
||||
| "BSC"
|
||||
| "BSCTestnet"
|
||||
| "ArbitrumOne"
|
||||
| "ArbitrumGoerli"
|
||||
| "Fantom"
|
||||
| "FantomTestnet"
|
||||
| "Sepolia"
|
||||
| "AvalancheFuji";
|
||||
|
||||
export type SupportedChainName = Chain | "Rinkeby";
|
||||
@@ -0,0 +1,12 @@
|
||||
// UNCHANGED: MERGED FROM sdk-common-utilities/iframeMessages
|
||||
export type MessageType<T> =
|
||||
| {
|
||||
eventType: string;
|
||||
success: true;
|
||||
data: T;
|
||||
}
|
||||
| {
|
||||
eventType: string;
|
||||
success: false;
|
||||
error: string;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
// CHANGED: export merged sdk-common-utilities/constants & js-client-sdk/constants
|
||||
export * from "./settings";
|
||||
export * from "./blockchain";
|
||||
export * from "./style";
|
||||
@@ -0,0 +1,43 @@
|
||||
// UNCHANGED: MERGE BETWEEN sdk-common-utilities/settings and js-client-sdk/settings
|
||||
const isDev = (): boolean => {
|
||||
return !!(
|
||||
typeof window !== "undefined" &&
|
||||
window.localStorage.getItem("IS_PAPER_DEV") === "true"
|
||||
);
|
||||
};
|
||||
|
||||
const isOldPaperDomain = (): boolean =>
|
||||
typeof window !== "undefined" &&
|
||||
(window.location.hostname === "paper.xyz" ||
|
||||
window.location.hostname.endsWith(".paper.xyz"));
|
||||
|
||||
const isThirdwebDomain = (): boolean =>
|
||||
typeof window !== "undefined" &&
|
||||
(window.location.hostname === "thirdweb.com" ||
|
||||
window.location.hostname.endsWith(".thirdweb.com"));
|
||||
|
||||
export const getPaperOriginUrl = (): string => {
|
||||
if (isDev()) {
|
||||
return (
|
||||
window.localStorage.getItem("PAPER_DEV_URL") ?? "http://localhost:3000"
|
||||
);
|
||||
}
|
||||
|
||||
if (isOldPaperDomain()) {
|
||||
return window.location.origin;
|
||||
}
|
||||
|
||||
if (isThirdwebDomain()) {
|
||||
return window.location.origin;
|
||||
}
|
||||
|
||||
return "https://payments.thirdweb.com";
|
||||
};
|
||||
|
||||
// eslint-disable-next-line better-tree-shaking/no-top-level-side-effects
|
||||
export const PAPER_APP_URL = getPaperOriginUrl();
|
||||
|
||||
export const CHECKOUT_WITH_ETH_IFRAME_URL = "/sdk/2022-08-12/checkout-with-eth";
|
||||
export const CHECKOUT_WITH_CARD_IFRAME_URL =
|
||||
"/sdk/2022-08-12/checkout-with-card";
|
||||
export const CREATE_WALLET_IFRAME_URL = "/sdk/v2/verify-email";
|
||||
@@ -0,0 +1,25 @@
|
||||
// // UNCHANGED: MERGED FROM sdk-common-utilities/style
|
||||
export type StyleObject = Partial<CSSStyleDeclaration>;
|
||||
|
||||
export interface ICustomizationOptions {
|
||||
colorPrimary?: string;
|
||||
colorBackground?: string;
|
||||
colorText?: string;
|
||||
borderRadius?: number;
|
||||
fontFamily?: string;
|
||||
|
||||
inputBackgroundColor?: string;
|
||||
inputBorderColor?: string;
|
||||
}
|
||||
export type CustomizationOptionsType = {
|
||||
[key in keyof ICustomizationOptions]: string;
|
||||
};
|
||||
export type Locale = "en" | "fr" | "es" | "it" | "de" | "ja" | "ko" | "zh";
|
||||
|
||||
export const DEFAULT_BRAND_OPTIONS = {
|
||||
colorPrimary: "#cf3781",
|
||||
colorBackground: "#ffffff",
|
||||
colorText: "#1a202c",
|
||||
borderRadius: 12,
|
||||
fontFamily: "Open Sans",
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
// CHANGED: exports
|
||||
|
||||
// Customization options and base urls
|
||||
export * from "./constants";
|
||||
// Types
|
||||
export * from "./interfaces";
|
||||
// Components
|
||||
export * from "./lib";
|
||||
@@ -0,0 +1,67 @@
|
||||
// UNCHANGED
|
||||
|
||||
export interface ReviewResult {
|
||||
/**
|
||||
* A unique ID for this purchase.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The cardholder's full name provided by the buyer.
|
||||
*/
|
||||
cardholderName: string;
|
||||
}
|
||||
|
||||
export interface KycModal {
|
||||
iframeLink: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ICheckoutWithCardConfigs interface defines the configuration options for the checkoutWithCard component provided by Paper.
|
||||
* This component allows users to generate a card payment component for NFTs without needing any backend calls.
|
||||
*
|
||||
* The configuration options include:
|
||||
*
|
||||
* - `contractId`: The id of your contract (not the address) found in you seller dashboard.
|
||||
* - `walletAddress`: The wallet address to which the payment will be sent (AKA the receiving wallet).
|
||||
* - `email`: (Optional) Email address of the buyer.
|
||||
* - `quantity`: (Optional) The number of items to be purchased.
|
||||
* - `metadata`: (Optional) A record of additional information about the transaction.
|
||||
* - `mintMethod`: (Optional, Only required for Custom Contract Types) The contract function name, function args, and payment information which will be sent to your contract.
|
||||
* - `contractArgs`: (Optional) Depending on the contract type, this can defined additional args to call your contract with. See [Thirdweb/Reservoir docs](https://docs.withpaper.com/reference/thirdweb-contracts) for more details.
|
||||
* - `feeBearer`: (Optional) Who will bear the transaction fee, either 'BUYER' or 'SELLER'.
|
||||
* - `capturePaymentLater`: (Optional) If set to true, the payment is authorized but not captured immediately.
|
||||
* - `fiatCurrency`: (Optional) The type of fiat currency for the payment.
|
||||
* - `title`: (Optional) The title to be displayed on the Stripe receipt.
|
||||
* - `sendEmailOnTransferSucceeded`: (Optional) If set to true, an email is sent when the transfer succeeds.
|
||||
* - `postPurchaseMessageMarkdown`: (Optional) A post-purchase message in Markdown format.
|
||||
* - `postPurchaseButtonText`: (Optional) The text for the post-purchase button.
|
||||
* - `successCallbackUrl`: (Optional) The URL to be called back when the transaction is successful.
|
||||
*
|
||||
* Note: This interface is part of an open-source npm package. Please ensure you understand the implications of each field before using this interface.
|
||||
*/
|
||||
export interface ICheckoutWithCardConfigs {
|
||||
contractId: string;
|
||||
walletAddress: string;
|
||||
email?: string;
|
||||
quantity?: number;
|
||||
mintMethod?: {
|
||||
name: string;
|
||||
args: Record<string, any>;
|
||||
payment: { value: string; currency: string };
|
||||
};
|
||||
contractArgs?: Record<string, any>;
|
||||
|
||||
// payment customizations
|
||||
capturePaymentLater?: boolean;
|
||||
fiatCurrency?: string;
|
||||
|
||||
// stripe receipt
|
||||
title?: string;
|
||||
|
||||
// email
|
||||
sendEmailOnTransferSucceeded?: boolean;
|
||||
postPurchaseMessageMarkdown?: string;
|
||||
postPurchaseButtonText?: string;
|
||||
successCallbackUrl?: string;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// UNCHANGED
|
||||
|
||||
import type { StyleObject } from "../constants/style";
|
||||
|
||||
export interface ModalStyles {
|
||||
main: StyleObject;
|
||||
overlay: StyleObject;
|
||||
body: StyleObject;
|
||||
iframe: StyleObject;
|
||||
closeButton?: StyleObject;
|
||||
spinner: StyleObject;
|
||||
}
|
||||
|
||||
export interface ModalInterface {
|
||||
modalContainer?: HTMLElement;
|
||||
modalStyles?: Partial<ModalStyles>;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// UNCHANGED
|
||||
|
||||
export type PaperUser = {
|
||||
/**
|
||||
* The user's email address.
|
||||
* This address is case-insensitive (i.e. different capitalizations map to the same wallet).
|
||||
*/
|
||||
emailAddress: string;
|
||||
|
||||
/**
|
||||
* The Paper Wallet address associated with this user's email address.
|
||||
*/
|
||||
walletAddress: string;
|
||||
|
||||
/**
|
||||
* The user's access code. Will only be set if [clientId] was provided. This can be used to query about the user details.
|
||||
*/
|
||||
accessCode?: string;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// CHANGED: Updated name to Payments
|
||||
|
||||
export type PaymentsSDKError = {
|
||||
/**
|
||||
* An enum representing the error encountered.
|
||||
* The value is a human-readable, English message describing the error.
|
||||
*/
|
||||
code: PaymentsSDKErrorCode | PayWithCryptoErrorCode;
|
||||
error: Error;
|
||||
};
|
||||
|
||||
export enum PaymentsSDKErrorCode {
|
||||
UserAbandonedCheckout = "User abandoned the checkout",
|
||||
UserLoginFailed = "User login failed",
|
||||
InvalidProps = "The props you passed in to this component are not valid.",
|
||||
InvalidCard = "The card information is invalid. Please double check that the Card, CVC, and Zip code are all correct.",
|
||||
EmailNotVerified = "The email was unable to be verified.",
|
||||
NotEnoughSupply = "There is not enough supply to claim.",
|
||||
AddressNotAllowed = "This address is not on the allowlist.",
|
||||
NoActiveClaimPhase = "There is no active claim phase at the moment.",
|
||||
}
|
||||
|
||||
export enum PayWithCryptoErrorCode {
|
||||
ErrorConnectingToWallet = "Error connecting to wallet",
|
||||
ErrorSendingTransaction = "Something went wrong sending transaction",
|
||||
InsufficientBalance = `Insufficient ETH`,
|
||||
TransactionCancelled = "Transaction Cancelled",
|
||||
WrongChain = `Wrong Chain Detected`,
|
||||
ChainSwitchUnderway = "There is a network switch already underway",
|
||||
PendingSignature = "Pending Signature",
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// UNCHANGED
|
||||
|
||||
export interface PriceSummary {
|
||||
quantity: number;
|
||||
unitPrice: PriceDetail;
|
||||
networkFees: PriceDetail;
|
||||
serviceFees: PriceDetail;
|
||||
total: PriceDetail;
|
||||
cryptoToFiatConversionRate?: number;
|
||||
}
|
||||
|
||||
export interface PriceDetail {
|
||||
/**
|
||||
* A human-readable string to display.
|
||||
* Example: $123.45 CAD
|
||||
*/
|
||||
display: string;
|
||||
/**
|
||||
* This is the value in smallest units (cents, yen, wei).
|
||||
* Example: 12345
|
||||
*/
|
||||
valueInSubunits: number;
|
||||
/**
|
||||
* The currency associated with the value.
|
||||
* Example: USD, ETH
|
||||
*/
|
||||
currency: string;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// CHANGED: Added exports
|
||||
export * from "./CheckoutWithCard";
|
||||
export * from "./PaymentsSdkError";
|
||||
export * from "./PaperUser";
|
||||
export * from "./PriceSummary";
|
||||
@@ -0,0 +1,256 @@
|
||||
// CHANGED: Added clientId to interface
|
||||
import type { ICustomizationOptions, Locale } from "../constants/style";
|
||||
import { DEFAULT_BRAND_OPTIONS } from "../constants/style";
|
||||
import { getPaperOriginUrl } from "../constants/settings";
|
||||
import {
|
||||
CHECKOUT_WITH_CARD_IFRAME_URL,
|
||||
PAPER_APP_URL,
|
||||
} from "../constants/settings";
|
||||
import type {
|
||||
ICheckoutWithCardConfigs,
|
||||
KycModal,
|
||||
ReviewResult,
|
||||
} from "../interfaces/CheckoutWithCard";
|
||||
import type {
|
||||
PaymentsSDKError,
|
||||
PaymentsSDKErrorCode,
|
||||
} from "../interfaces/PaymentsSdkError";
|
||||
import type { PriceSummary } from "../interfaces/PriceSummary";
|
||||
import { LinksManager } from "../utils/LinksManager";
|
||||
import { openCenteredPopup } from "../utils/device";
|
||||
import { postMessageToIframe } from "../utils/postMessageToIframe";
|
||||
import type { PaperPaymentElementConstructorArgs } from "./CreatePaymentElement";
|
||||
import { PaperPaymentElement } from "./CreatePaymentElement";
|
||||
import { Modal } from "./Modal";
|
||||
|
||||
export interface CheckoutWithCardLinkArgs {
|
||||
clientId?: string;
|
||||
sdkClientSecret?: string;
|
||||
appName?: string;
|
||||
options?: ICustomizationOptions;
|
||||
locale?: Locale;
|
||||
configs?: ICheckoutWithCardConfigs;
|
||||
|
||||
/**
|
||||
* @deprecated: No longer used. Domain is set to "withpaper.com".
|
||||
*/
|
||||
useAltDomain?: boolean;
|
||||
}
|
||||
|
||||
export function createCheckoutWithCardLink({
|
||||
clientId,
|
||||
sdkClientSecret,
|
||||
appName,
|
||||
options = { ...DEFAULT_BRAND_OPTIONS },
|
||||
locale,
|
||||
configs,
|
||||
}: CheckoutWithCardLinkArgs): URL {
|
||||
const CheckoutWithCardUrlBase = new URL(
|
||||
CHECKOUT_WITH_CARD_IFRAME_URL,
|
||||
PAPER_APP_URL,
|
||||
);
|
||||
let clientSecret = sdkClientSecret;
|
||||
if (!clientSecret && configs) {
|
||||
clientSecret = btoa(JSON.stringify(configs));
|
||||
}
|
||||
if (!clientSecret) {
|
||||
const error = `Must have either sdkClientSecret or configs field set. Received neither`;
|
||||
const destination = `/error?errorMessage=${error}`;
|
||||
const domain = getPaperOriginUrl();
|
||||
return new URL(destination, domain);
|
||||
}
|
||||
|
||||
if (!clientId) {
|
||||
const error = `Must have clientId field set. Please add clientId`;
|
||||
const destination = `/error?errorMessage=${error}`;
|
||||
const domain = getPaperOriginUrl();
|
||||
return new URL(destination, domain);
|
||||
}
|
||||
|
||||
const checkoutWithCardLink = new LinksManager(CheckoutWithCardUrlBase);
|
||||
checkoutWithCardLink.addClientId(clientId ?? "");
|
||||
checkoutWithCardLink.addClientSecret(clientSecret ?? "");
|
||||
checkoutWithCardLink.addStylingOptions(options);
|
||||
checkoutWithCardLink.addLocale(locale);
|
||||
checkoutWithCardLink.addAppName(appName);
|
||||
|
||||
return checkoutWithCardLink.getLink();
|
||||
}
|
||||
|
||||
export interface CheckoutWithCardMessageHandlerArgs {
|
||||
iframe: HTMLIFrameElement;
|
||||
onPaymentSuccess?: (props: {
|
||||
transactionId: string;
|
||||
/** @deprecated */
|
||||
id: string;
|
||||
}) => void;
|
||||
onReview?: (result: ReviewResult) => void;
|
||||
onError?: (error: PaymentsSDKError) => void;
|
||||
onOpenKycModal?: (props: KycModal) => void;
|
||||
onCloseKycModal?: () => void;
|
||||
onBeforeModalOpen?: (props: { url: string }) => void;
|
||||
onPriceUpdate?: (props: PriceSummary) => void;
|
||||
useAltDomain?: boolean;
|
||||
}
|
||||
|
||||
export function createCheckoutWithCardMessageHandler({
|
||||
iframe,
|
||||
onError,
|
||||
onReview,
|
||||
onPaymentSuccess,
|
||||
onBeforeModalOpen,
|
||||
onPriceUpdate,
|
||||
}: CheckoutWithCardMessageHandlerArgs) {
|
||||
let modal: Modal;
|
||||
|
||||
return (event: MessageEvent) => {
|
||||
if (!event.origin.startsWith(PAPER_APP_URL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = event;
|
||||
switch (data.eventType) {
|
||||
case "checkoutWithCardError":
|
||||
if (onError) {
|
||||
onError({
|
||||
code: data.code as PaymentsSDKErrorCode,
|
||||
error: data.error,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "paymentSuccess":
|
||||
if (onPaymentSuccess) {
|
||||
onPaymentSuccess({ transactionId: data.id, id: data.id });
|
||||
}
|
||||
|
||||
if (data.postToIframe) {
|
||||
postMessageToIframe(iframe, data.eventType, data);
|
||||
}
|
||||
break;
|
||||
|
||||
case "reviewComplete":
|
||||
if (onReview) {
|
||||
onReview({
|
||||
id: data.id,
|
||||
cardholderName: data.cardholderName,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "openModalWithUrl":
|
||||
if (
|
||||
onBeforeModalOpen &&
|
||||
data.url &&
|
||||
data.url.includes("promptKYCModal")
|
||||
) {
|
||||
onBeforeModalOpen({
|
||||
url: data.url,
|
||||
});
|
||||
} else {
|
||||
modal = new Modal(undefined, {
|
||||
body: {
|
||||
colorScheme: "light",
|
||||
},
|
||||
});
|
||||
modal.open({ iframeUrl: data.url });
|
||||
}
|
||||
break;
|
||||
|
||||
case "completedSDKModal":
|
||||
modal.close();
|
||||
if (data.postToIframe) {
|
||||
postMessageToIframe(iframe, data.eventType, data);
|
||||
}
|
||||
break;
|
||||
|
||||
case "requestedPopup": {
|
||||
// The iframe requested a popup.
|
||||
// The reference to this window is not stored so the popup cannot
|
||||
// be programmatically closed.
|
||||
const popupRef = openCenteredPopup({
|
||||
url: data.url,
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
});
|
||||
if (!popupRef) {
|
||||
console.error("CheckoutWithCard: Unable to open popup.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "sizing":
|
||||
iframe.style.height = data.height + "px";
|
||||
iframe.style.maxHeight = data.height + "px";
|
||||
break;
|
||||
|
||||
case "onPriceUpdate": {
|
||||
onPriceUpdate?.(data);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Ignore unrecognized event
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export type CheckoutWithCardElementArgs = Omit<
|
||||
CheckoutWithCardMessageHandlerArgs,
|
||||
"iframe"
|
||||
> &
|
||||
CheckoutWithCardLinkArgs &
|
||||
PaperPaymentElementConstructorArgs;
|
||||
|
||||
export function createCheckoutWithCardElement({
|
||||
clientId,
|
||||
onCloseKycModal,
|
||||
onOpenKycModal,
|
||||
sdkClientSecret,
|
||||
appName,
|
||||
elementOrId,
|
||||
onLoad,
|
||||
onError,
|
||||
locale,
|
||||
options,
|
||||
onPaymentSuccess,
|
||||
onReview,
|
||||
onBeforeModalOpen,
|
||||
onPriceUpdate,
|
||||
useAltDomain = true,
|
||||
configs,
|
||||
}: CheckoutWithCardElementArgs) {
|
||||
const checkoutWithCardId = "checkout-with-card-iframe";
|
||||
const checkoutWithCardMessageHandler = (iframe: HTMLIFrameElement) =>
|
||||
createCheckoutWithCardMessageHandler({
|
||||
iframe,
|
||||
onCloseKycModal,
|
||||
onOpenKycModal,
|
||||
onError,
|
||||
onPaymentSuccess,
|
||||
onReview,
|
||||
onBeforeModalOpen,
|
||||
onPriceUpdate,
|
||||
useAltDomain,
|
||||
});
|
||||
|
||||
const checkoutWithCardUrl = createCheckoutWithCardLink({
|
||||
clientId,
|
||||
sdkClientSecret,
|
||||
appName,
|
||||
locale,
|
||||
options,
|
||||
useAltDomain,
|
||||
configs,
|
||||
});
|
||||
|
||||
const paymentElement = new PaperPaymentElement({
|
||||
onLoad,
|
||||
elementOrId,
|
||||
});
|
||||
return paymentElement.createPaymentElement({
|
||||
handler: checkoutWithCardMessageHandler,
|
||||
iframeId: checkoutWithCardId,
|
||||
link: checkoutWithCardUrl,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
// CHANGED: Imports only
|
||||
import type { ethers } from "ethers";
|
||||
import {
|
||||
CHECKOUT_WITH_ETH_IFRAME_URL,
|
||||
PAPER_APP_URL,
|
||||
getPaperOriginUrl,
|
||||
} from "../constants/settings";
|
||||
import type { ICustomizationOptions, Locale } from "../constants/style";
|
||||
import { DEFAULT_BRAND_OPTIONS } from "../constants/style";
|
||||
import type { PaymentsSDKError } from "../interfaces/PaymentsSdkError";
|
||||
import { PayWithCryptoErrorCode } from "../interfaces/PaymentsSdkError";
|
||||
import type { PriceSummary } from "../interfaces/PriceSummary";
|
||||
import { LinksManager } from "../utils/LinksManager";
|
||||
import { handlePayWithCryptoError } from "../utils/handleCheckoutWithEthError";
|
||||
import { postMessageToIframe } from "../utils/postMessageToIframe";
|
||||
import type { PaperPaymentElementConstructorArgs } from "./CreatePaymentElement";
|
||||
import { PaperPaymentElement } from "./CreatePaymentElement";
|
||||
|
||||
export const PAY_WITH_ETH_ERROR = "payWithEthError";
|
||||
|
||||
export async function checkAndSendEth({
|
||||
data,
|
||||
iframe,
|
||||
payingWalletSigner,
|
||||
suppressErrorToast,
|
||||
onError,
|
||||
onPaymentSuccess,
|
||||
}: {
|
||||
payingWalletSigner: ethers.Signer;
|
||||
data: {
|
||||
chainId: number;
|
||||
chainName: string;
|
||||
blob: string;
|
||||
paymentAddress: string;
|
||||
value: string;
|
||||
transactionId: string;
|
||||
};
|
||||
suppressErrorToast: boolean;
|
||||
iframe: HTMLIFrameElement;
|
||||
onPaymentSuccess?:
|
||||
| CheckoutWithEthMessageHandlerArgs["onPaymentSuccess"]
|
||||
| CheckoutWithEthMessageHandlerArgs["onSuccess"];
|
||||
onError?: (error: PaymentsSDKError) => Promise<void> | void;
|
||||
}) {
|
||||
try {
|
||||
const chainId = await payingWalletSigner.getChainId();
|
||||
if (chainId !== data.chainId) {
|
||||
throw {
|
||||
isErrorObject: true,
|
||||
title: PayWithCryptoErrorCode.WrongChain,
|
||||
description: `Please change to ${data.chainName} to proceed.`,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
await handlePayWithCryptoError(e as Error, onError, (errorObject) => {
|
||||
postMessageToIframe(iframe, PAY_WITH_ETH_ERROR, {
|
||||
error: errorObject,
|
||||
suppressErrorToast,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// send the transaction
|
||||
try {
|
||||
console.log("sending funds...", data);
|
||||
const result = await payingWalletSigner.sendTransaction({
|
||||
chainId: data.chainId,
|
||||
data: data.blob,
|
||||
to: data.paymentAddress,
|
||||
value: data.value,
|
||||
});
|
||||
const receipt = await result.wait();
|
||||
if (onPaymentSuccess && result) {
|
||||
await onPaymentSuccess({
|
||||
onChainTxResponse: result,
|
||||
onChainTxReceipt: receipt,
|
||||
transactionId: data.transactionId,
|
||||
});
|
||||
}
|
||||
if (result) {
|
||||
postMessageToIframe(iframe, "paymentSuccess", {
|
||||
suppressErrorToast,
|
||||
transactionHash: result.hash,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("error sending funds", error);
|
||||
await handlePayWithCryptoError(error as Error, onError, (errorObject) => {
|
||||
postMessageToIframe(iframe, PAY_WITH_ETH_ERROR, {
|
||||
error: errorObject,
|
||||
suppressErrorToast,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface CheckoutWithEthMessageHandlerArgs {
|
||||
iframe: HTMLIFrameElement;
|
||||
onPaymentSuccess?: (props: {
|
||||
onChainTxReceipt: ethers.providers.TransactionReceipt;
|
||||
transactionId: string;
|
||||
}) => Promise<void> | void;
|
||||
/** @deprecated */
|
||||
onSuccess?: (props: {
|
||||
onChainTxResponse: ethers.providers.TransactionResponse;
|
||||
transactionId: string;
|
||||
}) => Promise<void> | void;
|
||||
onPriceUpdate?: (props: PriceSummary) => void;
|
||||
onError?: (error: PaymentsSDKError) => void;
|
||||
suppressErrorToast?: boolean;
|
||||
setUpUserPayingWalletSigner?: (args: {
|
||||
chainId: number;
|
||||
chainName?: string;
|
||||
}) => void | Promise<void>;
|
||||
payingWalletSigner: ethers.Signer;
|
||||
}
|
||||
|
||||
export function createCheckoutWithEthMessageHandler({
|
||||
iframe,
|
||||
onError,
|
||||
onPaymentSuccess,
|
||||
onPriceUpdate,
|
||||
payingWalletSigner,
|
||||
suppressErrorToast = false,
|
||||
setUpUserPayingWalletSigner,
|
||||
}: CheckoutWithEthMessageHandlerArgs) {
|
||||
return async (event: MessageEvent) => {
|
||||
if (!event.origin.startsWith(PAPER_APP_URL)) {
|
||||
return;
|
||||
}
|
||||
const data = event.data;
|
||||
switch (data.eventType) {
|
||||
case "payWithEth": {
|
||||
if (data.error) {
|
||||
await handlePayWithCryptoError(
|
||||
new Error(data.error),
|
||||
onError,
|
||||
(errorObject) => {
|
||||
postMessageToIframe(iframe, PAY_WITH_ETH_ERROR, {
|
||||
error: errorObject,
|
||||
suppressErrorToast,
|
||||
});
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Allows Dev's to inject any chain switching for their custom signer here.
|
||||
if (setUpUserPayingWalletSigner) {
|
||||
try {
|
||||
console.log("setting up signer");
|
||||
await setUpUserPayingWalletSigner({
|
||||
chainId: data.chainId,
|
||||
chainName: data.chainName,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("error setting up signer", error);
|
||||
await handlePayWithCryptoError(
|
||||
error as Error,
|
||||
onError,
|
||||
(errorObject) => {
|
||||
postMessageToIframe(iframe, PAY_WITH_ETH_ERROR, {
|
||||
error: errorObject,
|
||||
suppressErrorToast,
|
||||
});
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await checkAndSendEth({
|
||||
data,
|
||||
iframe,
|
||||
payingWalletSigner,
|
||||
suppressErrorToast,
|
||||
onError,
|
||||
onPaymentSuccess,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "checkout-with-eth-sizing": {
|
||||
iframe.style.height = data.height + "px";
|
||||
iframe.style.maxHeight = data.height + "px";
|
||||
break;
|
||||
}
|
||||
case "onPriceUpdate": {
|
||||
onPriceUpdate?.(data as PriceSummary);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICheckoutWithEthConfigs {
|
||||
contractId: string;
|
||||
walletAddress: string;
|
||||
email?: string;
|
||||
quantity?: number;
|
||||
mintMethod?: {
|
||||
name: string;
|
||||
args: Record<string, any>[];
|
||||
payment: { value: string; currency: string };
|
||||
};
|
||||
contractArgs?: Record<string, any>;
|
||||
|
||||
// payment customizations
|
||||
capturePaymentLater?: boolean;
|
||||
fiatCurrency?: string;
|
||||
|
||||
// stripe receipt
|
||||
title?: string;
|
||||
|
||||
// email
|
||||
sendEmailOnTransferSucceeded?: boolean;
|
||||
postPurchaseMessageMarkdown?: string;
|
||||
postPurchaseButtonText?: string;
|
||||
successCallbackUrl?: string;
|
||||
}
|
||||
|
||||
export interface CheckoutWithEthLinkArgs {
|
||||
sdkClientSecret?: string;
|
||||
appName?: string;
|
||||
payingWalletSigner: ethers.Signer;
|
||||
receivingWalletType?:
|
||||
| "WalletConnect"
|
||||
| "MetaMask"
|
||||
| "Coinbase Wallet"
|
||||
| string;
|
||||
showConnectWalletOptions?: boolean;
|
||||
|
||||
locale?: Locale;
|
||||
options?: ICustomizationOptions;
|
||||
configs?: ICheckoutWithEthConfigs;
|
||||
}
|
||||
|
||||
export async function createCheckoutWithEthLink({
|
||||
sdkClientSecret,
|
||||
payingWalletSigner,
|
||||
receivingWalletType,
|
||||
showConnectWalletOptions = false,
|
||||
appName,
|
||||
locale,
|
||||
options = {
|
||||
...DEFAULT_BRAND_OPTIONS,
|
||||
},
|
||||
configs,
|
||||
}: CheckoutWithEthLinkArgs) {
|
||||
const checkoutWithEthUrlBase = new URL(
|
||||
CHECKOUT_WITH_ETH_IFRAME_URL,
|
||||
PAPER_APP_URL,
|
||||
);
|
||||
const address = await payingWalletSigner.getAddress();
|
||||
let clientSecret = sdkClientSecret;
|
||||
if (!clientSecret && configs) {
|
||||
clientSecret = btoa(JSON.stringify(configs));
|
||||
}
|
||||
if (!clientSecret) {
|
||||
const error = `Must have either sdkClientSecret or configs field set. Received neither`;
|
||||
const destination = `/error?errorMessage=${error}`;
|
||||
const domain = getPaperOriginUrl();
|
||||
return new URL(destination, domain);
|
||||
}
|
||||
|
||||
const checkoutWithEthLink = new LinksManager(checkoutWithEthUrlBase);
|
||||
checkoutWithEthLink.addClientSecret(clientSecret ?? "");
|
||||
checkoutWithEthLink.addRecipientWalletAddress(address);
|
||||
checkoutWithEthLink.addPayerWalletAddress(address);
|
||||
checkoutWithEthLink.addReceivingWalletType(receivingWalletType);
|
||||
checkoutWithEthLink.addAppName(appName);
|
||||
checkoutWithEthLink.addShowConnectWalletOptions(showConnectWalletOptions);
|
||||
checkoutWithEthLink.addStylingOptions(options);
|
||||
checkoutWithEthLink.addLocale(locale);
|
||||
|
||||
return checkoutWithEthLink.getLink();
|
||||
}
|
||||
|
||||
export type CheckoutWithEthElementArgs = Omit<
|
||||
Omit<CheckoutWithEthMessageHandlerArgs, "iframe">,
|
||||
"setUpUserPayingWalletSigner"
|
||||
> &
|
||||
CheckoutWithEthLinkArgs &
|
||||
PaperPaymentElementConstructorArgs;
|
||||
|
||||
export async function createCheckoutWithEthElement({
|
||||
sdkClientSecret,
|
||||
suppressErrorToast,
|
||||
onError,
|
||||
onLoad,
|
||||
payingWalletSigner,
|
||||
receivingWalletType,
|
||||
appName,
|
||||
showConnectWalletOptions,
|
||||
locale,
|
||||
options,
|
||||
elementOrId,
|
||||
onPaymentSuccess: _onPaymentSuccess,
|
||||
onSuccess,
|
||||
}: CheckoutWithEthElementArgs): Promise<HTMLIFrameElement> {
|
||||
const onPaymentSuccess =
|
||||
_onPaymentSuccess ??
|
||||
(onSuccess as CheckoutWithEthMessageHandlerArgs["onPaymentSuccess"]);
|
||||
const checkoutWithEthId = "checkout-with-eth-iframe";
|
||||
|
||||
const checkoutWithEthMessageHandler = (iframe: HTMLIFrameElement) =>
|
||||
createCheckoutWithEthMessageHandler({
|
||||
iframe,
|
||||
onError,
|
||||
payingWalletSigner,
|
||||
suppressErrorToast,
|
||||
onPaymentSuccess,
|
||||
});
|
||||
|
||||
const checkoutWithEthUrl = await createCheckoutWithEthLink({
|
||||
payingWalletSigner,
|
||||
sdkClientSecret,
|
||||
appName,
|
||||
locale,
|
||||
options,
|
||||
receivingWalletType,
|
||||
showConnectWalletOptions,
|
||||
});
|
||||
const paymentElement = new PaperPaymentElement({
|
||||
onLoad,
|
||||
elementOrId,
|
||||
});
|
||||
return paymentElement.createPaymentElement({
|
||||
handler: checkoutWithEthMessageHandler,
|
||||
iframeId: checkoutWithEthId,
|
||||
link: checkoutWithEthUrl,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// CHANGED: packageJson import + version string
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, better-tree-shaking/no-top-level-side-effects
|
||||
const packageJson = require("../package.json");
|
||||
|
||||
export interface PaperPaymentElementConstructorArgs {
|
||||
onLoad?: (event?: Event) => void;
|
||||
elementOrId?: string | HTMLElement;
|
||||
}
|
||||
|
||||
export class PaperPaymentElement {
|
||||
private elementOrId?: PaperPaymentElementConstructorArgs["elementOrId"];
|
||||
private onLoad?: PaperPaymentElementConstructorArgs["onLoad"];
|
||||
|
||||
constructor({ elementOrId, onLoad }: PaperPaymentElementConstructorArgs) {
|
||||
this.elementOrId = elementOrId;
|
||||
this.onLoad = onLoad;
|
||||
}
|
||||
createPaymentElement({
|
||||
handler,
|
||||
link,
|
||||
iframeId,
|
||||
}: {
|
||||
handler: (
|
||||
iframe: HTMLIFrameElement,
|
||||
) => (event: MessageEvent) => void | Promise<void>;
|
||||
link: URL;
|
||||
iframeId: string;
|
||||
}) {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = link.href;
|
||||
iframe.id = iframeId;
|
||||
iframe.allow = "payment";
|
||||
iframe.setAttribute(
|
||||
"style",
|
||||
"margin-left:auto; margin-right:auto; width:100%; height: 100%; min-height:375px; transition-property:all; transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1); transition-duration:150ms; color-scheme: light;",
|
||||
);
|
||||
iframe.onload = (event: Event) => {
|
||||
if (this.onLoad) {
|
||||
this.onLoad(event);
|
||||
}
|
||||
};
|
||||
iframe.setAttribute(
|
||||
"data-thirdweb-sdk-version",
|
||||
`${packageJson.name}@${packageJson.version}`,
|
||||
);
|
||||
|
||||
if (!this.elementOrId) {
|
||||
window.addEventListener("message", handler(iframe));
|
||||
return iframe;
|
||||
}
|
||||
|
||||
let container: HTMLElement | string = this.elementOrId;
|
||||
if (typeof container === "string") {
|
||||
const domElement = document.getElementById(container);
|
||||
if (!domElement) {
|
||||
throw new Error("Invalid id given");
|
||||
}
|
||||
container = domElement;
|
||||
}
|
||||
|
||||
const existing: HTMLIFrameElement | null = container.querySelector(
|
||||
"#" + iframeId,
|
||||
);
|
||||
// if we already created an iframe, consider updating the iframe link if it's new
|
||||
if (existing) {
|
||||
if (existing.src === link.href) {
|
||||
return existing;
|
||||
}
|
||||
existing.src = link.href;
|
||||
return existing;
|
||||
}
|
||||
|
||||
window.addEventListener("message", handler(iframe));
|
||||
return container.appendChild(iframe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import type { StyleObject } from "../../constants/style";
|
||||
import type { ModalStyles } from "../../interfaces/Modal";
|
||||
import { getDefaultModalStyles } from "./styles";
|
||||
|
||||
import packageJson from "../../../package.json";
|
||||
|
||||
const MAIN_CLASSNAME = "paper--drawer-main";
|
||||
const OVERLAY_CLASSNAME = "paper--drawer-overlay";
|
||||
const BODY_CLASSNAME = "paper--drawer-body";
|
||||
const CLOSE_CLASSNAME = "paper--drawer-close";
|
||||
|
||||
export class Drawer {
|
||||
protected container: HTMLElement;
|
||||
protected main: HTMLDivElement;
|
||||
protected overlay: HTMLDivElement;
|
||||
protected closeButton: HTMLButtonElement;
|
||||
protected iframe: HTMLIFrameElement;
|
||||
protected onCloseCallback: (() => void) | undefined;
|
||||
|
||||
protected closeTimeout: number | undefined;
|
||||
// eslint-disable-next-line better-tree-shaking/no-top-level-side-effects
|
||||
styles = getDefaultModalStyles();
|
||||
body: HTMLDivElement;
|
||||
|
||||
constructor(container?: HTMLElement, styles?: Partial<ModalStyles>) {
|
||||
this.container = container || document.body;
|
||||
|
||||
if (styles) {
|
||||
this.mergeStyles(styles);
|
||||
}
|
||||
|
||||
this.main = document.createElement("div");
|
||||
this.main.className = MAIN_CLASSNAME;
|
||||
|
||||
this.overlay = document.createElement("div");
|
||||
this.overlay.className = OVERLAY_CLASSNAME;
|
||||
|
||||
this.body = document.createElement("div");
|
||||
this.body.className = BODY_CLASSNAME;
|
||||
|
||||
this.closeButton = document.createElement("button");
|
||||
this.closeButton.className = CLOSE_CLASSNAME;
|
||||
this.closeButton.innerHTML = "✕";
|
||||
this.closeButton.onclick = () => {
|
||||
this.close();
|
||||
};
|
||||
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.allow = "camera; microphone; payment";
|
||||
this.iframe.setAttribute(
|
||||
"data-thirdweb-sdk-version",
|
||||
`${packageJson.name}@${packageJson.version}`,
|
||||
);
|
||||
|
||||
this.assignStyles(this.main, this.styles.main);
|
||||
this.assignStyles(this.overlay, this.styles.overlay);
|
||||
this.assignStyles(this.body, this.styles.body);
|
||||
this.assignStyles(this.iframe, this.styles.iframe);
|
||||
if (this.styles.closeButton) {
|
||||
this.assignStyles(this.closeButton, this.styles.closeButton);
|
||||
}
|
||||
}
|
||||
|
||||
open({ iframeUrl }: { iframeUrl?: string } = {}) {
|
||||
if (iframeUrl) {
|
||||
this.iframe.src = iframeUrl;
|
||||
this.body.appendChild(this.iframe);
|
||||
}
|
||||
|
||||
this.addAccessibility();
|
||||
|
||||
this.main.appendChild(this.overlay);
|
||||
this.main.appendChild(this.body);
|
||||
this.main.appendChild(this.closeButton);
|
||||
|
||||
this.container.appendChild(this.main);
|
||||
document.body.style.overflow = "hidden";
|
||||
|
||||
// Animate in.
|
||||
this.overlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
|
||||
this.body.style.right = "0px";
|
||||
this.body.style.opacity = "1";
|
||||
|
||||
return this.iframe;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.closeButton.remove();
|
||||
|
||||
// Animate out.
|
||||
this.overlay.style.backgroundColor = "rgba(0, 0, 0, 0)";
|
||||
this.body.style.right = "-100px";
|
||||
this.body.style.opacity = "0";
|
||||
|
||||
// Remove drawer from DOM.
|
||||
this.closeTimeout = window.setTimeout(() => {
|
||||
document.body.style.overflow = "visible";
|
||||
this.main.remove();
|
||||
|
||||
window.clearTimeout(this.closeTimeout);
|
||||
this.onCloseCallback?.();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
setOnCloseCallback(callback: () => void) {
|
||||
this.onCloseCallback = callback;
|
||||
}
|
||||
|
||||
protected mergeStyles(styles: Partial<ModalStyles>) {
|
||||
this.styles.body = {
|
||||
...this.styles.body,
|
||||
...(styles.body || {}),
|
||||
};
|
||||
|
||||
this.styles.overlay = {
|
||||
...this.styles.overlay,
|
||||
...(styles.overlay || {}),
|
||||
};
|
||||
|
||||
this.styles.main = {
|
||||
...this.styles.main,
|
||||
...(styles.main || {}),
|
||||
};
|
||||
|
||||
this.styles.iframe = {
|
||||
...this.styles.iframe,
|
||||
...(styles.iframe || {}),
|
||||
};
|
||||
|
||||
this.styles.closeButton = {
|
||||
...this.styles.closeButton,
|
||||
...(styles.closeButton || {}),
|
||||
};
|
||||
}
|
||||
|
||||
protected addAccessibility() {
|
||||
this.main.setAttribute("aria-hidden", "true");
|
||||
this.overlay.setAttribute("aria-hidden", "true");
|
||||
this.body.setAttribute("aria-modal", "true");
|
||||
this.body.setAttribute("role", "dialog");
|
||||
}
|
||||
|
||||
protected assignStyles(el: HTMLElement, styles: StyleObject) {
|
||||
Object.assign(el.style, styles);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import type { ModalStyles } from "../../interfaces/Modal";
|
||||
|
||||
const fullScreen = {
|
||||
position: "fixed",
|
||||
top: "0px",
|
||||
left: "0px",
|
||||
right: "0px",
|
||||
bottom: "0px",
|
||||
};
|
||||
|
||||
export const getDefaultModalStyles = (): ModalStyles => ({
|
||||
main: {
|
||||
...fullScreen,
|
||||
zIndex: "2147483646",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "end",
|
||||
},
|
||||
overlay: {
|
||||
...fullScreen,
|
||||
backgroundColor: "rgba(0, 0, 0, 0)",
|
||||
transition: "background-color ease-out 0.2s",
|
||||
},
|
||||
body: {
|
||||
position: "fixed",
|
||||
width: "100%",
|
||||
maxWidth: "420px",
|
||||
top: "0px",
|
||||
bottom: "0px",
|
||||
right: "-100px",
|
||||
opacity: "0",
|
||||
boxShadow: "0px 10px 20px rgba(0, 0, 0, 0.25)",
|
||||
backgroundColor: window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "#2F2F2F"
|
||||
: "white",
|
||||
overflow: "hidden",
|
||||
transition: "all ease-out 0.2s",
|
||||
},
|
||||
spinner: {
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
bottom: "0",
|
||||
left: "0",
|
||||
right: "0",
|
||||
margin: "auto",
|
||||
borderWidth: "3px",
|
||||
borderColor: "#2D3748 #2D3748 transparent transparent",
|
||||
borderRadius: "50%",
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
animation: "spin 1s linear infinite",
|
||||
},
|
||||
iframe: {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
closeButton: {
|
||||
position: "fixed",
|
||||
cursor: "pointer",
|
||||
top: "0.75rem",
|
||||
right: "1rem",
|
||||
color: "#888",
|
||||
padding: "2px 8px",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { StyleObject } from "../../constants/style";
|
||||
import type { ModalStyles } from "../../interfaces/Modal";
|
||||
import { getDefaultModalStyles, modalKeyframeAnimations } from "./styles";
|
||||
|
||||
import packageJson from "../../../package.json";
|
||||
|
||||
export const MODAL_ID = "paper-js-sdk-modal";
|
||||
|
||||
export class Modal {
|
||||
protected container: HTMLElement;
|
||||
protected main: HTMLDivElement;
|
||||
protected overlay: HTMLDivElement;
|
||||
protected iframe: HTMLIFrameElement;
|
||||
protected spinner: HTMLDivElement;
|
||||
|
||||
protected style: HTMLStyleElement;
|
||||
// eslint-disable-next-line better-tree-shaking/no-top-level-side-effects
|
||||
styles = getDefaultModalStyles();
|
||||
body: HTMLDivElement;
|
||||
|
||||
constructor(container?: HTMLElement, styles?: Partial<ModalStyles>) {
|
||||
this.container = container || document.body;
|
||||
|
||||
if (styles) {
|
||||
this.mergeStyles(styles);
|
||||
}
|
||||
|
||||
this.main = document.createElement("div");
|
||||
this.main.id = MODAL_ID;
|
||||
|
||||
this.overlay = document.createElement("div");
|
||||
this.overlay.id = `${MODAL_ID}-overlay`;
|
||||
this.body = document.createElement("div");
|
||||
this.body.id = `${MODAL_ID}-body`;
|
||||
this.spinner = document.createElement("div");
|
||||
this.spinner.id = `${MODAL_ID}-spinner`;
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.id = `${MODAL_ID}-iframe`;
|
||||
this.iframe.allow = "camera; microphone; payment";
|
||||
this.iframe.setAttribute(
|
||||
"data-thirdweb-sdk-version",
|
||||
`${packageJson.name}@${packageJson.version}`,
|
||||
);
|
||||
|
||||
this.style = document.createElement("style");
|
||||
this.style.innerHTML = modalKeyframeAnimations;
|
||||
|
||||
this.assignStyles(this.main, this.styles.main);
|
||||
this.assignStyles(this.overlay, this.styles.overlay);
|
||||
this.assignStyles(this.body, this.styles.body);
|
||||
this.assignStyles(this.spinner, this.styles.spinner);
|
||||
this.assignStyles(this.iframe, this.styles.iframe);
|
||||
}
|
||||
|
||||
open({ iframeUrl }: { iframeUrl?: string } = {}) {
|
||||
if (iframeUrl) {
|
||||
this.body.appendChild(this.spinner);
|
||||
this.iframe.src = iframeUrl;
|
||||
// Remove the spinner when the iframe loads.
|
||||
this.iframe.onload = () => this.body.removeChild(this.spinner);
|
||||
this.body.appendChild(this.iframe);
|
||||
}
|
||||
|
||||
this.addAccessibility();
|
||||
|
||||
this.main.appendChild(this.overlay);
|
||||
this.main.appendChild(this.style);
|
||||
this.main.appendChild(this.body);
|
||||
|
||||
this.container.appendChild(this.main);
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
close() {
|
||||
this.body.style.animation = "pew-modal-slideOut 0.2s forwards";
|
||||
window.setTimeout(() => this.main.remove(), 250);
|
||||
}
|
||||
|
||||
protected mergeStyles(styles: Partial<ModalStyles>) {
|
||||
this.styles.body = {
|
||||
...this.styles.body,
|
||||
...(styles.body || {}),
|
||||
};
|
||||
|
||||
this.styles.spinner = {
|
||||
...this.styles.spinner,
|
||||
...(styles.spinner || {}),
|
||||
};
|
||||
|
||||
this.styles.overlay = {
|
||||
...this.styles.overlay,
|
||||
...(styles.overlay || {}),
|
||||
};
|
||||
|
||||
this.styles.main = {
|
||||
...this.styles.main,
|
||||
...(styles.main || {}),
|
||||
};
|
||||
|
||||
this.styles.iframe = {
|
||||
...this.styles.iframe,
|
||||
...(styles.iframe || {}),
|
||||
};
|
||||
}
|
||||
|
||||
protected addAccessibility() {
|
||||
this.main.setAttribute("aria-hidden", "true");
|
||||
this.overlay.setAttribute("aria-hidden", "true");
|
||||
this.body.setAttribute("aria-modal", "true");
|
||||
this.body.setAttribute("role", "dialog");
|
||||
}
|
||||
|
||||
protected assignStyles(el: HTMLElement, styles: StyleObject) {
|
||||
Object.assign(el.style, styles);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { ModalStyles } from "../../interfaces/Modal";
|
||||
|
||||
const fullScreen = {
|
||||
top: "0px",
|
||||
left: "0px",
|
||||
right: "0px",
|
||||
bottom: "0px",
|
||||
};
|
||||
|
||||
export const getDefaultModalStyles = (): ModalStyles => ({
|
||||
main: {
|
||||
...fullScreen,
|
||||
position: "fixed",
|
||||
zIndex: "10000",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
pointerEvents: "auto",
|
||||
},
|
||||
overlay: {
|
||||
...fullScreen,
|
||||
position: "absolute",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.33)",
|
||||
pointerEvents: "auto",
|
||||
},
|
||||
body: {
|
||||
background: "transparent",
|
||||
borderRadius: "12px",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
maxWidth: "500px",
|
||||
height: "700px",
|
||||
maxHeight: "80%",
|
||||
animation: "pew-modal-slideIn 0.2s forwards",
|
||||
pointerEvents: "auto",
|
||||
},
|
||||
spinner: {
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
bottom: "0",
|
||||
left: "0",
|
||||
right: "0",
|
||||
margin: "auto",
|
||||
borderWidth: "3px",
|
||||
borderColor: "#2D3748 #2D3748 transparent transparent",
|
||||
borderRadius: "50%",
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
animation: "spin 1s linear infinite",
|
||||
},
|
||||
iframe: {
|
||||
position: "relative",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
border: "none",
|
||||
background: "transparent",
|
||||
},
|
||||
});
|
||||
|
||||
export const modalKeyframeAnimations = `
|
||||
@keyframes pew-modal-slideIn {
|
||||
from {opacity: 0; transform: translate3d(0, 20px, 0);}
|
||||
to {opacity: 1; transform: translate3d(0, 0, 0);}
|
||||
}
|
||||
|
||||
@keyframes pew-modal-slideOut {
|
||||
from {opacity: 1; transform: translate3d(0, 0, 0);}
|
||||
to {opacity: 0; transform: translate3d(0, 20px, 0);}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
// CHANGED: ADDED
|
||||
export * from "./CheckoutWithCard";
|
||||
export * from "./CheckoutWithEth";
|
||||
export * from "./renderPaperCheckoutLink";
|
||||
@@ -0,0 +1,82 @@
|
||||
// UNCHANGED
|
||||
import { PAPER_APP_URL } from "../constants/settings";
|
||||
import { Drawer } from "./Drawer";
|
||||
|
||||
async function sleepForSeconds(seconds: number) {
|
||||
return new Promise((res) => {
|
||||
setTimeout(() => {
|
||||
res(0);
|
||||
}, seconds * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
export function renderPaperCheckoutLink({
|
||||
checkoutLinkUrl,
|
||||
onPaymentSucceeded,
|
||||
onPaymentFailed,
|
||||
onTransferSucceeded,
|
||||
onModalClosed,
|
||||
}: {
|
||||
checkoutLinkUrl: string;
|
||||
onPaymentSucceeded?: ({ transactionId }: { transactionId: string }) => void;
|
||||
onPaymentFailed?: ({ transactionId }: { transactionId: string }) => void;
|
||||
onTransferSucceeded?: ({
|
||||
transactionId,
|
||||
claimedTokens,
|
||||
}: {
|
||||
transactionId: string;
|
||||
claimedTokens: any;
|
||||
}) => void;
|
||||
onModalClosed?: () => void;
|
||||
}) {
|
||||
const drawer = new Drawer();
|
||||
|
||||
const formattedCheckoutLinkUrl = new URL(checkoutLinkUrl);
|
||||
formattedCheckoutLinkUrl.searchParams.set("display", "DRAWER");
|
||||
drawer.open({ iframeUrl: formattedCheckoutLinkUrl.href });
|
||||
if (onModalClosed) {
|
||||
drawer.setOnCloseCallback(onModalClosed);
|
||||
}
|
||||
|
||||
const messageHandler = async (e: MessageEvent) => {
|
||||
if (e.origin !== PAPER_APP_URL) {
|
||||
return;
|
||||
}
|
||||
const result = e.data;
|
||||
if (!result.eventType) {
|
||||
return;
|
||||
}
|
||||
switch (result.eventType) {
|
||||
case "paymentSuccess": {
|
||||
const transactionId = e.data.id;
|
||||
onPaymentSucceeded?.({ transactionId });
|
||||
break;
|
||||
}
|
||||
case "claimSuccessful": {
|
||||
const { id: transactionId, claimedTokens } = e.data;
|
||||
onTransferSucceeded?.({ transactionId, claimedTokens });
|
||||
await sleepForSeconds(3.5);
|
||||
drawer.close();
|
||||
break;
|
||||
}
|
||||
case "redirectAfterSuccess": {
|
||||
const redirectUrl = e.data.redirectUrl;
|
||||
window.location.assign(redirectUrl);
|
||||
break;
|
||||
}
|
||||
case "paymentFailed": {
|
||||
const transactionId = e.data.id;
|
||||
onPaymentFailed?.({ transactionId });
|
||||
break;
|
||||
}
|
||||
case "modalClosed": {
|
||||
onModalClosed?.();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported eventType ${result.eventType}`);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", messageHandler);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// CHANGED: added clientId to link
|
||||
|
||||
import type { ICustomizationOptions, Locale } from "../constants/style";
|
||||
|
||||
export class LinksManager {
|
||||
private link: URL;
|
||||
|
||||
constructor(baseLink: URL) {
|
||||
this.link = baseLink;
|
||||
}
|
||||
|
||||
addStylingOptions(options: ICustomizationOptions) {
|
||||
if (options.colorPrimary) {
|
||||
this.link.searchParams.set("colorPrimary", options.colorPrimary);
|
||||
}
|
||||
if (options.colorBackground) {
|
||||
this.link.searchParams.set("colorBackground", options.colorBackground);
|
||||
}
|
||||
if (options.colorText) {
|
||||
this.link.searchParams.set("colorText", options.colorText);
|
||||
}
|
||||
if (options.borderRadius !== undefined) {
|
||||
this.link.searchParams.set(
|
||||
"borderRadius",
|
||||
options.borderRadius.toString(),
|
||||
);
|
||||
}
|
||||
if (options.fontFamily) {
|
||||
this.link.searchParams.set("fontFamily", options.fontFamily);
|
||||
}
|
||||
if (options.inputBackgroundColor) {
|
||||
this.link.searchParams.set(
|
||||
"inputBackgroundColor",
|
||||
options.inputBackgroundColor,
|
||||
);
|
||||
}
|
||||
if (options.inputBorderColor) {
|
||||
this.link.searchParams.set("inputBorderColor", options.inputBorderColor);
|
||||
}
|
||||
}
|
||||
|
||||
addClientSecret(sdkClientSecret: string) {
|
||||
this.link.searchParams.set("sdkClientSecret", sdkClientSecret);
|
||||
}
|
||||
|
||||
addClientId(clientId: string) {
|
||||
this.link.searchParams.set("clientId", clientId);
|
||||
}
|
||||
|
||||
addLocale(locale?: Locale) {
|
||||
if (locale) {
|
||||
this.link.searchParams.set("locale", locale.toString());
|
||||
}
|
||||
}
|
||||
|
||||
addOTP() {
|
||||
this.link.searchParams.set("withOTP", "true");
|
||||
}
|
||||
|
||||
addAppName(appName?: string) {
|
||||
if (appName) {
|
||||
this.link.searchParams.set("appName", appName);
|
||||
}
|
||||
}
|
||||
|
||||
addShowConnectWalletOptions(showConnectWalletOptions: boolean) {
|
||||
this.link.searchParams.append(
|
||||
"showConnectWalletOptions",
|
||||
showConnectWalletOptions.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
addReceivingWalletType(walletType?: string) {
|
||||
this.link.searchParams.append("walletType", walletType || "Preset");
|
||||
}
|
||||
|
||||
addRecipientWalletAddress(address: string) {
|
||||
this.link.searchParams.set("recipientWalletAddress", address);
|
||||
}
|
||||
|
||||
addPayerWalletAddress(address: string) {
|
||||
this.link.searchParams.append("payerWalletAddress", address);
|
||||
}
|
||||
|
||||
addDate(date?: Date) {
|
||||
this.link.searchParams.set(
|
||||
"date",
|
||||
date ? date.toString() : Date.now().toString(),
|
||||
);
|
||||
}
|
||||
|
||||
getLink(): URL {
|
||||
return this.link;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// UNCHANGED
|
||||
/**
|
||||
* Opens a popup centered on the current window.
|
||||
* Note that modern browsers may prevent popups from opening automatically,
|
||||
* so try to handle the case where the window returned is null.
|
||||
*
|
||||
* @returns Window | null - The Window object that was opened. If null is returned, the popup failed to open.
|
||||
*/
|
||||
export const openCenteredPopup = ({
|
||||
url,
|
||||
width = 440,
|
||||
height = 700,
|
||||
}: {
|
||||
url: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}): Window | null => {
|
||||
if (!window?.top) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const y = window.top.outerHeight / 2 + window.top.screenY - height / 2;
|
||||
const x = window.top.outerWidth / 2 + window.top.screenX - width / 2;
|
||||
return window.open(
|
||||
url,
|
||||
"_blank",
|
||||
`popup=true,width=${width},height=${height},top=${y},left=${x}`,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
// Changed: Updated PaperSDKError
|
||||
import type { PaymentsSDKError } from "../interfaces/PaymentsSdkError";
|
||||
import { PayWithCryptoErrorCode } from "../interfaces/PaymentsSdkError";
|
||||
|
||||
export interface IErrorObject {
|
||||
isErrorObject: boolean;
|
||||
title: PayWithCryptoErrorCode;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const handlePayWithCryptoError = async (
|
||||
error: Error | IErrorObject,
|
||||
onError?: (code: PaymentsSDKError) => Promise<void> | void,
|
||||
postToParent?: (errorObject: Omit<IErrorObject, "isErrorObject">) => void,
|
||||
) => {
|
||||
if ("isErrorObject" in error) {
|
||||
if (onError) {
|
||||
await onError({ code: error.title, error: new Error(error.title) });
|
||||
}
|
||||
if (postToParent) {
|
||||
postToParent({ ...error });
|
||||
}
|
||||
} else if (!("message" in error)) {
|
||||
if (onError) {
|
||||
await onError({
|
||||
code: PayWithCryptoErrorCode.ErrorSendingTransaction,
|
||||
error: new Error(JSON.stringify(error)),
|
||||
});
|
||||
}
|
||||
if (postToParent) {
|
||||
postToParent({
|
||||
description: `${error}`,
|
||||
title: PayWithCryptoErrorCode.ErrorSendingTransaction,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
error.message.includes("rejected") ||
|
||||
error.message.includes("denied transaction")
|
||||
) {
|
||||
if (onError) {
|
||||
await onError({
|
||||
code: PayWithCryptoErrorCode.TransactionCancelled,
|
||||
error,
|
||||
});
|
||||
}
|
||||
if (postToParent) {
|
||||
postToParent({
|
||||
description: "",
|
||||
title: PayWithCryptoErrorCode.TransactionCancelled,
|
||||
});
|
||||
}
|
||||
} else if (error.message.includes("insufficient funds")) {
|
||||
if (onError) {
|
||||
await onError({
|
||||
code: PayWithCryptoErrorCode.InsufficientBalance,
|
||||
error,
|
||||
});
|
||||
}
|
||||
if (postToParent) {
|
||||
postToParent({
|
||||
description:
|
||||
"Check your wallet's ETH balance to make sure you have enough!",
|
||||
title: PayWithCryptoErrorCode.InsufficientBalance,
|
||||
});
|
||||
}
|
||||
} else if (error.message.includes("Error switching chain")) {
|
||||
if (onError) {
|
||||
await onError({
|
||||
code: PayWithCryptoErrorCode.ChainSwitchUnderway,
|
||||
error,
|
||||
});
|
||||
}
|
||||
if (postToParent) {
|
||||
postToParent({
|
||||
description: "Check your wallet app",
|
||||
title: PayWithCryptoErrorCode.ChainSwitchUnderway,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (onError) {
|
||||
await onError({
|
||||
code: PayWithCryptoErrorCode.ErrorSendingTransaction,
|
||||
error,
|
||||
});
|
||||
}
|
||||
if (postToParent) {
|
||||
postToParent({
|
||||
description: `${error.message}`,
|
||||
title: PayWithCryptoErrorCode.ErrorSendingTransaction,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
// UNCHANGED
|
||||
export function postMessageToIframe<T>(
|
||||
frame: HTMLIFrameElement,
|
||||
eventType: string,
|
||||
data: T,
|
||||
) {
|
||||
frame.contentWindow?.postMessage({ eventType, ...data }, "*");
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@thirdweb-dev/tsconfig/sdk.json",
|
||||
"include": ["src", "types"],
|
||||
"exclude": ["dist", "build", "node_modules"],
|
||||
"compilerOptions": {
|
||||
"noUncheckedIndexedAccess": true
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,16 @@
|
||||
# @thirdweb-dev/react-core
|
||||
|
||||
## 4.2.1
|
||||
|
||||
## 4.2.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b808e942`](https://github.com/thirdweb-dev/js/commit/b808e942fdaa81b5de61dcb3f2083081cb99ea11), [`e46dd025`](https://github.com/thirdweb-dev/js/commit/e46dd0254f157c3c4d50fe3e4f44e2e24eaf34a8)]:
|
||||
- @thirdweb-dev/sdk@4.0.24
|
||||
- @thirdweb-dev/wallets@2.3.3
|
||||
- @thirdweb-dev/auth@4.1.20
|
||||
|
||||
## 4.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-core",
|
||||
"version": "4.1.17",
|
||||
"version": "4.2.1",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @thirdweb-dev/react-native-compat
|
||||
|
||||
## 0.5.4
|
||||
|
||||
## 0.5.3
|
||||
|
||||
## 0.5.2
|
||||
|
||||
## 0.5.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native-compat",
|
||||
"description": "Shims for Thirdweb in React Native Projects",
|
||||
"version": "0.5.2",
|
||||
"version": "0.5.4",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# @thirdweb-dev/react-native
|
||||
|
||||
## 0.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/react-core@4.2.1
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2101](https://github.com/thirdweb-dev/js/pull/2101) [`7fdb1715`](https://github.com/thirdweb-dev/js/commit/7fdb1715e4263e9b9c90370a1e70c7fa1fa0e95e) Thanks [@iketw](https://github.com/iketw)! - Export types for better reference docs
|
||||
|
||||
- [#2108](https://github.com/thirdweb-dev/js/pull/2108) [`b90fa2cc`](https://github.com/thirdweb-dev/js/commit/b90fa2cc298bf1a6deb0f4c0f927ab77f2716448) Thanks [@iketw](https://github.com/iketw)! - Adds `hideSendButton` and `hideReceiveButton` to the ConnectWallet component.
|
||||
|
||||
Both props default to `false`.
|
||||
|
||||
```js
|
||||
<ConnectWallet hideReceiveButton={true} hideSendButton={true} />
|
||||
```
|
||||
|
||||
- Updated dependencies [[`b808e942`](https://github.com/thirdweb-dev/js/commit/b808e942fdaa81b5de61dcb3f2083081cb99ea11), [`e46dd025`](https://github.com/thirdweb-dev/js/commit/e46dd0254f157c3c4d50fe3e4f44e2e24eaf34a8)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native",
|
||||
"version": "0.5.2",
|
||||
"version": "0.5.4",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -24,15 +24,29 @@ import { SupportedTokens, defaultTokens } from "./SendFunds/defaultTokens";
|
||||
|
||||
export type ConnectWalletProps = {
|
||||
/**
|
||||
* render a custom button to display the connected wallet details instead of the default button
|
||||
* Renders a custom button to display the connected wallet details instead of the default button
|
||||
*/
|
||||
detailsButton?: ConnectWalletDetailsProps["detailsButton"];
|
||||
|
||||
/**
|
||||
* render custom rows in the Connect Wallet Details modal
|
||||
* Renders custom rows in the Connect Wallet Details modal
|
||||
*/
|
||||
extraRows?: ConnectWalletDetailsProps["extraRows"];
|
||||
|
||||
/**
|
||||
* Option to hide the Send button in the wallet details modal.
|
||||
*
|
||||
* The default is `false`
|
||||
*/
|
||||
hideSendButton?: boolean;
|
||||
|
||||
/**
|
||||
* Option to hide the Receive button in the wallet details modal.
|
||||
*
|
||||
* The default is `false`
|
||||
*/
|
||||
hideReceiveButton?: boolean;
|
||||
|
||||
/**
|
||||
* Hide option to request testnet funds for testnets in dropdown
|
||||
*
|
||||
@@ -84,6 +98,8 @@ export const ConnectWallet = ({
|
||||
buttonTitle,
|
||||
modalTitle,
|
||||
modalTitleIconUrl,
|
||||
hideReceiveButton,
|
||||
hideSendButton,
|
||||
extraRows,
|
||||
hideTestnetFaucet,
|
||||
displayBalanceToken,
|
||||
@@ -169,6 +185,8 @@ export const ConnectWallet = ({
|
||||
detailsButton={detailsButton}
|
||||
extraRows={extraRows}
|
||||
hideTestnetFaucet={hideTestnetFaucet}
|
||||
hideReceiveButton={hideReceiveButton}
|
||||
hideSendButton={hideSendButton}
|
||||
supportedTokens={supportedTokensMemo}
|
||||
displayBalanceToken={displayBalanceToken}
|
||||
hideSwitchToPersonalWallet={hideSwitchToPersonalWallet}
|
||||
|
||||
+27
-9
@@ -45,6 +45,8 @@ export const ConnectWalletDetailsModal = ({
|
||||
supportedTokens,
|
||||
displayBalanceToken,
|
||||
hideSwitchToPersonalWallet,
|
||||
hideReceiveButton,
|
||||
hideSendButton,
|
||||
}: {
|
||||
isVisible: boolean;
|
||||
onClosePress: () => void;
|
||||
@@ -54,6 +56,8 @@ export const ConnectWalletDetailsModal = ({
|
||||
supportedTokens: SupportedTokens;
|
||||
displayBalanceToken?: Record<number, string>;
|
||||
hideSwitchToPersonalWallet?: boolean;
|
||||
hideReceiveButton?: boolean;
|
||||
hideSendButton?: boolean;
|
||||
}) => {
|
||||
const l = useLocale();
|
||||
const [isExportModalVisible, setIsExportModalVisible] = useState(false);
|
||||
@@ -143,7 +147,7 @@ export const ConnectWalletDetailsModal = ({
|
||||
if (activeWallet?.walletId === walletIds.localWallet) {
|
||||
return (
|
||||
<>
|
||||
<View style={styles.currentNetwork}>
|
||||
<View style={styles.additionalActions}>
|
||||
<Text variant="bodySmallSecondary">
|
||||
{l.connect_wallet_details.additional_actions}
|
||||
</Text>
|
||||
@@ -267,14 +271,19 @@ export const ConnectWalletDetailsModal = ({
|
||||
</Text>
|
||||
</Box>
|
||||
) : null}
|
||||
<Box
|
||||
flexDirection="row"
|
||||
justifyContent="space-evenly"
|
||||
marginVertical="md"
|
||||
>
|
||||
<SendButton supportedTokens={supportedTokens} />
|
||||
<ReceiveButton />
|
||||
</Box>
|
||||
{hideReceiveButton && hideSendButton ? null : (
|
||||
<Box
|
||||
flexDirection="row"
|
||||
justifyContent="space-evenly"
|
||||
gap="xs"
|
||||
marginTop="md"
|
||||
>
|
||||
{hideSendButton ? null : (
|
||||
<SendButton supportedTokens={supportedTokens} />
|
||||
)}
|
||||
{hideReceiveButton ? null : <ReceiveButton />}
|
||||
</Box>
|
||||
)}
|
||||
<View style={styles.currentNetwork}>
|
||||
<Text variant="bodySmallSecondary">
|
||||
{l.connect_wallet_details.current_network}
|
||||
@@ -353,6 +362,15 @@ const styles = StyleSheet.create({
|
||||
minWidth: 200,
|
||||
},
|
||||
currentNetwork: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "flex-start",
|
||||
marginBottom: 8,
|
||||
marginTop: 28,
|
||||
},
|
||||
additionalActions: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignContent: "flex-start",
|
||||
|
||||
@@ -42,6 +42,20 @@ export type ConnectWalletDetailsProps = {
|
||||
* The default is `false`
|
||||
*/
|
||||
hideSwitchToPersonalWallet?: boolean;
|
||||
|
||||
/**
|
||||
* Option to hide the Send button in the wallet details modal.
|
||||
*
|
||||
* The default is `false`
|
||||
*/
|
||||
hideSendButton?: boolean;
|
||||
|
||||
/**
|
||||
* Option to hide the Receive button in the wallet details modal.
|
||||
*
|
||||
* The default is `false`
|
||||
*/
|
||||
hideReceiveButton?: boolean;
|
||||
};
|
||||
|
||||
export const WalletDetailsButton = ({
|
||||
@@ -49,6 +63,8 @@ export const WalletDetailsButton = ({
|
||||
detailsButton,
|
||||
extraRows,
|
||||
hideTestnetFaucet,
|
||||
hideReceiveButton,
|
||||
hideSendButton,
|
||||
supportedTokens,
|
||||
displayBalanceToken,
|
||||
hideSwitchToPersonalWallet,
|
||||
@@ -84,6 +100,8 @@ export const WalletDetailsButton = ({
|
||||
supportedTokens={supportedTokens}
|
||||
displayBalanceToken={displayBalanceToken}
|
||||
hideSwitchToPersonalWallet={hideSwitchToPersonalWallet}
|
||||
hideReceiveButton={hideReceiveButton}
|
||||
hideSendButton={hideSendButton}
|
||||
/>
|
||||
{detailsButton ? (
|
||||
detailsButton({ onPress })
|
||||
|
||||
@@ -33,7 +33,6 @@ export const ReceiveButton = () => {
|
||||
<>
|
||||
<IconTextButton
|
||||
flex={1}
|
||||
ml="xs"
|
||||
text={l.common.receive}
|
||||
justifyContent="center"
|
||||
icon={
|
||||
|
||||
@@ -22,7 +22,7 @@ import { useGlobalTheme, useUIContext } from "../providers/ui-context-provider";
|
||||
|
||||
export type ActionFn = (contract: SmartContract) => Promise<any>;
|
||||
|
||||
interface Web3ButtonProps<TActionFn extends ActionFn> {
|
||||
export interface Web3ButtonProps<TActionFn extends ActionFn> {
|
||||
contractAddress: `0x${string}` | `${string}.eth` | string;
|
||||
contractAbi?: ContractInterface;
|
||||
|
||||
|
||||
@@ -38,7 +38,11 @@ export {
|
||||
ConnectWallet,
|
||||
type ConnectWalletProps,
|
||||
} from "./components/ConnectWallet";
|
||||
export { Web3Button, type ActionFn } from "./components/Web3Button";
|
||||
export {
|
||||
Web3Button,
|
||||
type Web3ButtonProps,
|
||||
type ActionFn,
|
||||
} from "./components/Web3Button";
|
||||
|
||||
// utilities
|
||||
export * from "./utils/uri";
|
||||
@@ -61,11 +65,19 @@ export { ConnectWalletHeader } from "./components/ConnectWalletFlow/ConnectingWa
|
||||
export * from "./assets";
|
||||
|
||||
// providers
|
||||
// export * from "./providers/full";
|
||||
export * from "./providers/thirdweb-provider";
|
||||
export {
|
||||
ThirdwebProvider,
|
||||
type DefaultChains,
|
||||
type ThirdwebProviderProps,
|
||||
} from "./providers/thirdweb-provider";
|
||||
|
||||
// re-export everything from react-core
|
||||
export * from "@thirdweb-dev/react-core";
|
||||
|
||||
// ThirdwebSDK RN
|
||||
export { ThirdwebSDK } from "./sdk/ThirdwebSDK";
|
||||
|
||||
export {
|
||||
type SupportedTokens,
|
||||
defaultTokens,
|
||||
} from "./components/SendFunds/defaultTokens";
|
||||
|
||||
@@ -18,7 +18,7 @@ import { walletIds } from "@thirdweb-dev/wallets";
|
||||
import { ThirdwebStorage } from "../../core/storage/storage";
|
||||
import type { Locale } from "../i18n/types";
|
||||
|
||||
interface ThirdwebProviderProps<TChains extends Chain[]>
|
||||
export interface ThirdwebProviderProps<TChains extends Chain[]>
|
||||
extends Omit<
|
||||
ThirdwebProviderCoreProps<TChains>,
|
||||
"supportedWallets" | "secretKey" | "signer" | "theme"
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 4.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`d00ca7fd`](https://github.com/thirdweb-dev/js/commit/d00ca7fd2a0fa3e694bb37d1f486d3d4cf1ad79e)]:
|
||||
- @thirdweb-dev/payments@1.0.1
|
||||
- @thirdweb-dev/react-core@4.2.1
|
||||
|
||||
## 4.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1913](https://github.com/thirdweb-dev/js/pull/1913) [`ed3afd41`](https://github.com/thirdweb-dev/js/commit/ed3afd418cb6e0d711814a1c52afb302460751ba) Thanks [@IDubuque](https://github.com/IDubuque)! - Migrated `@paperxyz/react-client-sdk` into `@thirdweb-dev/react` to provide checkout components under the thirdweb react sdk. The `@paperxyz/react-client-sdk` package will be continued within the `@thirdweb-dev/react` package, aligning with the new branding and expanded capabilities under thirdweb. It inherits the core functionality and components of Paper's developer-first NFT checkout solution.
|
||||
|
||||
### Added
|
||||
|
||||
- Integrated the `@paper-xyz/react-client-sdk` components into `@thirdweb-dev/react`. This update brings all the Paper.xyz React components into the thirdweb ecosystem, providing a seamless developer experience for building NFT-powered applications.
|
||||
- New components from Paper.xyz for NFT checkouts, enhancing the existing thirdweb SDK capabilities.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b808e942`](https://github.com/thirdweb-dev/js/commit/b808e942fdaa81b5de61dcb3f2083081cb99ea11), [`e46dd025`](https://github.com/thirdweb-dev/js/commit/e46dd0254f157c3c4d50fe3e4f44e2e24eaf34a8), [`ed3afd41`](https://github.com/thirdweb-dev/js/commit/ed3afd418cb6e0d711814a1c52afb302460751ba)]:
|
||||
- @thirdweb-dev/sdk@4.0.24
|
||||
- @thirdweb-dev/wallets@2.3.3
|
||||
- @thirdweb-dev/payments@1.0.0
|
||||
- @thirdweb-dev/react-core@4.2.0
|
||||
|
||||
## 4.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "4.1.17",
|
||||
"version": "4.2.1",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -110,6 +110,8 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@emotion/css": "11.10.5",
|
||||
"@headlessui/react": "1.7.6",
|
||||
"@google/model-viewer": "^2.1.1",
|
||||
"@radix-ui/colors": "^0.1.9",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
@@ -122,8 +124,10 @@
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@tanstack/react-query": "^4.33.0",
|
||||
"@thirdweb-dev/chains": "workspace:*",
|
||||
"@thirdweb-dev/sdk": "workspace:*",
|
||||
"@thirdweb-dev/react-core": "workspace:*",
|
||||
"@thirdweb-dev/wallets": "workspace:*",
|
||||
"@thirdweb-dev/payments": "workspace:*",
|
||||
"buffer": "^6.0.3",
|
||||
"copy-to-clipboard": "^3.3.2",
|
||||
"detect-browser": "^5.3.0",
|
||||
|
||||
@@ -113,3 +113,4 @@ export type {
|
||||
|
||||
// at the moment we'll re-export everything from the evm package
|
||||
export * from "./evm";
|
||||
export * from "./payments";
|
||||
@@ -0,0 +1,54 @@
|
||||
// UNCHANGED
|
||||
import type { SupportedChainName } from "@thirdweb-dev/payments";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import React, { createContext, useContext, useMemo, useState } from "react";
|
||||
|
||||
interface SDKContext {
|
||||
chainName: SupportedChainName;
|
||||
setChainName: Dispatch<SetStateAction<SupportedChainName>>;
|
||||
clientId: string;
|
||||
appName: string;
|
||||
}
|
||||
|
||||
const defaultContextValue: SDKContext = {
|
||||
chainName: "Polygon",
|
||||
setChainName: () => {},
|
||||
clientId: "",
|
||||
appName: "",
|
||||
};
|
||||
|
||||
const PaymentsSDKContext = /* @__PURE__ */ createContext(defaultContextValue);
|
||||
|
||||
export interface PaymentsProviderProps {
|
||||
chainName?: SupportedChainName;
|
||||
appName?: string;
|
||||
clientId?: string;
|
||||
}
|
||||
|
||||
export const PaymentsSDKProvider = ({
|
||||
appName = "",
|
||||
chainName = "Polygon",
|
||||
clientId = "",
|
||||
children,
|
||||
}: React.PropsWithChildren<PaymentsProviderProps>) => {
|
||||
const [chainName_, setChainName] = useState<SupportedChainName>(chainName);
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
chainName: chainName_,
|
||||
setChainName,
|
||||
appName: appName,
|
||||
clientId: clientId,
|
||||
}),
|
||||
[chainName_, appName, clientId],
|
||||
);
|
||||
|
||||
return (
|
||||
<PaymentsSDKContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</PaymentsSDKContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePaymentsSDKContext = (): SDKContext => {
|
||||
return useContext(PaymentsSDKContext);
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// UNCHANGED
|
||||
declare module '*.png';
|
||||
declare module '*.webp';
|
||||
declare module '*.jpg';
|
||||
declare module '*.jpeg';
|
||||
declare module '*.svg';
|
||||
declare module '*.gif';
|
||||
declare module '*.css';
|
||||
@@ -0,0 +1,114 @@
|
||||
// CHANGED: Import packageJSON + sdk-version only
|
||||
import type {
|
||||
ICheckoutWithCardConfigs,
|
||||
PaymentsSDKError,
|
||||
PriceSummary,
|
||||
ReviewResult,
|
||||
ICustomizationOptions,
|
||||
Locale,
|
||||
} from "@thirdweb-dev/payments";
|
||||
import {
|
||||
createCheckoutWithCardElement,
|
||||
DEFAULT_BRAND_OPTIONS,
|
||||
} from "@thirdweb-dev/payments";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { usePaymentsSDKContext } from "../Provider";
|
||||
import { iframeContainer } from "../lib/utils/styles";
|
||||
import { SpinnerWrapper } from "./common/SpinnerWrapper";
|
||||
|
||||
import packageJson from "../../../package.json";
|
||||
|
||||
interface CheckoutWithCardProps {
|
||||
clientId?: string;
|
||||
sdkClientSecret?: string;
|
||||
onPaymentSuccess: (result: { transactionId: string }) => void;
|
||||
appName?: string;
|
||||
options?: ICustomizationOptions;
|
||||
onReview?: (result: ReviewResult) => void;
|
||||
onError?: (error: PaymentsSDKError) => void;
|
||||
onBeforeModalOpen?: (props: { url: string }) => void;
|
||||
onPriceUpdate?: (priceSummary: PriceSummary) => void;
|
||||
configs?: ICheckoutWithCardConfigs;
|
||||
|
||||
/**
|
||||
* @deprecated No longer used.
|
||||
*/
|
||||
experimentalUseAltDomain?: boolean;
|
||||
|
||||
/**
|
||||
* Sets the locale to a supported language.
|
||||
* NOTE: Localization is in early alpha and many languages are not yet supported.
|
||||
*
|
||||
* Defaults to English.
|
||||
*/
|
||||
locale?: Locale;
|
||||
}
|
||||
|
||||
export const CheckoutWithCard = ({
|
||||
clientId,
|
||||
sdkClientSecret,
|
||||
appName,
|
||||
options = {
|
||||
...DEFAULT_BRAND_OPTIONS,
|
||||
},
|
||||
onPaymentSuccess,
|
||||
onReview,
|
||||
onError,
|
||||
onBeforeModalOpen,
|
||||
onPriceUpdate,
|
||||
locale,
|
||||
configs,
|
||||
}: CheckoutWithCardProps): React.ReactElement => {
|
||||
const { appName: appNameContext } = usePaymentsSDKContext();
|
||||
const [isCardDetailIframeLoading, setIsCardDetailIframeLoading] =
|
||||
useState<boolean>(true);
|
||||
const onCardDetailLoad = useCallback(() => {
|
||||
setIsCardDetailIframeLoading(false);
|
||||
}, []);
|
||||
const CheckoutWithCardIframeContainerRef = useRef<HTMLDivElement>(null);
|
||||
const appNameToUse = appName || appNameContext;
|
||||
|
||||
// force hide spinner if iframe taking too long
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsCardDetailIframeLoading(false);
|
||||
}, 1000);
|
||||
return () => {
|
||||
clearTimeout(timer); // Clear the timer if the component unmounts before the delay
|
||||
};
|
||||
}, []);
|
||||
// Handle message events from the popup. Pass along the message to the iframe as well
|
||||
useEffect(() => {
|
||||
if (!CheckoutWithCardIframeContainerRef.current) {
|
||||
return;
|
||||
}
|
||||
createCheckoutWithCardElement({
|
||||
clientId,
|
||||
sdkClientSecret,
|
||||
appName: appNameToUse,
|
||||
elementOrId: CheckoutWithCardIframeContainerRef.current,
|
||||
locale,
|
||||
onError,
|
||||
onLoad: onCardDetailLoad,
|
||||
onPaymentSuccess,
|
||||
onReview,
|
||||
onBeforeModalOpen,
|
||||
onPriceUpdate,
|
||||
options,
|
||||
configs,
|
||||
});
|
||||
}, [CheckoutWithCardIframeContainerRef.current]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={iframeContainer}
|
||||
ref={CheckoutWithCardIframeContainerRef}
|
||||
// Label the package version.
|
||||
data-thirdweb-sdk-version={`${packageJson.name}@${packageJson.version}`}
|
||||
>
|
||||
{isCardDetailIframeLoading && <SpinnerWrapper />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
// UNCHANGED
|
||||
import { VerifyOwnershipWithPaper } from "./VerifyOwnershipWithPaper";
|
||||
|
||||
export { VerifyOwnershipWithPaper as LoginWithPaper };
|
||||
@@ -0,0 +1,490 @@
|
||||
// CHANGED: imports only
|
||||
import { PAPER_APP_URL, DEFAULT_BRAND_OPTIONS } from "@thirdweb-dev/payments";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import type {
|
||||
ContractType,
|
||||
CustomContractArgWrapper,
|
||||
ReadMethodCallType,
|
||||
WriteMethodCallType,
|
||||
} from "../interfaces/CustomContract";
|
||||
import { fetchCustomContractArgsFromProps } from "../interfaces/CustomContract";
|
||||
import type { PaymentSuccessResult } from "../interfaces/PaymentSuccessResult";
|
||||
import type { TransferSuccessResult } from "../interfaces/TransferSuccessResult";
|
||||
import { openCenteredPopup } from "../lib/utils/popup";
|
||||
|
||||
export enum PaperCheckoutDisplay {
|
||||
/**
|
||||
* Open the checkout in a new popup centered over the parent window.
|
||||
*/
|
||||
POPUP = "POPUP",
|
||||
|
||||
/**
|
||||
* Open the checkout in a new browser tab.
|
||||
*/
|
||||
NEW_TAB = "NEW_TAB",
|
||||
|
||||
/**
|
||||
* Open the checkout in a modal on the parent page with a darkened background.
|
||||
*
|
||||
* NOTE: Pay with Crypto is disabled in this display mode.
|
||||
*/
|
||||
MODAL = "MODAL",
|
||||
|
||||
/**
|
||||
* Open the checkout in a drawer on the right side of the parent page with a darkened background.
|
||||
*
|
||||
* NOTE: Pay with Crypto is disabled in this display mode.
|
||||
*/
|
||||
DRAWER = "DRAWER",
|
||||
|
||||
/**
|
||||
* Embed the checkout directly on the parent page.
|
||||
*
|
||||
* NOTE: Pay with Crypto is disabled in this display mode.
|
||||
*/
|
||||
EMBED = "EMBED",
|
||||
}
|
||||
|
||||
export type PaperCheckoutProps<T extends ContractType> =
|
||||
CustomContractArgWrapper<
|
||||
{
|
||||
checkoutId: string;
|
||||
display?: PaperCheckoutDisplay;
|
||||
recipientWalletAddress?: string;
|
||||
emailAddress?: string;
|
||||
quantity?: number;
|
||||
mintMethod?: WriteMethodCallType;
|
||||
eligibilityMethod?: ReadMethodCallType;
|
||||
metadata?: Record<string, any>;
|
||||
appName?: string;
|
||||
onOpenCheckout?: () => void;
|
||||
onCloseCheckout?: () => void;
|
||||
onPaymentSuccess?: (result: PaymentSuccessResult) => void;
|
||||
onTransferSuccess?: (result: TransferSuccessResult) => void;
|
||||
options?: {
|
||||
width: number;
|
||||
height: number;
|
||||
colorPrimary: string;
|
||||
colorBackground: string;
|
||||
colorText: string;
|
||||
borderRadius: number;
|
||||
fontFamily: string;
|
||||
};
|
||||
children?: React.ReactNode;
|
||||
},
|
||||
T
|
||||
>;
|
||||
|
||||
export const PaperCheckout = <T extends ContractType>({
|
||||
checkoutId,
|
||||
display = PaperCheckoutDisplay.POPUP,
|
||||
recipientWalletAddress,
|
||||
emailAddress,
|
||||
quantity,
|
||||
eligibilityMethod,
|
||||
mintMethod,
|
||||
metadata,
|
||||
appName,
|
||||
options = {
|
||||
width: 400,
|
||||
height: 800,
|
||||
...DEFAULT_BRAND_OPTIONS,
|
||||
},
|
||||
onOpenCheckout,
|
||||
onCloseCheckout,
|
||||
onPaymentSuccess,
|
||||
onTransferSuccess,
|
||||
children,
|
||||
...props
|
||||
}: PaperCheckoutProps<T>) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const { contractType, contractArgs } =
|
||||
fetchCustomContractArgsFromProps(props);
|
||||
|
||||
// Handle message events from iframe.
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const data = event.data;
|
||||
|
||||
switch (data.eventType) {
|
||||
case "paymentSuccess":
|
||||
if (onPaymentSuccess) {
|
||||
onPaymentSuccess({ id: data.id });
|
||||
}
|
||||
break;
|
||||
|
||||
case "transferSuccess":
|
||||
if (onTransferSuccess) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
onTransferSuccess({
|
||||
id: data.id,
|
||||
// ...
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "modalClosed":
|
||||
// Emitted when the iframe decides to close, such as when the purchase is completed.
|
||||
setIsOpen(false);
|
||||
if (onCloseCheckout) {
|
||||
onCloseCheckout();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore unrecognized event
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Build iframe URL with query params.
|
||||
const checkoutUrl = new URL(`/checkout/${checkoutId}`, PAPER_APP_URL);
|
||||
|
||||
checkoutUrl.searchParams.append("display", display);
|
||||
const mintMethodStringified = JSON.stringify(mintMethod);
|
||||
const eligibilityMethodStringified = JSON.stringify(eligibilityMethod);
|
||||
const metadataStringified = JSON.stringify(metadata);
|
||||
const contractArgsStringified = JSON.stringify(contractArgs);
|
||||
|
||||
if (options.colorPrimary) {
|
||||
checkoutUrl.searchParams.append("colorPrimary", options.colorPrimary);
|
||||
}
|
||||
if (options.colorBackground) {
|
||||
checkoutUrl.searchParams.append("colorBackground", options.colorBackground);
|
||||
}
|
||||
if (options.colorText) {
|
||||
checkoutUrl.searchParams.append("colorText", options.colorText);
|
||||
}
|
||||
if (options.borderRadius !== undefined) {
|
||||
checkoutUrl.searchParams.append(
|
||||
"borderRadius",
|
||||
options.borderRadius.toString(),
|
||||
);
|
||||
}
|
||||
if (options.fontFamily) {
|
||||
checkoutUrl.searchParams.append("fontFamily", options.fontFamily);
|
||||
}
|
||||
if (mintMethod) {
|
||||
checkoutUrl.searchParams.append(
|
||||
"mintMethod",
|
||||
Buffer.from(mintMethodStringified, "utf-8").toString("base64"),
|
||||
);
|
||||
}
|
||||
if (eligibilityMethod) {
|
||||
checkoutUrl.searchParams.append(
|
||||
"eligibilityMethod",
|
||||
Buffer.from(eligibilityMethodStringified, "utf-8").toString("base64"),
|
||||
);
|
||||
}
|
||||
if (contractType) {
|
||||
checkoutUrl.searchParams.append("contractType", contractType);
|
||||
}
|
||||
if (contractArgs) {
|
||||
checkoutUrl.searchParams.append(
|
||||
"contractArgs",
|
||||
// Base 64 encode
|
||||
Buffer.from(contractArgsStringified, "utf-8").toString("base64"),
|
||||
);
|
||||
}
|
||||
if (appName) {
|
||||
checkoutUrl.searchParams.append("appName", appName);
|
||||
}
|
||||
if (recipientWalletAddress) {
|
||||
checkoutUrl.searchParams.append("wallet", recipientWalletAddress);
|
||||
}
|
||||
if (emailAddress) {
|
||||
checkoutUrl.searchParams.append("username", emailAddress);
|
||||
}
|
||||
if (quantity) {
|
||||
checkoutUrl.searchParams.append("quantity", quantity.toString());
|
||||
}
|
||||
if (metadata) {
|
||||
checkoutUrl.searchParams.append(
|
||||
"metadata",
|
||||
encodeURIComponent(metadataStringified),
|
||||
);
|
||||
}
|
||||
|
||||
// Default button if the app doesn't pass one in.
|
||||
const clickableElement = children || (
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: "#cf3781",
|
||||
padding: "8px 20px 8px 20px",
|
||||
borderRadius: "8px",
|
||||
color: "white",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
{"Buy Now"}
|
||||
</button>
|
||||
);
|
||||
|
||||
switch (display) {
|
||||
case PaperCheckoutDisplay.POPUP: {
|
||||
const onClick = () => {
|
||||
openCenteredPopup({
|
||||
url: checkoutUrl.href,
|
||||
h: options.height,
|
||||
w: options.width,
|
||||
win: window,
|
||||
target: "_blank",
|
||||
});
|
||||
|
||||
if (onOpenCheckout) {
|
||||
onOpenCheckout();
|
||||
}
|
||||
};
|
||||
return <a onClick={onClick}>{clickableElement}</a>;
|
||||
}
|
||||
|
||||
case PaperCheckoutDisplay.NEW_TAB: {
|
||||
const onClick = () => {
|
||||
window.open(checkoutUrl, "_blank");
|
||||
if (onOpenCheckout) {
|
||||
onOpenCheckout();
|
||||
}
|
||||
};
|
||||
return <a onClick={onClick}>{clickableElement}</a>;
|
||||
}
|
||||
|
||||
case PaperCheckoutDisplay.MODAL: {
|
||||
const onOpen = () => {
|
||||
setIsOpen(true);
|
||||
if (onOpenCheckout) {
|
||||
onOpenCheckout();
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
setIsOpen(false);
|
||||
if (onCloseCheckout) {
|
||||
onCloseCheckout();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PaperCheckoutModal
|
||||
clickableElement={clickableElement}
|
||||
checkoutUrl={checkoutUrl.href}
|
||||
width={options.width}
|
||||
height={options.height}
|
||||
isOpen={isOpen}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
case PaperCheckoutDisplay.DRAWER: {
|
||||
const onOpen = () => {
|
||||
setIsOpen(true);
|
||||
if (onOpenCheckout) {
|
||||
onOpenCheckout();
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
setIsOpen(false);
|
||||
if (onCloseCheckout) {
|
||||
onCloseCheckout();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PaperCheckoutDrawer
|
||||
clickableElement={clickableElement}
|
||||
checkoutUrl={checkoutUrl.href}
|
||||
width={options.width}
|
||||
isOpen={isOpen}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
case PaperCheckoutDisplay.EMBED: {
|
||||
return (
|
||||
<iframe
|
||||
src={checkoutUrl.href}
|
||||
width={options.width}
|
||||
height={options.height}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
default:
|
||||
console.error(`Invalid or unimplemented display type: ${display}`);
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
const inlineStyles: { [name: string]: any } = {
|
||||
overlay: {
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
margin: 0,
|
||||
zIndex: 1000,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
visibility: "hidden",
|
||||
opacity: 0,
|
||||
transition: "all 0.2s ease",
|
||||
},
|
||||
overlayIsVisible: {
|
||||
visibility: "visible",
|
||||
opacity: 1,
|
||||
background: "#0008",
|
||||
},
|
||||
modalOverlay: {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
drawerOverlay: {
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
modalDialog: {
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
borderRadius: "8px",
|
||||
visibility: "hidden",
|
||||
opacity: 0,
|
||||
top: "5%",
|
||||
transition: "all 0.2s ease",
|
||||
maxWidth: "100vw",
|
||||
},
|
||||
modalDialogIsVisible: {
|
||||
visibility: "visible",
|
||||
opacity: 1,
|
||||
top: 0,
|
||||
},
|
||||
drawerDialog: {
|
||||
position: "relative",
|
||||
visibility: "hidden",
|
||||
opacity: 0,
|
||||
right: "-10%",
|
||||
transition: "all 0.2s ease",
|
||||
},
|
||||
drawerDialogIsVisible: {
|
||||
visibility: "visible",
|
||||
opacity: 1,
|
||||
right: 0,
|
||||
},
|
||||
modalCloseButton: {
|
||||
position: "absolute",
|
||||
top: "0.1em",
|
||||
right: "0.2em",
|
||||
borderRadius: "8px",
|
||||
fontSize: "x-large",
|
||||
padding: "0 0.4em",
|
||||
color: "#888",
|
||||
},
|
||||
};
|
||||
|
||||
const PaperCheckoutDrawer = ({
|
||||
clickableElement,
|
||||
checkoutUrl,
|
||||
width,
|
||||
isOpen,
|
||||
onOpen,
|
||||
onClose,
|
||||
}: {
|
||||
clickableElement: React.ReactNode;
|
||||
checkoutUrl: string;
|
||||
width: number;
|
||||
isOpen: boolean;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<a onClick={onOpen}>{clickableElement}</a>
|
||||
|
||||
<div
|
||||
className="paper-overlay"
|
||||
style={{
|
||||
...inlineStyles.overlay,
|
||||
...(isOpen ? inlineStyles.overlayIsVisible : {}),
|
||||
...inlineStyles.drawerOverlay,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="paper-drawer"
|
||||
style={{
|
||||
...inlineStyles.drawerDialog,
|
||||
...(isOpen ? inlineStyles.drawerDialogIsVisible : {}),
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<button onClick={onClose} style={inlineStyles.modalCloseButton}>
|
||||
×
|
||||
</button>
|
||||
{isOpen && <iframe src={checkoutUrl} width="100%" height="100%" />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PaperCheckoutModal = ({
|
||||
clickableElement,
|
||||
checkoutUrl,
|
||||
width,
|
||||
height,
|
||||
isOpen,
|
||||
onOpen,
|
||||
onClose,
|
||||
}: {
|
||||
clickableElement: React.ReactNode;
|
||||
checkoutUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
isOpen: boolean;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
// Set the max height to the window's inner height.
|
||||
// This method handles mobile browser heights more reliably than 100vh.
|
||||
const [innerHeight, setInnerHeight] = useState<number>(height);
|
||||
useEffect(() => setInnerHeight(window.innerHeight), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<a onClick={onOpen}>{clickableElement}</a>
|
||||
|
||||
<div
|
||||
className="paper-overlay"
|
||||
style={{
|
||||
...inlineStyles.overlay,
|
||||
...(isOpen ? inlineStyles.overlayIsVisible : {}),
|
||||
...inlineStyles.modalOverlay,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="paper-modal"
|
||||
style={{
|
||||
...inlineStyles.modalDialog,
|
||||
...(isOpen ? inlineStyles.modalDialogIsVisible : {}),
|
||||
width,
|
||||
height,
|
||||
maxHeight: innerHeight,
|
||||
}}
|
||||
>
|
||||
<button onClick={onClose} style={inlineStyles.modalCloseButton}>
|
||||
×
|
||||
</button>
|
||||
{isOpen && <iframe src={checkoutUrl} width="100%" height="100%" />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
// CHANGED: packageJson import + sdk-version + clientId
|
||||
import { css } from "@emotion/css";
|
||||
import type {
|
||||
PaymentsSDKError,
|
||||
SupportedChainName,
|
||||
} from "@thirdweb-dev/payments";
|
||||
import { PAPER_APP_URL, PaymentsSDKErrorCode } from "@thirdweb-dev/payments";
|
||||
import React, { useEffect } from "react";
|
||||
import { usePaymentsSDKContext } from "../Provider";
|
||||
import { openCenteredPopup } from "../lib/utils/popup";
|
||||
import { Button } from "./common/Button";
|
||||
|
||||
import packageJson from "../../../package.json";
|
||||
|
||||
interface VerifyOwnershipWithPaperProps {
|
||||
onSuccess?: (code: string) => void;
|
||||
onError?: (error: PaymentsSDKError) => void;
|
||||
onWindowClose?: () => void;
|
||||
children?: ({
|
||||
onClick,
|
||||
}: {
|
||||
onClick: () => void;
|
||||
}) => React.ReactNode | React.ReactNode;
|
||||
redirectUrl?: string;
|
||||
className?: string;
|
||||
chainName?: SupportedChainName;
|
||||
clientId?: string;
|
||||
}
|
||||
|
||||
const enum VERIFY_OWNERSHIP_WITH_PAPER_EVENT_TYPE {
|
||||
USER_LOGIN_SUCCESS = "userLoginSuccess",
|
||||
USER_LOGIN_FAILED = "userLoginFailed",
|
||||
USER_CLOSE_LOGIN_PAGE = "userCloseLoginPage",
|
||||
}
|
||||
|
||||
export const VerifyOwnershipWithPaper: React.FC<
|
||||
VerifyOwnershipWithPaperProps
|
||||
> = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
onWindowClose,
|
||||
className,
|
||||
children,
|
||||
redirectUrl,
|
||||
chainName,
|
||||
clientId,
|
||||
}) => {
|
||||
const { chainName: chainNameContext, clientId: clientIdContext } =
|
||||
usePaymentsSDKContext();
|
||||
const isChildrenFunction = typeof children === "function";
|
||||
|
||||
const chainNameToUse = chainName || chainNameContext;
|
||||
const clientIdToUse = clientId || clientIdContext;
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const data = event.data;
|
||||
switch (data.eventType) {
|
||||
case VERIFY_OWNERSHIP_WITH_PAPER_EVENT_TYPE.USER_LOGIN_SUCCESS:
|
||||
if (onSuccess) {
|
||||
onSuccess(data.values.accessCode);
|
||||
}
|
||||
break;
|
||||
case VERIFY_OWNERSHIP_WITH_PAPER_EVENT_TYPE.USER_CLOSE_LOGIN_PAGE: {
|
||||
if (onWindowClose) {
|
||||
onWindowClose();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VERIFY_OWNERSHIP_WITH_PAPER_EVENT_TYPE.USER_LOGIN_FAILED:
|
||||
if (onError) {
|
||||
onError({
|
||||
code: PaymentsSDKErrorCode.UserLoginFailed,
|
||||
error: new Error(PaymentsSDKErrorCode.UserLoginFailed),
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Ignore unrecognized event
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const url = new URL("/sdk/v1/login-with-paper", PAPER_APP_URL);
|
||||
url.searchParams.append("chainName", chainNameToUse);
|
||||
url.searchParams.append("clientId", clientIdToUse);
|
||||
url.searchParams.append("redirectUrl", redirectUrl || "");
|
||||
const onClick = () => {
|
||||
const loginWindow = openCenteredPopup({
|
||||
url: url.href,
|
||||
target: "_blank",
|
||||
win: window,
|
||||
w: 400,
|
||||
h: 600,
|
||||
});
|
||||
loginWindow?.focus();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{children && isChildrenFunction ? (
|
||||
children({ onClick })
|
||||
) : children ? (
|
||||
<a
|
||||
onClick={onClick}
|
||||
data-thirdweb-sdk-version={`${packageJson.name}@${packageJson.version}`}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
) : (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
data-thirdweb-sdk-version={`${packageJson.name}@${packageJson.version}`}
|
||||
>
|
||||
<div
|
||||
className={css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
<span style={{ marginRight: "8px" }}>{"Login with"}</span>{" "}
|
||||
<svg
|
||||
width="15"
|
||||
height="30"
|
||||
viewBox="0 0 26 49"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M25.8824 8.91421C25.8824 8.47803 25.5996 8.09218 25.1838 7.96071L0 0V11.7963L14.7899 16.276L2.77311 24.5V34.4815L5.58074 37.3767L3.1009 39.6283C2.89214 39.8178 2.77311 40.0867 2.77311 40.3687V49L11.0924 42.6481V32.6667L8.60325 30.5207L25.8824 19.963V8.91421Z"
|
||||
fill="#19A8D6"
|
||||
/>
|
||||
<path
|
||||
d="M25.8824 8.91421C25.8824 8.47803 25.5996 8.09218 25.1838 7.96071L0 0V11.7963L25.8824 19.963V8.91421Z"
|
||||
fill="#39D0FF"
|
||||
/>
|
||||
<path
|
||||
d="M11.0924 32.6667L2.77311 24.5V34.4815L11.0924 42.6481V32.6667Z"
|
||||
fill="#39D0FF"
|
||||
/>
|
||||
</svg>{" "}
|
||||
<span style={{ marginLeft: "5px" }}>{"Paper"}</span>
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
// Unchanged
|
||||
import { css, cx } from "@emotion/css";
|
||||
import React from "react";
|
||||
import { Spinner } from "./Spinner";
|
||||
|
||||
export const Button = ({
|
||||
isLoading = false,
|
||||
loadingText = "",
|
||||
...props
|
||||
}: {
|
||||
isLoading?: boolean;
|
||||
loadingText?: string;
|
||||
} & React.HTMLProps<HTMLButtonElement>): React.ReactElement => {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
type={props.type as "button" | "submit" | "reset" | undefined}
|
||||
disabled={isLoading || props.disabled}
|
||||
className={cx(buttonClass, props.className)}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div
|
||||
className={css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
`}
|
||||
>
|
||||
<Spinner
|
||||
className={
|
||||
loadingText
|
||||
? css`
|
||||
margin-right: 0.5rem;
|
||||
`
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
{loadingText}
|
||||
</div>
|
||||
) : (
|
||||
props.children
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const buttonClass = css`
|
||||
padding-top: 0.625rem;
|
||||
padding-bottom: 0.625rem;
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
background-color: #1f2937;
|
||||
color: #ffffff;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
font-weight: 600;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
border-radius: 0.5rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
`;
|
||||
@@ -0,0 +1,13 @@
|
||||
// Unchanged
|
||||
import React, { forwardRef, memo } from "react";
|
||||
|
||||
const IFrameBase = /* @__PURE__ */ forwardRef(
|
||||
(
|
||||
props: React.IframeHTMLAttributes<HTMLIFrameElement>,
|
||||
ref: React.ForwardedRef<HTMLIFrameElement>,
|
||||
) => {
|
||||
return <iframe ref={ref} {...props} />;
|
||||
},
|
||||
);
|
||||
IFrameBase.displayName = "IFrameBase";
|
||||
export const IFrameWrapper = /* @__PURE__ */ memo(IFrameBase);
|
||||
@@ -0,0 +1,209 @@
|
||||
// UNCHANGED
|
||||
import { css, cx } from "@emotion/css";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useEffect } from "react";
|
||||
import { opacity0, opacity1 } from "../../lib/utils/styles";
|
||||
|
||||
export const Modal: React.FC<{
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
escapeToClose?: boolean;
|
||||
clickOutsideModalToClose?: boolean;
|
||||
bgColor?: string;
|
||||
isFullScreen?: boolean;
|
||||
hasCloseButton?: boolean;
|
||||
children: React.ReactNode;
|
||||
}> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
escapeToClose = true,
|
||||
clickOutsideModalToClose = false,
|
||||
bgColor = "#FAFAFA",
|
||||
isFullScreen,
|
||||
hasCloseButton = true,
|
||||
children,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
if (escapeToClose) {
|
||||
document.addEventListener("keydown", keyDownHandler);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", keyDownHandler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const additionalDialogClasses = isFullScreen ? "" : dialogContainedClasses;
|
||||
const dialogPanelBg = isFullScreen ? "transparent" : bgColor;
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className={cx(
|
||||
"paper-modal",
|
||||
css`
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
`,
|
||||
)}
|
||||
onClose={clickOutsideModalToClose ? onClose : () => {}}
|
||||
>
|
||||
{/* Overlay */}
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter={enterClasses}
|
||||
enterFrom={opacity0}
|
||||
enterTo={opacity1}
|
||||
leave={leaveClasses}
|
||||
leaveFrom={opacity1}
|
||||
leaveTo={opacity0}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
"paper-modal-overlay",
|
||||
css`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
`,
|
||||
)}
|
||||
/>
|
||||
</Transition.Child>
|
||||
|
||||
<div
|
||||
className={css`
|
||||
overflow-y: auto;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100%;
|
||||
`}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter={enterClasses}
|
||||
enterFrom={cx(opacity0, scale95)}
|
||||
enterTo={cx(opacity1, scale100)}
|
||||
leave={leaveClasses}
|
||||
leaveFrom={cx(opacity1, scale100)}
|
||||
leaveTo={cx(opacity0, scale95)}
|
||||
>
|
||||
<Dialog.Panel
|
||||
className={cx(
|
||||
"paper-modal-content",
|
||||
dialogClasses,
|
||||
additionalDialogClasses,
|
||||
)}
|
||||
style={{ backgroundColor: dialogPanelBg }}
|
||||
>
|
||||
{hasCloseButton && <CloseButton onClose={onClose} />}
|
||||
{children}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
const CloseButton: React.FC<{ onClose: () => void }> = ({ onClose }) => {
|
||||
return (
|
||||
<button
|
||||
aria-label="close modal"
|
||||
className={css`
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: rgba(160, 174, 192, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: rgba(160, 174, 192, 0.2);
|
||||
}
|
||||
`}
|
||||
>
|
||||
<svg
|
||||
className={css`
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
`}
|
||||
onClick={onClose}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const enterClasses = css`
|
||||
transition-duration: 300ms;
|
||||
transition-timing-function: ease-out;
|
||||
`;
|
||||
|
||||
const leaveClasses = css`
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease-in;
|
||||
`;
|
||||
|
||||
const scale95 = css`
|
||||
transform: scale(0.95);
|
||||
`;
|
||||
|
||||
const scale100 = css`
|
||||
transform: scale(1);
|
||||
`;
|
||||
|
||||
const dialogClasses = css`
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
position: relative;
|
||||
transition-property: all;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
`;
|
||||
|
||||
const dialogContainedClasses = css`
|
||||
padding: 1.25rem;
|
||||
|
||||
@media (min-width: 640px) {
|
||||
margin: 1rem;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,30 @@
|
||||
// UNCHANGED
|
||||
import { css, cx, keyframes } from "@emotion/css";
|
||||
import React from "react";
|
||||
|
||||
export const Spinner = ({
|
||||
className,
|
||||
}: {
|
||||
className?: string;
|
||||
}): React.ReactElement => {
|
||||
return <div id="loader" className={cx(loader, className)}></div>;
|
||||
};
|
||||
|
||||
const Spin = keyframes`
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`;
|
||||
|
||||
const loader = css`
|
||||
border: 0.15rem solid #f3f3f300;
|
||||
border-top: 0.15rem solid #000000;
|
||||
border-left: 0.15rem solid #000000;
|
||||
border-radius: 50%;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
animation: ${Spin} 1s linear infinite;
|
||||
`;
|
||||
@@ -0,0 +1,24 @@
|
||||
// UNCHANGED
|
||||
import { css } from "@emotion/css";
|
||||
import { Spinner } from "./Spinner";
|
||||
|
||||
export const SpinnerWrapper: React.FC = () => {
|
||||
return (
|
||||
<div className={wrapperStyle}>
|
||||
<Spinner className={spinnerStyle} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const wrapperStyle = css`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
`;
|
||||
|
||||
const spinnerStyle = css`
|
||||
color: #000000;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
`;
|
||||
@@ -0,0 +1,22 @@
|
||||
// UNCHANGED
|
||||
export {
|
||||
PaymentsSDKErrorCode,
|
||||
PayWithCryptoErrorCode,
|
||||
} from "@thirdweb-dev/payments";
|
||||
export type {
|
||||
PaymentsSDKError,
|
||||
PaperUser,
|
||||
Locale,
|
||||
} from "@thirdweb-dev/payments";
|
||||
// re-export types and enums
|
||||
export * from "./Provider";
|
||||
export * from "./components/CheckoutWithCard";
|
||||
export * from "./components/LoginWithPaper";
|
||||
export * from "./components/PaperCheckout";
|
||||
export * from "./components/VerifyOwnershipWithPaper";
|
||||
export type {
|
||||
ReadMethodCallType,
|
||||
WriteMethodCallType,
|
||||
} from "./interfaces/CustomContract";
|
||||
export * from "./interfaces/PaymentSuccessResult";
|
||||
export * from "./interfaces/TransferSuccessResult";
|
||||
@@ -0,0 +1,136 @@
|
||||
// UNCHANGED
|
||||
export type SignedPayload = {
|
||||
payload: { [key: string]: any };
|
||||
signature: string;
|
||||
};
|
||||
|
||||
export enum ContractType {
|
||||
// Ethereum/Polygon
|
||||
THIRDWEB_NFT_DROP_V2 = "THIRDWEB_NFT_DROP_V2",
|
||||
THIRDWEB_EDITION_DROP_V2 = "THIRDWEB_EDITION_DROP_V2",
|
||||
THIRDWEB_SIGNATURE = "THIRDWEB_SIGNATURE",
|
||||
// Solana
|
||||
CANDY_MACHINE = "CANDY_MACHINE",
|
||||
AUCTION_HOUSE = "AUCTION_HOUSE",
|
||||
}
|
||||
|
||||
export type CustomContractArgWrapper<props, T extends ContractType> =
|
||||
| props
|
||||
| (props & {
|
||||
contractType: T;
|
||||
contractArgs: T extends ContractType.AUCTION_HOUSE
|
||||
? {
|
||||
mintAddress: string;
|
||||
tokenAccount: string;
|
||||
sellerWalletAddress: string;
|
||||
price: {
|
||||
amount: number;
|
||||
currency: "SOL" | "USDC";
|
||||
};
|
||||
}
|
||||
: T extends ContractType.THIRDWEB_SIGNATURE
|
||||
? SignedPayload
|
||||
: undefined;
|
||||
});
|
||||
|
||||
export type fetchCustomContractArgsFromPropsResponse = {
|
||||
contractType?: ContractType;
|
||||
contractArgs?: any;
|
||||
};
|
||||
|
||||
export const fetchCustomContractArgsFromProps = (
|
||||
props: Record<string, any>,
|
||||
): fetchCustomContractArgsFromPropsResponse => {
|
||||
const propsToReturn: fetchCustomContractArgsFromPropsResponse = {};
|
||||
if ("contractType" in props) {
|
||||
propsToReturn.contractType = props.contractType;
|
||||
}
|
||||
if ("contractArgs" in props) {
|
||||
propsToReturn.contractArgs = props.contractArgs;
|
||||
}
|
||||
|
||||
return propsToReturn;
|
||||
};
|
||||
|
||||
/** This is basically a map from argument name to the value
|
||||
* Example:
|
||||
* json
|
||||
* recipient: "0x...",
|
||||
* quantity: 1,
|
||||
*
|
||||
* Corresponds to the following argument stub in solidity:
|
||||
* solidity
|
||||
* function myFunction (address recipient, uint128 quantity)
|
||||
*
|
||||
*
|
||||
* You can also pass your complex params for your contract like so:
|
||||
*
|
||||
* json
|
||||
* _user:address: "0x...", age: 24,
|
||||
* _quantity: 2,
|
||||
*
|
||||
*
|
||||
*
|
||||
* The above correspond to the following argument stub in solidity:
|
||||
* solidity
|
||||
* struct User
|
||||
* string name;
|
||||
* uint256 age;
|
||||
*
|
||||
*
|
||||
* function myFunction(User calldata _user, uint256 _quantity)
|
||||
*
|
||||
*
|
||||
*/
|
||||
type ArgumentMapType = {
|
||||
[key: string]:
|
||||
| string
|
||||
| null
|
||||
| number
|
||||
| boolean
|
||||
| Array<ArgumentMapType>
|
||||
| ArgumentMapType;
|
||||
};
|
||||
|
||||
/** This specifies the way a method should be called.
|
||||
*
|
||||
* Note that the argument names should match the argument names in your contract.
|
||||
*
|
||||
* Example:
|
||||
* ```json
|
||||
* {
|
||||
* name: "claim",
|
||||
* args: { _recipient: "0x...", _quantity: 2 }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Corresponds to the following function stub in solidity:
|
||||
* ```solidity
|
||||
* function claim(address _recipient, uint256 _quantity)
|
||||
* ```
|
||||
*
|
||||
* For more on the types of arguments you can pass, see ArgumentMapType
|
||||
*/
|
||||
export type ReadMethodCallType = {
|
||||
name: string;
|
||||
args?: ArgumentMapType;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is similar to ReadMethodCallType with two added properties
|
||||
*
|
||||
* ## payment
|
||||
* * We will automatically call the `approve` function for non native coins.
|
||||
* * The `value` should be human readable. So "1.2" represents "1.2" ETH or "1.2" USDC depending on the `currency` field
|
||||
*
|
||||
* ## callOptions
|
||||
* * As of now, we only support specifying the relative amount of gas to use.
|
||||
* * They correspond to the values of the [gas trackers](https://etherscan.io/gastracker) at the time of calling the function
|
||||
*/
|
||||
export type WriteMethodCallType = ReadMethodCallType & {
|
||||
payment: {
|
||||
currency: "MATIC" | "ETH" | "USDC" | "SOL" | "AVAX" | "USDC.e";
|
||||
value: string;
|
||||
};
|
||||
callOptions?: { gasOptions?: "low" | "medium" | "high" };
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
// UNCHANGED
|
||||
export interface PaymentSuccessResult {
|
||||
/**
|
||||
* A unique ID for this purchase.
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// UNCHANGED
|
||||
export type TransferSuccessResult = {
|
||||
/**
|
||||
* A unique ID for this purchase.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* A list of tokens claimed.
|
||||
*/
|
||||
claimedTokens: ClaimedToken[];
|
||||
};
|
||||
|
||||
export type ClaimedToken = {
|
||||
/**
|
||||
* The address of the NFT contract.
|
||||
*/
|
||||
contractAddress: string;
|
||||
|
||||
/**
|
||||
* The title of the collection.
|
||||
*/
|
||||
collectionTitle: string;
|
||||
|
||||
/**
|
||||
* The ID of the token in the collection that was claimed.
|
||||
* For ERC-721 collections, this ID is unique for each claim.
|
||||
* For ERC-1155 collections, this ID is identical for all buyers of this NFT token.
|
||||
*/
|
||||
tokenId: string;
|
||||
|
||||
transactions: {
|
||||
/**
|
||||
* The transaction hash for the claim transaction.
|
||||
*/
|
||||
claimHash: string;
|
||||
|
||||
/**
|
||||
* The blockchain explorer URL for the claim transaction.
|
||||
*/
|
||||
claimExplorerUrl: string;
|
||||
|
||||
/**
|
||||
* The transaction hash for the transfer transaction.
|
||||
* This hash is undefined if the claim directly sent the token to the buyer's wallet.
|
||||
*/
|
||||
transferHash: string | undefined;
|
||||
|
||||
/**
|
||||
* The blockchain explorer URL for the transfer transaction.
|
||||
* This hash is undefined if the claim directly sent the token to the buyer's wallet.
|
||||
*/
|
||||
transferExplorerUrl: string | undefined;
|
||||
};
|
||||
|
||||
metadata: {
|
||||
/**
|
||||
* The title of the NFT in the collection.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The description of the NFT
|
||||
*/
|
||||
description: string | undefined;
|
||||
|
||||
/**
|
||||
* An image representing the NFT.
|
||||
*/
|
||||
image: string | undefined;
|
||||
|
||||
/**
|
||||
* Arbitrary properties (i.e. traits) describing the NFT.
|
||||
*/
|
||||
properties: object | undefined;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
// UNCHANGED
|
||||
export const enum WalletType {
|
||||
Preset = "Preset",
|
||||
MetaMask = "metaMask",
|
||||
CoinbaseWallet = "coinbaseWallet",
|
||||
WalletConnect = "walletConnect",
|
||||
Phantom = "Phantom",
|
||||
}
|
||||
|
||||
export interface ConnectWalletProps {
|
||||
onWalletConnected: onWalletConnectedType;
|
||||
onWalletConnectFail: onWalletConnectFailType;
|
||||
}
|
||||
|
||||
export type onWalletConnectFailType = (props: {
|
||||
walletType: WalletType;
|
||||
currentUserWalletType: WalletType;
|
||||
error: Error;
|
||||
}) => void;
|
||||
export type onWalletConnectedType = (props: {
|
||||
userAddress: string;
|
||||
chainId: number;
|
||||
}) => void;
|
||||
@@ -0,0 +1,40 @@
|
||||
// UNCHANGED
|
||||
/**
|
||||
* Opens a popup centered on the parent page and returns a reference to the window.
|
||||
* The caller can close the popup with `popupWindow.close()`.
|
||||
* @returns The Window that was popped up
|
||||
*/
|
||||
export function openCenteredPopup({
|
||||
url,
|
||||
target,
|
||||
win,
|
||||
w,
|
||||
h,
|
||||
}: {
|
||||
url: string;
|
||||
target: string;
|
||||
win: Window & typeof globalThis;
|
||||
w: number;
|
||||
h: number;
|
||||
}) {
|
||||
const height = win?.top?.outerHeight || 100;
|
||||
const width = win?.top?.outerWidth || 100;
|
||||
const screenX = win?.top?.screenX || 100;
|
||||
const screenY = win?.top?.screenY || 100;
|
||||
const y = height / 2 + screenY - h / 2;
|
||||
const x = width / 2 + screenX - w / 2;
|
||||
return win.open(
|
||||
url,
|
||||
target,
|
||||
`toolbar=no,
|
||||
location=no,
|
||||
status=no,
|
||||
menubar=no,
|
||||
scrollbars=yes,
|
||||
resizable=yes,
|
||||
width=${w},
|
||||
height=${h},
|
||||
top=${y},
|
||||
left=${x}`,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// UNCHANGED
|
||||
export function postMessageToIframe(
|
||||
frame: HTMLIFrameElement,
|
||||
eventType: string,
|
||||
data: any,
|
||||
) {
|
||||
frame.contentWindow?.postMessage({ eventType, ...data }, "*");
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// UNCHANGED
|
||||
const IFRAME_EXPANDED_HEIGHT = 350;
|
||||
|
||||
export function resizeIframeToExpandedHeight(frame: HTMLIFrameElement) {
|
||||
frame.classList.toggle("transition-height-expanded");
|
||||
if (parseInt(frame.height) < IFRAME_EXPANDED_HEIGHT) {
|
||||
frame.height = IFRAME_EXPANDED_HEIGHT + "px";
|
||||
}
|
||||
}
|
||||
|
||||
export const FULL_SCREEN_IFRAME_STYLE = { width: "100vw", height: "100vh" };
|
||||
@@ -0,0 +1,49 @@
|
||||
// UNCHANGED
|
||||
import { css } from "@emotion/css";
|
||||
|
||||
export const opacity0 = css`
|
||||
opacity: 0;
|
||||
`;
|
||||
|
||||
export const opacity1 = css`
|
||||
opacity: 1;
|
||||
`;
|
||||
|
||||
export const iframeContainer = css`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const transitionContainer = css`
|
||||
display: grid;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const enterTransition = css`
|
||||
transition-delay: 150ms;
|
||||
transition-property: opacity;
|
||||
transition-duration: 75ms;
|
||||
`;
|
||||
|
||||
const leaveTransition = css`
|
||||
transition-property: opacity;
|
||||
transition-duration: 150ms;
|
||||
`;
|
||||
|
||||
const transitionDefaultClasses = css`
|
||||
background-color: transparent;
|
||||
grid-column-start: 1;
|
||||
grid-row-start: 1;
|
||||
`;
|
||||
|
||||
export const commonTransitionProps = {
|
||||
className: transitionDefaultClasses,
|
||||
enter: enterTransition,
|
||||
enterFrom: opacity0,
|
||||
enterTo: opacity1,
|
||||
leave: leaveTransition,
|
||||
leaveFrom: opacity1,
|
||||
leaveTo: opacity0,
|
||||
};
|
||||
@@ -255,7 +255,7 @@ export const defaultTokens: SupportedTokens = {
|
||||
icon: wrappedEthIcon,
|
||||
},
|
||||
{
|
||||
address: "0xc7198437980c041c805A1EDcbA50c1Ce5db95118",
|
||||
address: "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7",
|
||||
name: "Tether USD",
|
||||
symbol: "USDT",
|
||||
icon: tetherUsdIcon,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"],
|
||||
"compilerOptions": {
|
||||
"noUncheckedIndexedAccess": true
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/sdk
|
||||
|
||||
## 4.0.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2105](https://github.com/thirdweb-dev/js/pull/2105) [`b808e942`](https://github.com/thirdweb-dev/js/commit/b808e942fdaa81b5de61dcb3f2083081cb99ea11) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Fix verification
|
||||
|
||||
## 4.0.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "4.0.23",
|
||||
"version": "4.0.24",
|
||||
"description": "The main thirdweb SDK.",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -93,5 +93,5 @@ export async function getCompositeABI(
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
return pluginABIs.length > 0 ? joinABIs([abi, ...pluginABIs]) : abi;
|
||||
return pluginABIs.length > 0 ? joinABIs([...pluginABIs], abi) : abi;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,15 @@ import { unique } from "../utils";
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function joinABIs(abis: Abi[]): Abi {
|
||||
export function joinABIs(abis: Abi[], abiWithConstructor?: Abi): Abi {
|
||||
const parsedABIs = abis.map((abi) => AbiSchema.parse(abi)).flat();
|
||||
const filteredABIs = parsedABIs.filter((item) => item.type !== "constructor");
|
||||
|
||||
const filteredABIs = unique(parsedABIs, (a, b): boolean => {
|
||||
if (abiWithConstructor) {
|
||||
filteredABIs.push(...AbiSchema.parse(abiWithConstructor));
|
||||
}
|
||||
|
||||
const finalABIs = unique(filteredABIs, (a, b): boolean => {
|
||||
return (
|
||||
a.name === b.name &&
|
||||
a.type === b.type &&
|
||||
@@ -15,7 +20,5 @@ export function joinABIs(abis: Abi[]): Abi {
|
||||
);
|
||||
});
|
||||
|
||||
const finalABIs = filteredABIs.filter((item) => item.type !== "constructor");
|
||||
|
||||
return AbiSchema.parse(finalABIs);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { isContractDeployed } from "./any-evm-utils/isContractDeployed";
|
||||
import { getEncodedConstructorParamsForThirdwebContract } from "./any-evm-utils/getEncodedConstructorParamsForThirdwebContract";
|
||||
import { getThirdwebContractAddress } from "./any-evm-utils/getThirdwebContractAddress";
|
||||
import { extractConstructorParamsFromAbi } from "./feature-detection/extractConstructorParamsFromAbi";
|
||||
import { resolveContractUriFromAddress } from "./feature-detection/resolveContractUriFromAddress";
|
||||
import {
|
||||
resolveContractUriFromAddress,
|
||||
resolveImplementation,
|
||||
} from "./feature-detection/resolveContractUriFromAddress";
|
||||
import { fetchSourceFilesFromMetadata } from "./fetchSourceFilesFromMetadata";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { Abi } from "../schema/contracts/custom";
|
||||
@@ -15,6 +18,7 @@ import { fetchContractMetadataFromAddress } from "./metadata-resolver";
|
||||
import type { ContractPublisher } from "@thirdweb-dev/contracts-js";
|
||||
import { fetchExtendedReleaseMetadata } from "./feature-detection/fetchExtendedReleaseMetadata";
|
||||
import { getContractPublisherAddress } from "../constants/addresses/getContractPublisherAddress";
|
||||
import { getCreate2FactoryAddress } from "./any-evm-utils/getCreate2FactoryAddress";
|
||||
|
||||
const RequestStatus = {
|
||||
OK: "1",
|
||||
@@ -133,6 +137,8 @@ export async function verify(
|
||||
): Promise<string | string[]> {
|
||||
try {
|
||||
const provider = getChainProvider(chainId, {});
|
||||
contractAddress = (await resolveImplementation(contractAddress, provider))
|
||||
.address;
|
||||
const compilerMetadata = await fetchContractMetadataFromAddress(
|
||||
contractAddress,
|
||||
provider,
|
||||
@@ -339,21 +345,10 @@ async function fetchConstructorParams(
|
||||
if (constructorParamTypes.length === 0) {
|
||||
return "";
|
||||
}
|
||||
const requestBody = {
|
||||
apiKey: explorerAPIKey,
|
||||
module: "account",
|
||||
action: "txlist",
|
||||
address: contractAddress,
|
||||
page: "1",
|
||||
sort: "asc",
|
||||
offset: "1",
|
||||
};
|
||||
const parameters = new URLSearchParams({ ...requestBody });
|
||||
const result = await fetch(explorerAPIUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: parameters.toString(),
|
||||
});
|
||||
|
||||
const result = await fetch(
|
||||
`${explorerAPIUrl}?module=contract&action=getcontractcreation&contractaddresses=${contractAddress}&apikey=${explorerAPIKey}`,
|
||||
);
|
||||
const data = await result.json();
|
||||
if (
|
||||
data &&
|
||||
@@ -361,13 +356,16 @@ async function fetchConstructorParams(
|
||||
data.result[0] !== undefined
|
||||
) {
|
||||
const contract = new utils.Interface(abi);
|
||||
const txDeployBytecode = data.result[0].input;
|
||||
const txHash = data.result[0].txHash;
|
||||
let constructorArgs = "";
|
||||
|
||||
if (contract.deploy.inputs.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const tx = await provider.getTransaction(txHash);
|
||||
const txDeployBytecode = tx.data;
|
||||
|
||||
// first: attempt to get it from Publish
|
||||
try {
|
||||
const bytecode = await fetchDeployBytecodeFromPublishedContractMetadata(
|
||||
@@ -382,7 +380,16 @@ async function fetchConstructorParams(
|
||||
? bytecode
|
||||
: `0x${bytecode}`;
|
||||
|
||||
constructorArgs = txDeployBytecode.substring(bytecodeHex.length);
|
||||
let create2FactoryAddress;
|
||||
try {
|
||||
create2FactoryAddress = await getCreate2FactoryAddress(provider);
|
||||
} catch (error) {}
|
||||
|
||||
// if deterministic deploy through create2factory, remove salt length too
|
||||
const create2SaltLength = tx.to === create2FactoryAddress ? 64 : 0;
|
||||
constructorArgs = txDeployBytecode.substring(
|
||||
bytecodeHex.length + create2SaltLength,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// contracts not published through thirdweb
|
||||
|
||||
@@ -11,7 +11,9 @@ export * from "./contracts";
|
||||
export { StaticJsonRpcBatchProvider } from "./lib/static-batch-rpc";
|
||||
|
||||
// export integration things
|
||||
export * from "./integrations/thirdweb-checkout";
|
||||
export type {
|
||||
PaperCreateCheckoutLinkShardParams
|
||||
} from "./integrations/thirdweb-checkout";
|
||||
|
||||
// explicitly export the *TYPES* of prebuilt contracts
|
||||
export type { Edition } from "./contracts/prebuilt-implementations/edition";
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/service-utils
|
||||
|
||||
## 0.4.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2107](https://github.com/thirdweb-dev/js/pull/2107) [`1a5627f7`](https://github.com/thirdweb-dev/js/commit/1a5627f72d5a38386228517ef99fb800ca0f5a57) Thanks [@gerrysaporito](https://github.com/gerrysaporito)! - added checkout as service
|
||||
|
||||
## 0.4.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/service-utils",
|
||||
"version": "0.4.10",
|
||||
"version": "0.4.11",
|
||||
"main": "dist/thirdweb-dev-service-utils.cjs.js",
|
||||
"module": "dist/thirdweb-dev-service-utils.esm.js",
|
||||
"exports": {
|
||||
|
||||
@@ -44,6 +44,13 @@ export const SERVICE_DEFINITIONS = {
|
||||
// all actions allowed
|
||||
actions: [],
|
||||
},
|
||||
payments: {
|
||||
name: "checkout",
|
||||
title: "Checkouts",
|
||||
description: "Payments for easy web3 onboarding",
|
||||
// all actions allowed
|
||||
actions: [],
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const SERVICE_NAMES = Object.keys(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GatewayUrls } from "../types";
|
||||
import CIDTool from "cid-tool";
|
||||
import { GatewayUrls } from "../types";
|
||||
import { getProcessEnv } from "./process";
|
||||
import { sha256HexSync } from "@thirdweb-dev/crypto";
|
||||
|
||||
@@ -80,6 +80,7 @@ export function getGatewayUrlForCid(
|
||||
clientId?: string,
|
||||
): string {
|
||||
const parts = cid.split("/");
|
||||
|
||||
const hash = convertCidToV1(parts[0]);
|
||||
const filePath = parts.slice(1).join("/");
|
||||
|
||||
@@ -158,7 +159,7 @@ export function prepareGatewayUrls(
|
||||
* @internal
|
||||
*/
|
||||
export function convertCidToV1(cid: string) {
|
||||
let normalized: string;
|
||||
let normalized: string = '';
|
||||
try {
|
||||
const hash = cid.split("/")[0];
|
||||
normalized = CIDTool.base32(hash);
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @thirdweb-dev/unity-js-bridge
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b808e942`](https://github.com/thirdweb-dev/js/commit/b808e942fdaa81b5de61dcb3f2083081cb99ea11), [`e46dd025`](https://github.com/thirdweb-dev/js/commit/e46dd0254f157c3c4d50fe3e4f44e2e24eaf34a8)]:
|
||||
- @thirdweb-dev/sdk@4.0.24
|
||||
- @thirdweb-dev/wallets@2.3.3
|
||||
- @thirdweb-dev/auth@4.1.20
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/unity-js-bridge",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.3",
|
||||
"main": "dist/thirdweb-unity-bridge.js",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @thirdweb-dev/wallets
|
||||
|
||||
## 2.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2103](https://github.com/thirdweb-dev/js/pull/2103) [`e46dd025`](https://github.com/thirdweb-dev/js/commit/e46dd0254f157c3c4d50fe3e4f44e2e24eaf34a8) Thanks [@MananTank](https://github.com/MananTank)! - Update JSDoc comments
|
||||
|
||||
- Updated dependencies [[`b808e942`](https://github.com/thirdweb-dev/js/commit/b808e942fdaa81b5de61dcb3f2083081cb99ea11)]:
|
||||
- @thirdweb-dev/sdk@4.0.24
|
||||
|
||||
## 2.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/wallets",
|
||||
"version": "2.3.2",
|
||||
"version": "2.3.3",
|
||||
"main": "dist/thirdweb-dev-wallets.cjs.js",
|
||||
"module": "dist/thirdweb-dev-wallets.esm.js",
|
||||
"browser": {
|
||||
@@ -493,8 +493,8 @@
|
||||
"@safe-global/safe-ethers-adapters": "0.1.0-alpha.17",
|
||||
"@safe-global/safe-ethers-lib": "^1.9.4",
|
||||
"@thirdweb-dev/chains": "workspace:*",
|
||||
"@thirdweb-dev/crypto": "workspace:*",
|
||||
"@thirdweb-dev/contracts-js": "workspace:*",
|
||||
"@thirdweb-dev/crypto": "workspace:*",
|
||||
"@thirdweb-dev/sdk": "workspace:*",
|
||||
"@walletconnect/core": "^2.9.1",
|
||||
"@walletconnect/ethereum-provider": "^2.9.1",
|
||||
@@ -546,6 +546,7 @@
|
||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.22.5",
|
||||
"@babel/plugin-transform-private-methods": "7.22.5",
|
||||
"@microsoft/api-extractor": "^7.36.3",
|
||||
"@noble/ed25519": "^1.7.1",
|
||||
"@preconstruct/cli": "2.7.0",
|
||||
"@thirdweb-dev/tsconfig": "workspace:*",
|
||||
@@ -562,8 +563,8 @@
|
||||
"ethers-aws-kms-signer": "^1.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"typescript": "^5.1.6",
|
||||
"typedoc-gen": "workspace:*"
|
||||
"typedoc-gen": "workspace:*",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --write 'src/**/*'",
|
||||
|
||||
@@ -4,7 +4,14 @@
|
||||
import { typedoc } from "typedoc-gen";
|
||||
|
||||
typedoc({
|
||||
entryPoints: ["src/index.ts"],
|
||||
entryPoints: [
|
||||
"src/index.ts",
|
||||
"src/evm/wallets/aws-kms.ts",
|
||||
"src/evm/wallets/gcp-kms.ts",
|
||||
"src/evm/wallets/private-key.ts",
|
||||
"src/evm/wallets/aws-secrets-manager.ts",
|
||||
"src/evm/wallets/local-wallet-node.ts",
|
||||
],
|
||||
exclude: [],
|
||||
output: "both", // TODO: change this to 'json' when old portal is fully migrated to new portal
|
||||
});
|
||||
|
||||
@@ -8,6 +8,9 @@ export type CreateAsyncStorage = (name: string) => AsyncStorage;
|
||||
|
||||
const PREFIX = "__TW__";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class AsyncLocalStorage implements AsyncStorage {
|
||||
name: string;
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ export type EmbeddedWalletConstructorType = ClientIdConstructorType & {
|
||||
*/
|
||||
chain: Chain;
|
||||
/**
|
||||
* @param authResult - the authResult returned from the EmbeddedWalletSdk auth method
|
||||
* @param authResult - The authResult returned from the EmbeddedWalletSdk auth method
|
||||
* @returns
|
||||
*/
|
||||
onAuthSuccess?: (authResult: AuthAndWalletRpcReturnType) => void;
|
||||
|
||||
@@ -147,7 +147,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
/**
|
||||
* Check whether the connected signer can execute a given transaction using the smart wallet.
|
||||
* @param transaction - the transaction to execute using the smart wallet.
|
||||
* @param transaction - The transaction to execute using the smart wallet.
|
||||
* @returns whether the connected signer can execute the transaction using the smart wallet.
|
||||
*/
|
||||
async hasPermissionToExecute(transaction: Transaction): Promise<boolean> {
|
||||
@@ -172,7 +172,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
/**
|
||||
* Send a single transaction without waiting for confirmations
|
||||
* @param transaction - the transaction to send
|
||||
* @param transaction - The transaction to send
|
||||
* @returns The awaitable transaction
|
||||
*/
|
||||
async send(transaction: Transaction): Promise<providers.TransactionResponse> {
|
||||
@@ -186,7 +186,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
/**
|
||||
* Execute a single transaction (waiting for confirmations)
|
||||
* @param transaction - the transaction to execute
|
||||
* @param transaction - The transaction to execute
|
||||
* @returns The transaction receipt
|
||||
*/
|
||||
async execute(transaction: Transaction): Promise<TransactionResult> {
|
||||
@@ -217,7 +217,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
/**
|
||||
* Execute multiple transactions in a single batch
|
||||
* @param transactions - the transactions to execute
|
||||
* @param transactions - The transactions to execute
|
||||
* @returns The transaction receipt
|
||||
*/
|
||||
async executeBatch(
|
||||
|
||||
@@ -103,9 +103,9 @@ export abstract class BaseAccountAPI {
|
||||
|
||||
/**
|
||||
* encode the call from entryPoint through our account to the target contract.
|
||||
* @param target - the target contract address
|
||||
* @param value - the value to send to the target contract
|
||||
* @param data - the calldata to send to the target contract
|
||||
* @param target - The target contract address
|
||||
* @param value - The value to send to the target contract
|
||||
* @param data - The calldata to send to the target contract
|
||||
*/
|
||||
abstract prepareExecute(
|
||||
target: string,
|
||||
@@ -115,7 +115,7 @@ export abstract class BaseAccountAPI {
|
||||
|
||||
/**
|
||||
* sign a userOp's hash (userOpHash).
|
||||
* @param userOpHash - the hash to sign
|
||||
* @param userOpHash - The hash to sign
|
||||
*/
|
||||
abstract signUserOpHash(userOpHash: string): Promise<string>;
|
||||
|
||||
@@ -385,7 +385,7 @@ export abstract class BaseAccountAPI {
|
||||
|
||||
/**
|
||||
* Sign the filled userOp.
|
||||
* @param userOp - the UserOperation to sign (with signature field ignored)
|
||||
* @param userOp - The UserOperation to sign (with signature field ignored)
|
||||
*/
|
||||
async signUserOp(userOp: UserOperationStruct): Promise<UserOperationStruct> {
|
||||
const userOpHash = await this.getUserOpHash(userOp);
|
||||
|
||||
@@ -106,7 +106,7 @@ export class HttpRpcClient {
|
||||
|
||||
/**
|
||||
* send a UserOperation to the bundler
|
||||
* @param userOp1 - the UserOperation to send
|
||||
* @param userOp1 - The UserOperation to send
|
||||
* @returns userOpHash the id of this operation, for getUserOperationTransaction
|
||||
*/
|
||||
async sendUserOpToBundler(userOp1: UserOperationStruct): Promise<string> {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ProviderConfig } from "../types";
|
||||
|
||||
/**
|
||||
* wrap an existing provider to tunnel requests through Account Abstraction.
|
||||
* @param originalProvider - the normal provider
|
||||
* @param originalProvider - The normal provider
|
||||
* @param config - see {@link ClientConfig} for more info
|
||||
* @param originalSigner - use this signer as the owner. of this wallet. By default, use the provider's signer
|
||||
*/
|
||||
|
||||
@@ -26,7 +26,7 @@ export * from "./wallets/paper-wallet";
|
||||
// just the types
|
||||
export { AbstractClientWallet } from "./wallets/base";
|
||||
export type { WalletOptions } from "./wallets/base";
|
||||
export * from "./wallets/blocto";
|
||||
export { type BloctoOptions, BloctoWallet } from "./wallets/blocto";
|
||||
export * from "./wallets/coinbase-wallet";
|
||||
export * from "./wallets/embedded-wallet";
|
||||
export * from "./wallets/ethers";
|
||||
@@ -35,7 +35,13 @@ export * from "./wallets/injected";
|
||||
export * from "./wallets/local-wallet";
|
||||
export * from "./wallets/signer";
|
||||
export * from "./wallets/magic";
|
||||
export * from "./wallets/metamask";
|
||||
|
||||
export {
|
||||
MetaMaskWallet,
|
||||
type MetamaskAdditionalOptions,
|
||||
type MetamaskWalletOptions,
|
||||
} from "./wallets/metamask";
|
||||
|
||||
export * from "./wallets/phantom";
|
||||
export * from "./wallets/private-key";
|
||||
export * from "./wallets/rainbow-wallet";
|
||||
|
||||
@@ -1,9 +1,64 @@
|
||||
let walletAnalyticsEnabled = true;
|
||||
|
||||
/**
|
||||
* Check if Wallet Analytics is enabled or not
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { isWalletAnalyticsEnabled } from '@thirdweb/wallets';
|
||||
*
|
||||
* const isAnalyticsEnabled = isWalletAnalyticsEnabled();
|
||||
* ```
|
||||
*
|
||||
* The Wallet analytics can be used to track:
|
||||
* - Total and Unique users
|
||||
* - Users connected over time
|
||||
* - Type of wallets connected
|
||||
* - Distribution of wallets connected
|
||||
*
|
||||
* You can view these Analytics in the [ThirdWeb Wallet Analytics dashboard](https://thirdweb.com/dashboard/wallets/analytics)
|
||||
*
|
||||
* By default it is enabled. You can disable it by calling `setWalletAnalyticsEnabled(false)`
|
||||
*
|
||||
* ```ts
|
||||
* import { setWalletAnalyticsEnabled } from '@thirdweb/wallets';
|
||||
*
|
||||
* setWalletAnalyticsEnabled(false);
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export function isWalletAnalyticsEnabled() {
|
||||
return walletAnalyticsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable Wallet Analytics
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { setWalletAnalyticsEnabled } from '@thirdweb/wallets';
|
||||
*
|
||||
* setWalletAnalyticsEnabled(false);
|
||||
* ```
|
||||
*
|
||||
* The Wallet analytics can be used to track:
|
||||
* - Total and Unique users
|
||||
* - Users connected over time
|
||||
* - Type of wallets connected
|
||||
* - Distribution of wallets connected
|
||||
*
|
||||
* You can view these Analytics in the [ThirdWeb Wallet Analytics dashboard](https://thirdweb.com/dashboard/wallets/analytics)
|
||||
*
|
||||
* By default, The Wallet Analytics is enabled
|
||||
*
|
||||
* You can check if it is enabled or not by calling `isWalletAnalyticsEnabled()`
|
||||
* ```ts
|
||||
* import { isWalletAnalyticsEnabled } from '@thirdweb/wallets';
|
||||
*
|
||||
* const isAnalyticsEnabled = isWalletAnalyticsEnabled();
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export function setWalletAnalyticsEnabled(enabled: boolean) {
|
||||
walletAnalyticsEnabled = enabled;
|
||||
}
|
||||
|
||||
@@ -60,17 +60,29 @@ export async function checkContractWalletSignature(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for any wallet in the Wallet SDK, including backend wallets. It contains the functionality common to all wallets.
|
||||
*
|
||||
* This wallet is not meant to be used directly, but instead be extended to [build your own wallet](https://portal.thirdweb.com/wallet/build-a-wallet)
|
||||
*
|
||||
* @abstractWallet
|
||||
*/
|
||||
export abstract class AbstractWallet
|
||||
extends EventEmitter<WalletEvents>
|
||||
implements GenericAuthWallet, EVMWallet
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public type: Ecosystem = "evm";
|
||||
|
||||
/**
|
||||
* Returns an [ethers Signer](https://docs.ethers.org/v5/api/signer/) object of the connected wallet
|
||||
*/
|
||||
public abstract getSigner(): Promise<Signer>;
|
||||
|
||||
/**
|
||||
* @returns The account address from connected wallet
|
||||
* Returns the account address of the connected wallet
|
||||
*/
|
||||
public async getAddress(): Promise<string> {
|
||||
const signer = await this.getSigner();
|
||||
@@ -78,9 +90,11 @@ export abstract class AbstractWallet
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The native token balance of the connected wallet
|
||||
* Returns the balance of the connected wallet for the specified token address. If no token address is specified, it returns the balance of the native token
|
||||
*
|
||||
* @param tokenAddress - The contract address of the token
|
||||
*/
|
||||
public async getBalance(currencyAddress: string = NATIVE_TOKEN_ADDRESS) {
|
||||
public async getBalance(tokenAddress: string = NATIVE_TOKEN_ADDRESS) {
|
||||
const signer = await this.getSigner();
|
||||
const address = await this.getAddress();
|
||||
|
||||
@@ -89,25 +103,32 @@ export abstract class AbstractWallet
|
||||
}
|
||||
|
||||
let balance: BigNumber;
|
||||
if (isNativeToken(currencyAddress)) {
|
||||
if (isNativeToken(tokenAddress)) {
|
||||
balance = await signer.provider.getBalance(address);
|
||||
} else {
|
||||
const erc20 = createErc20(signer, currencyAddress);
|
||||
const erc20 = createErc20(signer, tokenAddress);
|
||||
balance = await erc20.balanceOf(address);
|
||||
}
|
||||
|
||||
// Note: assumes that the native currency decimals is 18, which isn't always correct
|
||||
return await fetchCurrencyValue(signer.provider, currencyAddress, balance);
|
||||
return await fetchCurrencyValue(signer.provider, tokenAddress, balance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The chain id from connected wallet
|
||||
* Returns the chain id of the network that the wallet is connected to
|
||||
*/
|
||||
public async getChainId(): Promise<number> {
|
||||
const signer = await this.getSigner();
|
||||
return signer.getChainId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers some amount of tokens to the specified address
|
||||
* @param to - The address to transfer the amount to
|
||||
* @param amount - The amount to transfer
|
||||
* @param currencyAddress - The contract address of the token to transfer. If not specified, it defaults to the native token
|
||||
* @returns The transaction result
|
||||
*/
|
||||
public async transfer(
|
||||
to: string,
|
||||
amount: Price,
|
||||
@@ -141,7 +162,9 @@ export abstract class AbstractWallet
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The signature of the message
|
||||
* Sign a message with the connected wallet and return the signature
|
||||
* @param message - The message to sign
|
||||
* @returns - The signature
|
||||
*/
|
||||
public async signMessage(message: Bytes | string): Promise<string> {
|
||||
const signer = await this.getSigner();
|
||||
@@ -149,8 +172,11 @@ export abstract class AbstractWallet
|
||||
}
|
||||
|
||||
/**
|
||||
* verify the signature of a message
|
||||
* @returns `true` if the signature is valid, `false` otherwise
|
||||
* Verify the signature of a message. It returns `true` if the signature is valid, `false` otherwise
|
||||
* @param message - The message to verify
|
||||
* @param signature - The signature to verify
|
||||
* @param address - The address to verify the signature against
|
||||
* @param chainId - The chain id of the network to verify the signature against, If not specified, it defaults to 1 ( Ethereum mainnet )
|
||||
*/
|
||||
public async verifySignature(
|
||||
message: string,
|
||||
|
||||
@@ -6,6 +6,9 @@ export interface AsyncWalletOptions {
|
||||
cacheSigner: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class AsyncWallet extends AbstractWallet {
|
||||
#signer?: Signer;
|
||||
#options: AsyncWalletOptions;
|
||||
|
||||
@@ -3,12 +3,13 @@ import type { Signer } from "ethers";
|
||||
import type { AwsKmsSignerCredentials } from "ethers-aws-kms-signer";
|
||||
|
||||
/**
|
||||
* Create a wallet instance using a private key stored in AWS KMS.
|
||||
* Connect to a custodial wallet with a signing key in [AWS Key Management Service](https://aws.amazon.com/kms/).
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk"
|
||||
* import { AwsKmsWallet } from "@thirdweb-dev/sdk/evm/wallets"
|
||||
* In order to instantiate an AWS KMS wallet, you need to get the relevant credentials for the AWS KMS key that you want to use.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* import { AwsKmsWallet } from "@thirdweb-dev/wallets/evm/wallets/aws-kms";
|
||||
*
|
||||
* const wallet = new AwsKmsWallet({
|
||||
* region: "us-east-1",
|
||||
@@ -17,18 +18,27 @@ import type { AwsKmsSignerCredentials } from "ethers-aws-kms-signer";
|
||||
* sessionToken: process.env.AWS_SESSION_TOKEN,
|
||||
* keyId: process.env.AWS_KEY_ID,
|
||||
* });
|
||||
*
|
||||
* const sdk = await ThirdwebSDK.fromWallet(wallet, "mainnet");
|
||||
* ```
|
||||
*
|
||||
* @wallet
|
||||
*/
|
||||
export class AwsKmsWallet extends AbstractWallet {
|
||||
#signer?: Promise<Signer>;
|
||||
#options: AwsKmsSignerCredentials;
|
||||
|
||||
/**
|
||||
* Create instance of `AwsKmsWallet`
|
||||
* @param options -
|
||||
* The `options` object of type `AwsKmsSignerCredentials` from `ethers-aws-kms-signer` package
|
||||
*/
|
||||
constructor(options: AwsKmsSignerCredentials) {
|
||||
super();
|
||||
this.#options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get [ethers signer](https://docs.ethers.io/v5/api/signer/) of the connected wallet
|
||||
*/
|
||||
async getSigner(): Promise<Signer> {
|
||||
if (!this.#signer) {
|
||||
this.#signer = new Promise(async (resolve, reject) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user