Compare commits

...
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
3871068aa6 Version Packages (#2116)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-03 15:59:09 +13:00
Isaac DubuqueandGitHub d00ca7fd2a add patch bump to payments package (#2115) 2024-01-03 02:51:11 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
9ee3c21eac Version Packages (#2102)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-03 14:34:19 +13:00
ed3afd418c Isaac/react sdk migration (#1913)
Co-authored-by: gerrysaporito <[email protected]>
2024-01-03 00:55:16 +00:00
iketwandGitHub b90fa2cc29 [RN] Adds hideSendButtonand hideReceiveButton to ConnectWallet (#2108) 2024-01-02 20:02:44 +00:00
EimanandGitHub 2ab517aa49 changing Avalanche tether USD address mapping (#2109) 2023-12-28 16:34:34 +00:00
Manan TankandGitHub e46dd0254f [wallets] JSDoc improvements for autogenerated docs (#2103) 2023-12-22 10:06:51 +00:00
Gerry SaporitoandGitHub 1a5627f72d chore: added payments to scope (#2107) 2023-12-20 18:39:51 +00:00
YashandGitHub b808e942fd [SDK] Fix verification (#2105) 2023-12-20 15:02:04 +00:00
iketwandGitHub 7fdb1715e4 [RN] Export types to improve references (#2101) 2023-12-19 17:02:08 +00:00
134 changed files with 6789 additions and 252 deletions
+7
View File
@@ -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 -1
View File
@@ -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": {
+9
View File
@@ -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 -1
View File
@@ -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",
+11
View File
@@ -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"
]
}
+4
View File
@@ -0,0 +1,4 @@
# folders
dist/
# files
+21
View File
@@ -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.",
},
],
},
],
},
};
+18
View File
@@ -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
+14
View File
@@ -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 />
+58
View File
@@ -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;
};
+4
View File
@@ -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";
+25
View File
@@ -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",
};
+8
View File
@@ -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;
}
+17
View File
@@ -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);
}
}
+146
View File
@@ -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 = "&#x2715;";
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",
},
});
+116
View File
@@ -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);
}
}
+75
View File
@@ -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); }
}
`;
+4
View File
@@ -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;
}
}
+29
View File
@@ -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 }, "*");
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "@thirdweb-dev/tsconfig/sdk.json",
"include": ["src", "types"],
"exclude": ["dist", "build", "node_modules"],
"compilerOptions": {
"noUncheckedIndexedAccess": true
}
}
+11
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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",
+26
View File
@@ -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 -1
View File
@@ -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}
@@ -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;
+15 -3
View File
@@ -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"
+27
View File
@@ -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
+5 -1
View File
@@ -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",
+1
View File
@@ -113,3 +113,4 @@ export type {
// at the moment we'll re-export everything from the evm package
export * from "./evm";
export * from "./payments";
+54
View File
@@ -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
View File
@@ -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}>
&times;
</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}>
&times;
</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;
`;
+22
View File
@@ -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 -1
View File
@@ -3,6 +3,8 @@
"include": ["."],
"exclude": ["dist", "build", "node_modules"],
"compilerOptions": {
"noUncheckedIndexedAccess": true
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"esModuleInterop": true
}
}
+6
View File
@@ -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 -1
View File
@@ -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);
}
+25 -18
View File
@@ -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
+3 -1
View File
@@ -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";
+6
View File
@@ -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 -1
View File
@@ -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(
+3 -2
View File
@@ -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);
+9
View File
@@ -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 -1
View File
@@ -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",
+9
View File
@@ -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
+5 -4
View File
@@ -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/**/*'",
+8 -1
View File
@@ -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;
@@ -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
*/
+8 -2
View File
@@ -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;
}
+37 -11
View File
@@ -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;
+17 -7
View File
@@ -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