Skip to content

WIP brige embed #7222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 29 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fe64ae4
WIP brige embed
joaquim-verges May 30, 2025
88618c8
refactor: account for fee action
gregfromstl Jun 4, 2025
337f52f
fix: disable start transaction button on autostart
gregfromstl Jun 4, 2025
0c0b747
fix: respect updated amount
gregfromstl Jun 4, 2025
8763876
feat: include prices in routes
gregfromstl Jun 4, 2025
a906c6e
feat: sort by dollar balance
gregfromstl Jun 4, 2025
11e831e
nit: adjust chain icon position
gregfromstl Jun 4, 2025
8b24014
feat: improve number formatting
gregfromstl Jun 4, 2025
c6737a0
polish direct payment screen ui
joaquim-verges Jun 5, 2025
a486c8a
code cleanup
joaquim-verges Jun 5, 2025
c6112fe
top up label
joaquim-verges Jun 5, 2025
dcd7b08
TransactionPayment component
joaquim-verges Jun 5, 2025
ec80659
update paymentMachine tests
joaquim-verges Jun 5, 2025
c0da29c
update FundWallet look
joaquim-verges Jun 5, 2025
a10b218
feat: adds token not supported screen
gregfromstl Jun 5, 2025
90fef12
docs: adds theme stories
gregfromstl Jun 5, 2025
c0429e1
feat: auto-add unsupported tokens
gregfromstl Jun 5, 2025
224e2af
refactor: make token name and symbol optional
gregfromstl Jun 5, 2025
2221dcf
feat: enable passing quickOptions
gregfromstl Jun 5, 2025
74faebe
ui polish, reuse header accross all modes
joaquim-verges Jun 6, 2025
2998cfe
UI polish for transactions and tokens
joaquim-verges Jun 6, 2025
e600e26
update useSendTransaction to use new widget
joaquim-verges Jun 6, 2025
f3f49cd
transaction execution
joaquim-verges Jun 6, 2025
6499e49
incorporate post buy transaction in the state machine
joaquim-verges Jun 6, 2025
7ae018b
update payment state machine tests
joaquim-verges Jun 6, 2025
984c348
playground build
joaquim-verges Jun 6, 2025
d2dcf7c
refactor: rename quickOptions to presetOptions for consistency across…
gregfromstl Jun 6, 2025
84a5e8c
feat: adds onramp logos
gregfromstl Jun 6, 2025
484a3e1
feat: show quotes for onramp providers
gregfromstl Jun 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Welcome, AI copilots! This guide captures the coding standards, architectural de
- Biome governs formatting and linting; its rules live in biome.json.
- Run pnpm biome check --apply before committing.
- Avoid editor‑specific configs; rely on the shared settings.
- make sure everything builds after each file change by running `pnpm build`


Expand All @@ -39,7 +40,8 @@ Welcome, AI copilots! This guide captures the coding standards, architectural de
- Co‑locate tests: foo.ts ↔ foo.test.ts.
- Use real function invocations with stub data; avoid brittle mocks.
- For network interactions, use Mock Service Worker (MSW) to intercept fetch/HTTP calls, mocking only scenarios that are hard to reproduce.
- Keep tests deterministic and side‑effect free; Jest is pre‑configured.
- Keep tests deterministic and side‑effect free; Vitest is pre‑configured.
- to run the tests: `cd packages thirdweb & pnpm test:dev <filename>`


Expand Down
3 changes: 2 additions & 1 deletion apps/playground-web/src/app/connect/pay/commerce/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ function BuyMerch() {
sellerAddress: "0xEb0effdFB4dC5b3d5d3aC6ce29F3ED213E95d675",
},
metadata: {
name: "Black Hoodie (Size L)",
name: "Black Hoodie",
description: "Size L. Ships worldwide.",
image: "/drip-hoodie.png",
},
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type PayEmbedPlaygroundOptions = {
mode?: "fund_wallet" | "direct_payment" | "transaction";
title: string | undefined;
image: string | undefined;
description: string | undefined;

// fund_wallet mode options
buyTokenAddress: string | undefined;
Expand Down
20 changes: 20 additions & 0 deletions apps/playground-web/src/app/connect/pay/embed/LeftSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,26 @@ export function LeftSection(props: {
/>
</div>
</div>

{/* Modal description */}
<div className="flex flex-col gap-2">
<Label htmlFor="modal-description">Image</Label>
<Input
id="modal-description"
placeholder="Your own description here"
className="bg-card"
value={options.payOptions.description}
onChange={(e) =>
setOptions((v) => ({
...v,
payOptions: {
...payOptions,
description: e.target.value,
},
}))
}
/>
</div>
</div>
</CollapsibleSection>

Expand Down
16 changes: 10 additions & 6 deletions apps/playground-web/src/app/connect/pay/embed/RightSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,19 @@ export function RightSection(props: {
(props.options.payOptions.mode === "transaction"
? "Transaction"
: props.options.payOptions.mode === "direct_payment"
? "Purchase"
? "Product Name"
: "Buy Crypto"),
description:
props.options.payOptions.description || "Your own description here",
image:
props.options.payOptions.image ||
`https://placehold.co/600x400/${
props.options.theme.type === "dark"
? "1d1d23/7c7a85"
: "f2eff3/6f6d78"
}?text=Your%20Product%20Here&font=roboto`,
props.options.payOptions.mode === "direct_payment"
? `https://placehold.co/600x400/${
props.options.theme.type === "dark"
? "1d1d23/7c7a85"
: "f2eff3/6f6d78"
}?text=Your%20Product%20Here&font=roboto`
: undefined,
},

// Mode-specific options
Expand Down
1 change: 1 addition & 0 deletions apps/playground-web/src/app/connect/pay/embed/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const defaultConnectOptions: PayEmbedPlaygroundOptions = {
mode: "fund_wallet",
title: "",
image: "",
description: "",
buyTokenAddress: NATIVE_TOKEN_ADDRESS,
buyTokenAmount: "0.01",
buyTokenChain: base,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function BuyMerchPreview() {
},
metadata: {
name: "Black Hoodie (Size L)",
description: "Size L. Ships worldwide.",
image: "/drip-hoodie.png",
},
}}
Expand Down
4 changes: 2 additions & 2 deletions packages/thirdweb/scripts/wallets/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,11 @@ export type MinimalWalletInfo = {
/**
* @internal
*/
const ALL_MINIMAL_WALLET_INFOS = <const>${JSON.stringify(
const ALL_MINIMAL_WALLET_INFOS = ${JSON.stringify(
[...walletInfos, ...customWalletInfos],
null,
2,
)} satisfies MinimalWalletInfo[];
)} as const satisfies MinimalWalletInfo[];

export default ALL_MINIMAL_WALLET_INFOS;
`,
Expand Down
5 changes: 5 additions & 0 deletions packages/thirdweb/src/bridge/Routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export async function routes(options: routes.Options): Promise<routes.Result> {
sortBy,
limit,
offset,
includePrices,
} = options;

const clientFetch = getClientFetch(client);
Expand Down Expand Up @@ -159,6 +160,9 @@ export async function routes(options: routes.Options): Promise<routes.Result> {
if (sortBy) {
url.searchParams.set("sortBy", sortBy);
}
if (includePrices) {
url.searchParams.set("includePrices", includePrices.toString());
}

const response = await clientFetch(url.toString());
if (!response.ok) {
Expand All @@ -185,6 +189,7 @@ export declare namespace routes {
transactionHash?: ox__Hex.Hex;
sortBy?: "popularity";
maxSteps?: number;
includePrices?: boolean;
limit?: number;
offset?: number;
};
Expand Down
83 changes: 82 additions & 1 deletion packages/thirdweb/src/bridge/Token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export async function tokens(options: tokens.Options): Promise<tokens.Result> {

export declare namespace tokens {
/**
* Input parameters for {@link Bridge.tokens}.
* Input parameters for {@link tokens}.
*/
type Options = {
/** Your {@link ThirdwebClient} instance. */
Expand All @@ -182,3 +182,84 @@ export declare namespace tokens {
*/
type Result = Token[];
}

/**
* Adds a token to the Universal Bridge for indexing.
*
* This function requests the Universal Bridge to index a specific token on a given chain.
* Once indexed, the token will be available for cross-chain operations.
*
* @example
* ```typescript
* import { Bridge } from "thirdweb";
*
* // Add a token for indexing
* const result = await Bridge.add({
* client: thirdwebClient,
* chainId: 1,
* tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
* });
* ```
*
* @param options - The options for adding a token.
* @param options.client - Your thirdweb client.
* @param options.chainId - The chain ID where the token is deployed.
* @param options.tokenAddress - The contract address of the token to add.
*
* @returns A promise that resolves when the token has been successfully submitted for indexing.
*
* @throws Will throw an error if there is an issue adding the token.
* @bridge
* @beta
*/
export async function add(options: add.Options): Promise<add.Result> {
const { client, chainId, tokenAddress } = options;

const clientFetch = getClientFetch(client);
const url = `${getThirdwebBaseUrl("bridge")}/v1/tokens`;

const requestBody = {
chainId,
tokenAddress,
};

const response = await clientFetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});

if (!response.ok) {
const errorJson = await response.json();
throw new ApiError({
code: errorJson.code || "UNKNOWN_ERROR",
message: errorJson.message || response.statusText,
correlationId: errorJson.correlationId || undefined,
statusCode: response.status,
});
}

const { data }: { data: Token } = await response.json();
return data;
}

export declare namespace add {
/**
* Input parameters for {@link add}.
*/
type Options = {
/** Your {@link ThirdwebClient} instance. */
client: ThirdwebClient;
/** The chain ID where the token is deployed. */
chainId: number;
/** The contract address of the token to add. */
tokenAddress: string;
};

/**
* The result returned from {@link Bridge.add}.
*/
type Result = Token;
}
2 changes: 1 addition & 1 deletion packages/thirdweb/src/bridge/types/BridgeAction.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type Action = "approval" | "transfer" | "buy" | "sell";
export type Action = "approval" | "transfer" | "buy" | "sell" | "fee";
11 changes: 11 additions & 0 deletions packages/thirdweb/src/bridge/types/Errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { stringify } from "../../utils/json.js";

type ErrorCode =
| "INVALID_INPUT"
| "ROUTE_NOT_FOUND"
Expand All @@ -22,4 +24,13 @@ export class ApiError extends Error {
this.correlationId = args.correlationId;
this.statusCode = args.statusCode;
}

override toString() {
return stringify({
code: this.code,
message: this.message,
statusCode: this.statusCode,
correlationId: this.correlationId,
});
}
}
4 changes: 2 additions & 2 deletions packages/thirdweb/src/pay/buyWithFiat/getQuote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,9 @@ export async function getBuyWithFiatQuote(
provider?: FiatProvider,
): "stripe" | "coinbase" | "transak" => {
switch (provider) {
case "STRIPE":
case "stripe":
return "stripe";
case "TRANSAK":
case "transak":
return "transak";
default: // default to coinbase when undefined or any other value
return "coinbase";
Expand Down
8 changes: 4 additions & 4 deletions packages/thirdweb/src/pay/convert/cryptoToFiat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Address } from "abitype";
import type { Chain } from "../../chains/types.js";
import type { ThirdwebClient } from "../../client/client.js";
import { isAddress } from "../../utils/address.js";
import { getTokenPrice } from "./get-token.js";
import { getToken } from "./get-token.js";
import type { SupportedFiatCurrency } from "./type.js";

/**
Expand Down Expand Up @@ -73,11 +73,11 @@ export async function convertCryptoToFiat(
"Invalid fromTokenAddress. Expected a valid EVM contract address",
);
}
const price = await getTokenPrice(client, fromTokenAddress, chain.id);
if (!price) {
const token = await getToken(client, fromTokenAddress, chain.id);
if (token.priceUsd === 0) {
throw new Error(
`Error: Failed to fetch price for token ${fromTokenAddress} on chainId: ${chain.id}`,
);
}
return { result: price * fromAmount };
return { result: token.priceUsd * fromAmount };
}
8 changes: 4 additions & 4 deletions packages/thirdweb/src/pay/convert/fiatToCrypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Address } from "abitype";
import type { Chain } from "../../chains/types.js";
import type { ThirdwebClient } from "../../client/client.js";
import { isAddress } from "../../utils/address.js";
import { getTokenPrice } from "./get-token.js";
import { getToken } from "./get-token.js";
import type { SupportedFiatCurrency } from "./type.js";

/**
Expand Down Expand Up @@ -72,11 +72,11 @@ export async function convertFiatToCrypto(
if (!isAddress(to)) {
throw new Error("Invalid `to`. Expected a valid EVM contract address");
}
const price = await getTokenPrice(client, to, chain.id);
if (!price || price === 0) {
const token = await getToken(client, to, chain.id);
if (!token || token.priceUsd === 0) {
throw new Error(
`Error: Failed to fetch price for token ${to} on chainId: ${chain.id}`,
);
}
return { result: fromAmount / price };
return { result: fromAmount / token.priceUsd };
}
21 changes: 17 additions & 4 deletions packages/thirdweb/src/pay/convert/get-token.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import { tokens } from "../../bridge/Token.js";
import { add, tokens } from "../../bridge/Token.js";
import type { Token } from "../../bridge/types/Token.js";
import type { ThirdwebClient } from "../../client/client.js";
import { withCache } from "../../utils/promise/withCache.js";

export async function getTokenPrice(
export async function getToken(
client: ThirdwebClient,
tokenAddress: string,
chainId: number,
) {
): Promise<Token> {
return withCache(
async () => {
const result = await tokens({
client,
tokenAddress,
chainId,
});
return result[0]?.priceUsd;
const token = result[0];
if (!token) {
// Attempt to add the token
const tokenResult = await add({
client,
chainId,
tokenAddress,
}).catch(() => {
throw new Error("Token not supported");
});
return tokenResult;
}
return token;
},
{
cacheKey: `get-token-price-${tokenAddress}-${chainId}`,
Expand Down
2 changes: 1 addition & 1 deletion packages/thirdweb/src/pay/utils/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ export type PayOnChainTransactionDetails = {

export type FiatProvider = (typeof FiatProviders)[number];

export const FiatProviders = ["COINBASE", "STRIPE", "TRANSAK"] as const;
export const FiatProviders = ["coinbase", "stripe", "transak"] as const;
Loading
Loading