From de73a8d5d3d53cc79a2528cc06101e817f0c7983 Mon Sep 17 00:00:00 2001 From: Charlie Sibbach Date: Thu, 17 Sep 2026 22:52:56 -0700 Subject: [PATCH 01/11] Enhance PaymentFeePicker and related components to support ITransactionWork - Updated PaymentFeePicker to accept `work` prop for unsigned transaction estimates. - Modified quotePayment method in TransactionService to handle multiple work items. - Integrated work handling in various components including RelayerConfirmModalChrome, TransferTokensModal, and CCTPBridge. - Refactored CancelDelegationModal and SignModals to utilize work for fee estimation. - Improved error handling and memoization for better performance and reliability. --- src/components/PaymentFeePicker.tsx | 53 ++++-- src/components/RelayerConfirmModalChrome.tsx | 4 + src/components/modals/CCTPBridge.tsx | 3 + .../modals/CancelDelegationModal.tsx | 1 + src/components/modals/SignModals.tsx | 11 ++ src/components/modals/TransferTokensModal.tsx | 53 +++++- .../implementations/business/BridgeService.ts | 28 +++- .../business/DelegationService.ts | 61 ++++--- .../business/TransactionService.ts | 2 + .../business/utils/TransactionUtils.ts | 154 ++++++++++++++++-- src/lib/interfaces/business/IBridgeService.ts | 4 +- .../interfaces/business/IDelegationService.ts | 10 ++ .../business/ITransactionService.ts | 4 +- src/lib/interfaces/business/index.ts | 1 + .../business/utils/ITransactionUtils.ts | 5 +- src/wallet/WalletProvider.tsx | 5 + src/wallet/modalTypes.ts | 5 + src/wallet/useWalletBoot.ts | 17 ++ 18 files changed, 366 insertions(+), 55 deletions(-) diff --git a/src/components/PaymentFeePicker.tsx b/src/components/PaymentFeePicker.tsx index 3cd8d95..dd70b54 100644 --- a/src/components/PaymentFeePicker.tsx +++ b/src/components/PaymentFeePicker.tsx @@ -1,7 +1,11 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { EVMAccountAddress, EVMChainId } from "@1shotapi/ows-types"; import { formatUnits } from "viem"; -import type { IPaymentQuote, IPaymentTokenOption } from "../lib/interfaces/business"; +import type { + IPaymentQuote, + IPaymentTokenOption, + ITransactionWork, +} from "../lib/interfaces/business"; import type { IFinalRelayerFee } from "../lib/types/domain/RelayerSendUi"; import { useWallet } from "../wallet/WalletProvider"; import { AssetIcon } from "./AssetIcon"; @@ -19,6 +23,8 @@ export type IPaymentFeePickerMode = "estimate" | "final"; export interface IPaymentFeePickerProps { chainId: EVMChainId; ownerAddress: EVMAccountAddress; + /** ExactCalldata work used for unsigned `relayer_estimate7710Transaction`. */ + work: ITransactionWork | ITransactionWork[]; quote: IPaymentQuote | null; error: string | null; loading: boolean; @@ -66,12 +72,13 @@ function PaymentTokenRow({ /** * Loads payment-token options (USDC preferred) and shows a live fee quote - * with auto-refresh. Exact fee is settled by `relayer_estimate7710Transaction` - * at submit; use mode `final` to show the relayer-settled amount. + * from unsigned `relayer_estimate7710Transaction`. Use mode `final` after the + * signed estimate settles the amount at submit. */ export function PaymentFeePicker({ chainId, ownerAddress, + work, quote, error, loading, @@ -90,15 +97,34 @@ export function PaymentFeePicker({ onQuoteChangeRef.current = onQuoteChange; }, [onQuoteChange]); + const workKey = useMemo(() => { + const items = Array.isArray(work) ? work : [work]; + return items + .map( + (item) => + `${String(item.to)}:${String(item.data || "0x")}:${item.value ?? 0n}`, + ) + .join("|"); + }, [work]); + const getNewQuote = useCallback(async (): Promise => { - const next = await transactionService.quotePayment( - chainId, - ownerAddress, - preferredToken, - ); - onQuoteChangeRef.current(next, null); - return next.feeFormatted; - }, [chainId, ownerAddress, preferredToken, transactionService]); + try { + const next = await transactionService.quotePayment( + chainId, + ownerAddress, + work, + preferredToken, + ); + onQuoteChangeRef.current(next, null); + return next.feeFormatted; + } catch (err: unknown) { + onQuoteChangeRef.current( + null, + err instanceof Error ? err.message : "Failed to quote fee", + ); + throw err; + } + }, [chainId, ownerAddress, preferredToken, transactionService, work]); async function onSelectToken(token: EVMAccountAddress): Promise { setSelectBusy(true); @@ -107,6 +133,7 @@ export function PaymentFeePicker({ const next = await transactionService.quotePayment( chainId, ownerAddress, + work, token, ); onQuoteChange(next, null); @@ -149,7 +176,7 @@ export function PaymentFeePicker({ {feeDisplay} ) : ( diff --git a/src/components/RelayerConfirmModalChrome.tsx b/src/components/RelayerConfirmModalChrome.tsx index 0bea72d..b729464 100644 --- a/src/components/RelayerConfirmModalChrome.tsx +++ b/src/components/RelayerConfirmModalChrome.tsx @@ -1,4 +1,5 @@ import type { EVMAccountAddress, EVMChainId } from "@1shotapi/ows-types"; +import type { ITransactionWork } from "../lib/interfaces/business"; import { PaymentFeePicker } from "./PaymentFeePicker"; import type { useRelayerConfirmSubmit } from "./useRelayerConfirmSubmit"; @@ -8,10 +9,12 @@ type RelayerSubmitState = ReturnType; export function RelayerConfirmModalChrome({ chainId, ownerAddress, + work, submit, }: { chainId: EVMChainId; ownerAddress: EVMAccountAddress; + work: ITransactionWork | ITransactionWork[]; submit: RelayerSubmitState; }) { return ( @@ -24,6 +27,7 @@ export function RelayerConfirmModalChrome({ diff --git a/src/components/modals/SignModals.tsx b/src/components/modals/SignModals.tsx index d3793b9..5ecdc94 100644 --- a/src/components/modals/SignModals.tsx +++ b/src/components/modals/SignModals.tsx @@ -498,6 +498,16 @@ export function SendTransactionModal({ ) : legacyPhase === "signing" ? ( @@ -660,6 +670,7 @@ export function ConfirmTransferModal({ ) : legacyPhase === "signing" ? ( diff --git a/src/components/modals/TransferTokensModal.tsx b/src/components/modals/TransferTokensModal.tsx index 98229da..b4463a8 100644 --- a/src/components/modals/TransferTokensModal.tsx +++ b/src/components/modals/TransferTokensModal.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { encodeFunctionData, erc20Abi, @@ -13,7 +13,7 @@ import { } from "@1shotapi/ows-types"; import type { TrackedAsset } from "../../lib/types/domain"; import { EAssetType } from "../../lib/types/enum/EAssetType"; -import type { IPaymentQuote } from "../../lib/interfaces/business"; +import type { IPaymentQuote, ITransactionWork } from "../../lib/interfaces/business"; import { useStyle } from "../../style/StyleProvider"; import { chainTechnologyFor } from "../../wallet/activeAddress"; import { useLiveTrackedBalance } from "../../wallet/useLiveTrackedBalance"; @@ -111,6 +111,46 @@ export function TransferTokensModal({ [amount, balance, decimals, copy], ); + const relayerWork = useMemo((): ITransactionWork | null => { + if ( + !useRelayer || + asset.type !== EAssetType.Erc20 || + !recipient || + !amount.trim() || + amountError + ) { + return null; + } + let parsed: bigint; + try { + parsed = parseUnits(amount.trim(), decimals); + } catch { + return null; + } + if (parsed <= 0n) { + return null; + } + return { + to: asset.address, + data: HexString( + encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [recipient as EVMAccountAddress, parsed], + }) as Hex, + ), + value: 0n, + }; + }, [ + amount, + amountError, + asset.address, + asset.type, + decimals, + recipient, + useRelayer, + ]); + const onQuoteChange = useCallback( (next: IPaymentQuote | null, error: string | null) => { setQuote(next); @@ -119,6 +159,12 @@ export function TransferTokensModal({ [], ); + // Drop stale quotes whenever ExactCalldata work changes (or becomes incomplete). + useEffect(() => { + setQuote(null); + setQuoteError(null); + }, [relayerWork]); + const canSubmit = useMemo(() => { if (busy || asset.type !== EAssetType.Erc20) { return false; @@ -270,10 +316,11 @@ export function TransferTokensModal({ invalidAddressError={copy.invalidAddressError} disabled={busy} /> - {useRelayer && evmAddress ? ( + {useRelayer && evmAddress && relayerWork ? ( { + const resolved = await this.resolveCancelDelegation(params); + return resolved.work; + } + async cancelDelegation( params: ICancelDelegationParams, ): Promise { const chain = await this.requireRelayerChain(params.chainId); + const { stored, work } = await this.resolveCancelDelegation(params); + + const result = await this.transactionUtils.sendViaRelayer({ + chainId: params.chainId, + work, + paymentToken: params.paymentToken, + feeAtoms: params.feeAtoms, + relayerUrl: chain.relayerUrl, + prefetchRelayerVaultAssertion: true, + retainDisplayDuringSubmit: true, + onAwaitingConfirmation: params.onAwaitingConfirmation, + onFinalFeeRequired: params.onFinalFeeRequired, + }); + + let deletedDelegationId: ICancelDelegationResult["deletedDelegationId"]; + if (stored) { + await this.delegationRepository.deleteDelegation( + stored.delegationId, + ); + deletedDelegationId = stored.delegationId; + } + + return { ...result, deletedDelegationId }; + } + private async resolveCancelDelegation(params: { + chainId: EVMChainId; + stored?: IStoredDelegation; + permissionContext?: HexString; + }): Promise<{ stored?: IStoredDelegation; work: ITransactionWork }> { let stored = params.stored; let mmDelegation: Delegation; @@ -250,31 +288,14 @@ export class DelegationService implements IDelegationService { delegation: mmDelegation, }) as Hex; - const result = await this.transactionUtils.sendViaRelayer({ - chainId: params.chainId, + return { + stored, work: { to: EVMAccountAddress(getAddress(environment.DelegationManager)), data: HexString(disableCalldata), value: 0n, }, - paymentToken: params.paymentToken, - feeAtoms: params.feeAtoms, - relayerUrl: chain.relayerUrl, - prefetchRelayerVaultAssertion: true, - retainDisplayDuringSubmit: true, - onAwaitingConfirmation: params.onAwaitingConfirmation, - onFinalFeeRequired: params.onFinalFeeRequired, - }); - - let deletedDelegationId: ICancelDelegationResult["deletedDelegationId"]; - if (stored) { - await this.delegationRepository.deleteDelegation( - stored.delegationId, - ); - deletedDelegationId = stored.delegationId; - } - - return { ...result, deletedDelegationId }; + }; } async getSupportedExecutionPermissions(): Promise { diff --git a/src/lib/implementations/business/TransactionService.ts b/src/lib/implementations/business/TransactionService.ts index 88d013e..679d761 100644 --- a/src/lib/implementations/business/TransactionService.ts +++ b/src/lib/implementations/business/TransactionService.ts @@ -54,11 +54,13 @@ export class TransactionService implements ITransactionService { quotePayment( chainId: EVMChainId, owner: EVMAccountAddress, + work: ITransactionWork | ITransactionWork[], preferredToken?: EVMAccountAddress, ): Promise { return this.options.transactionUtils.quotePayment( chainId, owner, + work, preferredToken, ); } diff --git a/src/lib/implementations/business/utils/TransactionUtils.ts b/src/lib/implementations/business/utils/TransactionUtils.ts index 4ce4a7c..66b63e8 100644 --- a/src/lib/implementations/business/utils/TransactionUtils.ts +++ b/src/lib/implementations/business/utils/TransactionUtils.ts @@ -68,12 +68,29 @@ import "../../utils/registerSmartAccountsEnvironments"; const STATELESS_DELEGATOR_IMPL = "0x63c0c19a282a1B52b07dD5a65b58948A07DAE32B" as const; +/** + * 65-byte zero signature for `relayer_estimate7710Transaction` unsigned + * estimates. Valid ECDSA length so the DelegatorEstimateShim's + * ECDSA.tryRecover pays ecrecover precompile gas (prefer over bytes32(0)). + */ +export const PLACEHOLDER_DELEGATION_SIGNATURE_65_ZERO = + "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" as const; + /** IndexedDB key for the client delegation-binding value (not localStorage). */ const DELEGATION_BINDING_IDB_KEY = "oneshot.dbind"; const LEGACY_DELEGATION_SECRET_KEY = "oneshot.delegationSecret"; const POLL_MS = 1000; const MAX_POLL_ATTEMPTS = 180; +type ExactCalldataDelegationArgs = { + smartAccount: Awaited>; + delegate: EVMAccountAddress; + target: EVMAccountAddress; + value: bigint; + callData: Hex; + chainIdNumber: number; +}; + export type TransactionUtilsOptions = { chainRepository: IChainRepository; relayerRepository: IOneshotRelayerRepository; @@ -219,8 +236,14 @@ export class TransactionUtils implements ITransactionUtils { async quotePayment( chainId: EVMChainId, owner: EVMAccountAddress, + work: ITransactionWork | ITransactionWork[], preferredToken?: EVMAccountAddress, ): Promise { + const workItems = Array.isArray(work) ? work : [work]; + if (workItems.length === 0) { + throw new Error("quotePayment requires at least one work item"); + } + const chain = await this.requireRelayerChain(chainId); const capabilities = await this.options.relayerRepository.getCapabilities( chain.relayerUrl, @@ -250,10 +273,99 @@ export class TransactionUtils implements ITransactionUtils { throw new Error("No relayer payment token with a positive balance"); } - // Confirm UI uses a conservative mock (≥ typical $0.01 minFee). The real fee - // comes from relayer_estimate7710Transaction at submit — not getFeeData - // (whose minFee is a human decimal string, not atoms). - const feeAtoms = makeTokenAmount(parseUnits("0.01", selected.decimals)); + // Seed fee ExactCalldata with typical minFee; estimate returns the real + // requiredPaymentAmount (often higher on Ethereum). + const seedFeeAtoms = makeTokenAmount( + parseUnits("0.01", selected.decimals), + ); + + const chainIdNumber = Number(BigInt(chainId)); + const viemAccount = await this.getViemAccount(owner); + const smartAccount = await toMetaMaskSmartAccount({ + client: client as never, + implementation: Implementation.Stateless7702, + address: owner, + signer: { account: viemAccount }, + }); + + const feeCalldata = HexStringCompat( + encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [capabilities.feeCollector, seedFeeAtoms], + }), + ); + + const feeDelegation = this.createUnsignedExactCalldataDelegation({ + smartAccount, + delegate: capabilities.targetAddress, + target: selected.address, + value: 0n, + callData: feeCalldata, + chainIdNumber, + }); + const workDelegations = workItems.map((item) => + this.createUnsignedExactCalldataDelegation({ + smartAccount, + delegate: capabilities.targetAddress, + target: item.to, + value: item.value ?? 0n, + callData: (item.data || "0x") as Hex, + chainIdNumber, + }), + ); + + const params: IRelayer7710Params = { + chainId: chainIdNumber.toString(10), + transactions: [ + { + permissionContext: [toRelayerJson(feeDelegation)], + executions: [ + { + target: selected.address, + value: "0", + data: feeCalldata as HexString, + }, + ], + }, + ...workItems.map((item, index) => { + const value = item.value ?? 0n; + return { + permissionContext: [toRelayerJson(workDelegations[index])], + executions: [ + { + target: item.to, + value: value === 0n ? "0" : `0x${value.toString(16)}`, + data: (item.data || "0x") as HexString, + }, + ], + }; + }), + ], + }; + + console.debug( + "[business/TransactionUtils] quotePayment unsigned estimate", + { + chainId, + paymentToken: selected.address, + workCount: workItems.length, + }, + ); + + const estimate = + await this.options.relayerRepository.estimate7710Transaction( + chain.relayerUrl, + params, + ); + + if (!estimate.success || !estimate.requiredPaymentAmount) { + throw new Error( + estimate.error ?? "relayer_estimate7710Transaction failed", + ); + } + + const feeAtoms = tokenAmountFromAtomString(estimate.requiredPaymentAmount); return { tokens, @@ -559,7 +671,7 @@ export class TransactionUtils implements ITransactionUtils { if ( estimate.success && estimate.requiredPaymentAmount && - tokenAmountFromAtomString(estimate.requiredPaymentAmount) !== feeAtoms + tokenAmountFromAtomString(estimate.requiredPaymentAmount) > feeAtoms ) { feeAtoms = tokenAmountFromAtomString(estimate.requiredPaymentAmount); const paymentTokenMeta = capabilities.tokens.find( @@ -604,6 +716,8 @@ export class TransactionUtils implements ITransactionUtils { // mint a new quote while leaving feeAtoms at required₁ — payment/context // mismatch under rising gas. Match the UI fee-bump path (no re-estimate). } + // If signed required ≤ quoted feeAtoms, keep the already-signed fee + // ExactCalldata (slight overpay is fine; no AdjustFee ceremony). if (!estimate.success) { throw new Error( @@ -665,19 +779,14 @@ export class TransactionUtils implements ITransactionUtils { } } - private async createAndSignExactCalldataDelegation(args: { - smartAccount: Awaited>; - delegate: EVMAccountAddress; - target: EVMAccountAddress; - value: bigint; - callData: Hex; - chainIdNumber: number; - }): Promise { + private createExactCalldataDelegation( + args: ExactCalldataDelegationArgs, + ): ReturnType { const { smartAccount, delegate, target, value, callData } = args; const salt = randomSalt32(); const selector = methodSelector(callData); - const delegation = createDelegation({ + return createDelegation({ to: getAddress(delegate), from: smartAccount.address, environment: smartAccount.environment, @@ -690,6 +799,23 @@ export class TransactionUtils implements ITransactionUtils { valueLte: { maxValue: value }, }, }); + } + + private createUnsignedExactCalldataDelegation( + args: ExactCalldataDelegationArgs, + ): unknown { + const delegation = this.createExactCalldataDelegation(args); + return { + ...delegation, + signature: PLACEHOLDER_DELEGATION_SIGNATURE_65_ZERO, + }; + } + + private async createAndSignExactCalldataDelegation( + args: ExactCalldataDelegationArgs, + ): Promise { + const { smartAccount } = args; + const delegation = this.createExactCalldataDelegation(args); // Callers must already have the flyout open (SignHelper.withDisplay for // eth_sendTransaction, plus sendViaRelayer.ensureDisplay for size). Do not diff --git a/src/lib/interfaces/business/IBridgeService.ts b/src/lib/interfaces/business/IBridgeService.ts index b58472f..ca71b40 100644 --- a/src/lib/interfaces/business/IBridgeService.ts +++ b/src/lib/interfaces/business/IBridgeService.ts @@ -8,7 +8,7 @@ import type { ECctpTransferSpeed } from "../../types/enum/ECctpTransferSpeed"; import type { KnownAsset } from "../../types/domain/KnownAsset"; import type { SupportedChain } from "../../types/domain/SupportedChain"; import type { ICctpInFlightBurn } from "../data/ICircleRepository"; -import type { IPaymentQuote } from "./ITransactionService"; +import type { IPaymentQuote, ITransactionWork } from "./ITransactionService"; import type { TokenAmount } from "../../types/primitives"; export interface ICctpQuoteParams { @@ -35,6 +35,8 @@ export interface ICctpBridgeQuote { netReceivedAtoms: bigint; paymentQuote: IPaymentQuote; burnCalldata: HexString; + /** Approve (if needed) + burn ExactCalldata for fee estimate / submit. */ + relayerWork: ITransactionWork[]; } export interface ICctpBridgePayment { diff --git a/src/lib/interfaces/business/IDelegationService.ts b/src/lib/interfaces/business/IDelegationService.ts index de4d128..785f56e 100644 --- a/src/lib/interfaces/business/IDelegationService.ts +++ b/src/lib/interfaces/business/IDelegationService.ts @@ -12,6 +12,7 @@ import type { IStoredDelegation } from "../../types/domain/StoredDelegation"; import type { DelegationId } from "../../types/primitives/DelegationId"; import type { IRelayerSendUiCallbacks } from "../../types/domain/RelayerSendUi"; import type { TokenAmount } from "../../types/primitives"; +import type { ITransactionWork } from "./ITransactionService"; /** Phase-1 EIP-7715 permission type (ERC-20 period transfer). */ export const ERC20_TOKEN_PERIODIC = "erc20-token-periodic" as const; @@ -56,6 +57,12 @@ export interface ICancelDelegationParams extends IRelayerSendUiCallbacks { permissionContext?: HexString; } +export type IBuildCancelWorkParams = { + chainId: EVMChainId; + stored?: IStoredDelegation; + permissionContext?: HexString; +}; + export interface ICancelDelegationResult extends ISendTransactionResult { /** Set when a known vault entry was deleted after on-chain cancel. */ deletedDelegationId?: DelegationId; @@ -72,6 +79,9 @@ export interface IDelegationService { params: ICreateExecutionPermissionsParams, ): Promise; + /** ExactCalldata work for unsigned fee estimate before cancel confirm. */ + buildCancelWork(params: IBuildCancelWorkParams): Promise; + cancelDelegation( params: ICancelDelegationParams, ): Promise; diff --git a/src/lib/interfaces/business/ITransactionService.ts b/src/lib/interfaces/business/ITransactionService.ts index 2b78ad1..f7191f4 100644 --- a/src/lib/interfaces/business/ITransactionService.ts +++ b/src/lib/interfaces/business/ITransactionService.ts @@ -60,11 +60,13 @@ export interface ITransactionService { /** * Prefer USDC with balance, then USDT, else first token with balance. * When `preferredToken` is set, use it if present in capabilities. - * Returns a mock fee for confirm UI; submit uses `relayer_estimate7710Transaction`. + * Quotes via unsigned `relayer_estimate7710Transaction` (placeholder + * signatures) so confirm UI shows an accurate fee before passkey sign. */ quotePayment( chainId: EVMChainId, owner: EVMAccountAddress, + work: ITransactionWork | ITransactionWork[], preferredToken?: EVMAccountAddress, ): Promise; diff --git a/src/lib/interfaces/business/index.ts b/src/lib/interfaces/business/index.ts index 2aafa10..00b4f47 100644 --- a/src/lib/interfaces/business/index.ts +++ b/src/lib/interfaces/business/index.ts @@ -22,6 +22,7 @@ export type { } from "./IBitcoinService"; export { IBitcoinServiceType } from "./IBitcoinService"; export type { + IBuildCancelWorkParams, ICancelDelegationParams, ICancelDelegationResult, ICreateExecutionPermissionParams, diff --git a/src/lib/interfaces/business/utils/ITransactionUtils.ts b/src/lib/interfaces/business/utils/ITransactionUtils.ts index ccdab05..1f9f65a 100644 --- a/src/lib/interfaces/business/utils/ITransactionUtils.ts +++ b/src/lib/interfaces/business/utils/ITransactionUtils.ts @@ -32,11 +32,14 @@ export interface ITransactionUtils { /** * Prefer USDC with balance, then USDT, else first token with balance. - * Mock fee for confirm UI; submit uses `relayer_estimate7710Transaction`. + * Builds unsigned ExactCalldata fee+work delegations (placeholder + * signatures) and calls `relayer_estimate7710Transaction` so the confirm + * UI shows `requiredPaymentAmount` before any passkey ceremony. */ quotePayment( chainId: EVMChainId, owner: EVMAccountAddress, + work: ITransactionWork | ITransactionWork[], preferredToken?: EVMAccountAddress, ): Promise; diff --git a/src/wallet/WalletProvider.tsx b/src/wallet/WalletProvider.tsx index 32baa10..f3f8579 100644 --- a/src/wallet/WalletProvider.tsx +++ b/src/wallet/WalletProvider.tsx @@ -695,6 +695,10 @@ export function WalletProvider({ children }: { children: ReactNode }) { if (!owner) { throw new Error("Wallet address is required to cancel a permission"); } + const cancelWork = await delegationService.buildCancelWork({ + chainId: stored.chainId, + stored, + }); const transactionHash = await pushModal( ({ id, resolve, reject }) => ({ id, @@ -704,6 +708,7 @@ export function WalletProvider({ children }: { children: ReactNode }) { chainName: chain.label, chainId: stored.chainId, ownerAddress: owner, + work: cancelWork, }, execute: async (payment: IRelayerConfirmSendResult, ui) => { const result = await delegationService.cancelDelegation({ diff --git a/src/wallet/modalTypes.ts b/src/wallet/modalTypes.ts index 00660b4..e790f50 100644 --- a/src/wallet/modalTypes.ts +++ b/src/wallet/modalTypes.ts @@ -22,6 +22,7 @@ import type { } from "../circle/cctpBridgeTypes"; import type { TokenAmount } from "../lib/types/primitives"; import type { IRelayerSendUiCallbacks } from "../lib/types/domain/RelayerSendUi"; +import type { ITransactionWork } from "../lib/interfaces/business/ITransactionService"; export type WalletSetupChoice = "login" | "create" | "import" | "cancel"; @@ -37,6 +38,8 @@ export interface IConfirmTransferRequest { chainId: EVMChainId; ownerAddress: EVMAccountAddress; useRelayer: boolean; + /** ExactCalldata work for unsigned fee estimate (host send payload). */ + work: ITransactionWork; } /** Relayer payment selection from TX confirm UI (before execute). */ @@ -77,6 +80,8 @@ export interface ICancelDelegationConfirmRequest { chainName: string; chainId: EVMChainId; ownerAddress: EVMAccountAddress; + /** ExactCalldata work for unsigned fee estimate. */ + work: ITransactionWork; } export type ModalRequest = diff --git a/src/wallet/useWalletBoot.ts b/src/wallet/useWalletBoot.ts index c379e5c..1e019b8 100644 --- a/src/wallet/useWalletBoot.ts +++ b/src/wallet/useWalletBoot.ts @@ -499,6 +499,11 @@ export function useWalletBoot({ "Wallet address is required to cancel a permission", ); } + const cancelWork = await delegationService.buildCancelWork({ + chainId, + ...(stored ? { stored } : {}), + permissionContext: params.permissionContext, + }); const domain = stored?.hostDomain ?? transactionUtils.resolveHostDomain(); @@ -517,6 +522,7 @@ export function useWalletBoot({ chainName: chain.label, chainId, ownerAddress: owner, + work: cancelWork, }, execute: async (payment: IRelayerConfirmSendResult, ui) => { const result = await delegationService.cancelDelegation({ @@ -812,6 +818,16 @@ export function useWalletBoot({ }; let hash: EVMTransactionHash; + const valueRaw = String(request.value); + const workValue = + valueRaw && valueRaw !== "0x0" && valueRaw !== "0x" + ? BigInt(valueRaw) + : undefined; + const sendWork = { + to: request.to!, + data: request.data, + value: workValue, + }; if (transfer) { const known = await knownAssetRepository.getKnownAsset( request.chainId, @@ -849,6 +865,7 @@ export function useWalletBoot({ chainId: request.chainId, ownerAddress: request.address, useRelayer, + work: sendWork, }, execute: executeSend, resolve, From 07e401100776be899d223df0cb138c277ee8eea2 Mon Sep 17 00:00:00 2001 From: Charlie Sibbach Date: Thu, 17 Sep 2026 22:55:48 -0700 Subject: [PATCH 02/11] Prevent hiding the wallet after in-wallet Send. --- src/lib/implementations/business/utils/TransactionUtils.ts | 7 +++++-- src/lib/types/domain/RelayerSendUi.ts | 6 +++++- src/wallet/WalletProvider.tsx | 7 ++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/lib/implementations/business/utils/TransactionUtils.ts b/src/lib/implementations/business/utils/TransactionUtils.ts index 66b63e8..ed790e2 100644 --- a/src/lib/implementations/business/utils/TransactionUtils.ts +++ b/src/lib/implementations/business/utils/TransactionUtils.ts @@ -773,8 +773,11 @@ export class TransactionUtils implements ITransactionUtils { throw pollError; } } catch (error) { - // ensureDisplay may have left the flyout open; hideDisplay is idempotent. - await this.options.owsProvider.hideDisplay(); + // Host-initiated sends collapse the flyout on failure; in-wallet flows + // (TransferTokensModal, cancel) keep the open display. + if (!retainDisplayDuringSubmit) { + await this.options.owsProvider.hideDisplay(); + } throw error; } } diff --git a/src/lib/types/domain/RelayerSendUi.ts b/src/lib/types/domain/RelayerSendUi.ts index ea2e5d2..240e7e1 100644 --- a/src/lib/types/domain/RelayerSendUi.ts +++ b/src/lib/types/domain/RelayerSendUi.ts @@ -17,6 +17,10 @@ export type IRelayerSendUiCallbacks = { onFinalFeeRequired?: (fee: IFinalRelayerFee) => Promise; /** After passkey ceremonies, before relayer submit/poll. */ onAwaitingConfirmation?: () => void; - /** Keep the flyout open through submit/poll (in-wallet cancel flows). */ + /** + * Keep the flyout open through submit/poll and on error. + * Use for in-wallet flows (TransferTokensModal, cancel); omit for host + * eth_sendTransaction so the wallet collapses after the last passkey. + */ retainDisplayDuringSubmit?: boolean; }; diff --git a/src/wallet/WalletProvider.tsx b/src/wallet/WalletProvider.tsx index f3f8579..7bdd806 100644 --- a/src/wallet/WalletProvider.tsx +++ b/src/wallet/WalletProvider.tsx @@ -572,7 +572,12 @@ export function WalletProvider({ children }: { children: ReactNode }) { const result = await transactionService.sendTransaction( chainId, { to, data, value }, - payment, + { + ...payment, + // Flyout is already open for in-wallet Send — do not requestHide + // after passkey (host eth_sendTransaction defaults to hide). + retainDisplayDuringSubmit: true, + }, ); await onSigningAuthenticated(); return result.transactionHash; From 625f6e57be4ab53b7cb8c8fb91a4cea87fe2bdac Mon Sep 17 00:00:00 2001 From: Charlie Sibbach Date: Sun, 20 Sep 2026 15:53:33 -0700 Subject: [PATCH 03/11] Change to actual release version of Appkit --- package-lock.json | 401 ++++++++++-------- package.json | 2 +- src/components/OnrampView.tsx | 2 +- .../implementations/utils/CircleProvider.ts | 2 +- src/lib/interfaces/utils/ICircleProvider.ts | 2 +- 5 files changed, 230 insertions(+), 179 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb942f5..6971ee7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@1shotapi/ows-signer-utils": "^0.6.2", "@1shotapi/ows-types": "^0.8.0", "@1shotapi/ows-wallet-utils": "^0.5.1", - "@crcl-main/app-kit": "^1.14.0-canary-feature-onramp-kit-sdk.1789055534", + "@circle-fin/app-kit": "^1.15.2", "@fontsource-variable/geist": "^5.3.0", "@metamask/smart-accounts-kit": "^2.0.0", "@reown/walletkit": "^1.5.6", @@ -1025,103 +1025,18 @@ "win32" ] }, - "node_modules/@coral-xyz/anchor": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.31.1.tgz", - "integrity": "sha512-QUqpoEK+gi2S6nlYc2atgT2r41TT3caWr/cPUEL8n8Md9437trZ68STknq897b82p5mW0XrTBNOzRbmIRJtfsA==", - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "@coral-xyz/anchor-errors": "^0.31.1", - "@coral-xyz/borsh": "^0.31.1", - "@noble/hashes": "^1.3.1", - "@solana/web3.js": "^1.69.0", - "bn.js": "^5.1.2", - "bs58": "^4.0.1", - "buffer-layout": "^1.2.2", - "camelcase": "^6.3.0", - "cross-fetch": "^3.1.5", - "eventemitter3": "^4.0.7", - "pako": "^2.0.3", - "superstruct": "^0.15.4", - "toml": "^3.0.0" - }, - "engines": { - "node": ">=17" - } - }, - "node_modules/@coral-xyz/anchor-errors": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz", - "integrity": "sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/@coral-xyz/anchor/node_modules/base-x": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", - "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@coral-xyz/anchor/node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "license": "MIT", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/@coral-xyz/anchor/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@coral-xyz/anchor/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/@coral-xyz/borsh": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.31.1.tgz", - "integrity": "sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw==", - "license": "Apache-2.0", - "dependencies": { - "bn.js": "^5.1.2", - "buffer-layout": "^1.2.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@solana/web3.js": "^1.69.0" - } - }, - "node_modules/@crcl-main/app-kit": { - "version": "1.14.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/app-kit/-/app-kit-1.14.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-04GjiZqTZAiNz+KXvafEk/4TS3pXEWfZPk6HUoyKzIwpMsz8JD6ixNeBy4wuBLEcSCNSJd1A9IF25AWoHKODZQ==", - "dependencies": { + "node_modules/@circle-fin/app-kit": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@circle-fin/app-kit/-/app-kit-1.15.2.tgz", + "integrity": "sha512-7+MaJVcdUJ5Mr1Tonz5/b56gm1F1K2HML04iOimMUmKT3q3mfixNKztAZVH2bv+K6ZXMHXQ0SkI7ZYraOPsseg==", + "dependencies": { + "@circle-fin/bridge-kit": "1.15.1", + "@circle-fin/earn-kit": "1.7.0", + "@circle-fin/onramp-kit": "1.0.1", + "@circle-fin/provider-gateway-v1": "1.5.0", + "@circle-fin/swap-kit": "1.7.0", + "@circle-fin/unified-balance-kit": "1.7.0", "@coral-xyz/anchor": "^0.31.1", - "@crcl-main/bridge-kit": "1.14.1-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/earn-kit": "1.6.1-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/onramp-kit": "0.0.1-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/provider-gateway-v1": "1.4.0-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/swap-kit": "1.6.1-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/unified-balance-kit": "1.6.0-canary-feature-onramp-kit-sdk.1789055534", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1140,7 +1055,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/app-kit/node_modules/@noble/curves": { + "node_modules/@circle-fin/app-kit/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1152,7 +1067,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/app-kit/node_modules/@noble/hashes": { + "node_modules/@circle-fin/app-kit/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1164,7 +1079,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/app-kit/node_modules/pino": { + "node_modules/@circle-fin/app-kit/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1186,7 +1101,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/app-kit/node_modules/zod": { + "node_modules/@circle-fin/app-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1195,13 +1110,14 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/bridge-kit": { - "version": "1.14.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/bridge-kit/-/bridge-kit-1.14.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-oBze4ODsutrppjkhL0jq1JtxdFFgo4qYDSK3yScrDiE3CcohyOom5S8MvN/QY72xUteQKzCiDgXWpQqsB7PaSQ==", + "node_modules/@circle-fin/bridge-kit": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@circle-fin/bridge-kit/-/bridge-kit-1.15.1.tgz", + "integrity": "sha512-CnMoBmZJL4NXYDZ11uHxdQJ+9/h9JQSJPOXmFw0EJRn45m0NqWJFWJgyOH+1Kmt8csDOcBgNLWii4WSn3wS4qA==", "dependencies": { - "@crcl-main/provider-cctp-v2": "1.13.0-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/provider-fee-v1": "0.3.0-canary-feature-onramp-kit-sdk.1789055534", + "@circle-fin/provider-cctp-v2": "^1.14.0", + "@circle-fin/provider-cctpx": "^1.0.0", + "@circle-fin/provider-fee-v1": "^0.3.1", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1216,7 +1132,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/bridge-kit/node_modules/pino": { + "node_modules/@circle-fin/bridge-kit/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1238,7 +1154,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/bridge-kit/node_modules/zod": { + "node_modules/@circle-fin/bridge-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1247,12 +1163,12 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/earn-kit": { - "version": "1.6.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/earn-kit/-/earn-kit-1.6.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-5YwMUf2PK8VsFlOoQv38ykp8gDxWbVqQkHFCgb9OaqfHmmPn9f/xwQDdluvAkuA85pj2UI9iJKc00VZyT50a5g==", + "node_modules/@circle-fin/earn-kit": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@circle-fin/earn-kit/-/earn-kit-1.7.0.tgz", + "integrity": "sha512-Yb04OT4UEX6IJoCApkWuuB6VTL8+dJpyvKb0ZcAKokKKvvnr8SwuWUlx1yYqd2x6Y9GJR6GyfYz5PZx1zyZiQA==", "dependencies": { - "@crcl-main/provider-earn-service": "1.5.1-canary-feature-onramp-kit-sdk.1789055534", + "@circle-fin/provider-earn-service": "^1.6.0", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1268,7 +1184,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/earn-kit/node_modules/pino": { + "node_modules/@circle-fin/earn-kit/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1290,7 +1206,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/earn-kit/node_modules/zod": { + "node_modules/@circle-fin/earn-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1299,10 +1215,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/onramp-kit": { - "version": "0.0.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/onramp-kit/-/onramp-kit-0.0.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-xEpJA3oRgNGOKAZaDcjobmEZnxfDunFjNeWH3gZzCJJrj4mboNHPJVOJMpHPLOErfSBuilZcU+sQuvrwAJ0OrA==", + "node_modules/@circle-fin/onramp-kit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@circle-fin/onramp-kit/-/onramp-kit-1.0.1.tgz", + "integrity": "sha512-N8Mok3ybsEFl2+ZoDmAjjAPpqasQQu6FHIqPMwIups0eqaOEvu7I+GCg3P+wxelqm9eQLSZORXt6tUcUH9Ojzg==", "dependencies": { "pino": "10.1.0", "zod": "3.25.67" @@ -1311,7 +1227,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/onramp-kit/node_modules/pino": { + "node_modules/@circle-fin/onramp-kit/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1333,7 +1249,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/onramp-kit/node_modules/zod": { + "node_modules/@circle-fin/onramp-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1342,10 +1258,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-cctp-v2": { - "version": "1.13.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-cctp-v2/-/provider-cctp-v2-1.13.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-Q6HmGkmeGee4OqogbJ9cP/hgfIlj2NNIHh03F5i20Oq8FGag81jbFlaSGTNzMTFMyRSy//zyc8vSANkmeBY2tA==", + "node_modules/@circle-fin/provider-cctp-v2": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-cctp-v2/-/provider-cctp-v2-1.14.0.tgz", + "integrity": "sha512-AX4fQueRft5TfS6XnYjPkHa6El+Dyf7Q2SyifLC6sfOT1kh2FdMvPKc4cWMdn5cBVQczDXlgXiP+PlOSrpK2qw==", "dependencies": { "@coral-xyz/anchor": "^0.31.1", "@ethersproject/abi": "^5.8.0", @@ -1372,7 +1288,7 @@ } } }, - "node_modules/@crcl-main/provider-cctp-v2/node_modules/@noble/curves": { + "node_modules/@circle-fin/provider-cctp-v2/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1384,7 +1300,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-cctp-v2/node_modules/@noble/hashes": { + "node_modules/@circle-fin/provider-cctp-v2/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1396,7 +1312,57 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-cctp-v2/node_modules/pino": { + "node_modules/@circle-fin/provider-cctp-v2/node_modules/pino": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", + "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/@circle-fin/provider-cctp-v2/node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@circle-fin/provider-cctpx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-cctpx/-/provider-cctpx-1.0.1.tgz", + "integrity": "sha512-mGbnNJTAj4H26NAZxZr8RyxQtMS2nNwWsl1hDzXau0OQARKieCQV/GIwhDXeVI+BzfgBbFLnQOtrAmkWV3owVA==", + "dependencies": { + "@ethersproject/abi": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/units": "^5.8.0", + "@solana/web3.js": "^1.98.4", + "abitype": "^1.1.0", + "bs58": "6.0.0", + "pino": "10.1.0", + "zod": "3.25.67" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@circle-fin/provider-cctpx/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1418,7 +1384,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/provider-cctp-v2/node_modules/zod": { + "node_modules/@circle-fin/provider-cctpx/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1427,10 +1393,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-earn-service": { - "version": "1.5.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-earn-service/-/provider-earn-service-1.5.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-YwP9w5ly42cgW1GngCxCfVqLZh0s1INa2ZpzxgKEzsetKUvvIPhyVjGulY7czYeGj5KKHYZ2ZXQqrC9T5qLSpQ==", + "node_modules/@circle-fin/provider-earn-service": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-earn-service/-/provider-earn-service-1.6.0.tgz", + "integrity": "sha512-XPhnAMHfANPdA/ENtF87xykKrp2TUezTc/jzJYzdagwV4JKpNmKN8XdJOUFNekS3QEff+ab7jyC131VywUrzIQ==", "dependencies": { "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", @@ -1447,7 +1413,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/provider-earn-service/node_modules/pino": { + "node_modules/@circle-fin/provider-earn-service/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1469,7 +1435,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/provider-earn-service/node_modules/zod": { + "node_modules/@circle-fin/provider-earn-service/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1478,10 +1444,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-fee-v1": { - "version": "0.3.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-fee-v1/-/provider-fee-v1-0.3.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-3TFMhB3BQgP632LEN7ZK1GiqeX2aTtwEXR7FdksKOjPwTCrfrzvH9D76aYGzLn4SBR9tV+nQjIQnzJwz5GWz8Q==", + "node_modules/@circle-fin/provider-fee-v1": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-fee-v1/-/provider-fee-v1-0.3.1.tgz", + "integrity": "sha512-KHWU3pj7RLIw1QQ7EJE4SWaJbmEtuz0Vs9RjANWBkC+sI/PjxnzCTEOraFdNAK3QpxftZoDX21dqT6v/a0c4YA==", "dependencies": { "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", @@ -1494,7 +1460,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/provider-fee-v1/node_modules/zod": { + "node_modules/@circle-fin/provider-fee-v1/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1503,10 +1469,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-gateway-v1": { - "version": "1.4.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-gateway-v1/-/provider-gateway-v1-1.4.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-dGq3koCcmVP+Zh1ez0dupDN/Sb0bMt7h+WfWQJGpMcJao/dfGjf3Dx8X366CWgxlYM/emD0RVPEYw7UcakJVxw==", + "node_modules/@circle-fin/provider-gateway-v1": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-gateway-v1/-/provider-gateway-v1-1.5.0.tgz", + "integrity": "sha512-NiEISO6jKbc4fmDG1Qhd5vma5VA3S1o7uz/U4hW9L/5nAnW5LZJfO1SrfX1jHQfgnmvY79qR3Q7Qi0TGDbuzIg==", "dependencies": { "@coral-xyz/anchor": "^0.31.1", "@ethersproject/abi": "^5.8.0", @@ -1526,7 +1492,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/provider-gateway-v1/node_modules/@noble/curves": { + "node_modules/@circle-fin/provider-gateway-v1/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1538,7 +1504,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-gateway-v1/node_modules/@noble/hashes": { + "node_modules/@circle-fin/provider-gateway-v1/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1550,7 +1516,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-gateway-v1/node_modules/pino": { + "node_modules/@circle-fin/provider-gateway-v1/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1572,7 +1538,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/provider-gateway-v1/node_modules/zod": { + "node_modules/@circle-fin/provider-gateway-v1/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1581,10 +1547,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-stablecoin-service-swap": { - "version": "1.5.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-stablecoin-service-swap/-/provider-stablecoin-service-swap-1.5.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-lgwYtOhKQ61kHOtkoV5YtPzLjSx5xrUrE2UwK4K0q84ecoMF6oS6r4q6SHNeopVHxakaDMHVM7ZHUghLGz2KEA==", + "node_modules/@circle-fin/provider-stablecoin-service-swap": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-stablecoin-service-swap/-/provider-stablecoin-service-swap-1.6.0.tgz", + "integrity": "sha512-WVA6LY4rYgSCXoVd0ukbTQXjX9RwbKzoq8IJzr9kqSKUllTMMrf449G94WUZKVihSUGs4uPUkhlDBDH2ZVwRng==", "dependencies": { "@coral-xyz/anchor": "^0.31.1", "@ethersproject/address": "^5.8.0", @@ -1601,7 +1567,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/provider-stablecoin-service-swap/node_modules/@noble/curves": { + "node_modules/@circle-fin/provider-stablecoin-service-swap/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1613,7 +1579,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-stablecoin-service-swap/node_modules/@noble/hashes": { + "node_modules/@circle-fin/provider-stablecoin-service-swap/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1625,7 +1591,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-stablecoin-service-swap/node_modules/zod": { + "node_modules/@circle-fin/provider-stablecoin-service-swap/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1634,13 +1600,13 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/swap-kit": { - "version": "1.6.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/swap-kit/-/swap-kit-1.6.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-I6VqJ5oml3Rh2SnE/VHm/B17KFFNx6naMuYv1Hgaem783e7812lSlWZ17dwxMz2wnJ2/CNzzM6vrrMn0BOQkSQ==", + "node_modules/@circle-fin/swap-kit": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@circle-fin/swap-kit/-/swap-kit-1.7.0.tgz", + "integrity": "sha512-kzACxFS+x7QEtFNE/UBe9A+RjeUDN5vKws6ztXA5XaBlazIqIMQwvXNfAiBlCtBmcYTLihsAKA7RARBiwQ9SaQ==", "dependencies": { + "@circle-fin/provider-stablecoin-service-swap": "^1.6.0", "@coral-xyz/anchor": "^0.31.1", - "@crcl-main/provider-stablecoin-service-swap": "1.5.1-canary-feature-onramp-kit-sdk.1789055534", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1656,7 +1622,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/swap-kit/node_modules/@noble/curves": { + "node_modules/@circle-fin/swap-kit/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1668,7 +1634,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/swap-kit/node_modules/@noble/hashes": { + "node_modules/@circle-fin/swap-kit/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1680,7 +1646,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/swap-kit/node_modules/zod": { + "node_modules/@circle-fin/swap-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1689,15 +1655,15 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/unified-balance-kit": { - "version": "1.6.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/unified-balance-kit/-/unified-balance-kit-1.6.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-fFOiusRzjGdXcti/bOkMu1aPYQowtC9VJ9QxCaBjfoIT8hf/szl9k/i0DAEpFZeTWRcOiiGznH8orM/hjoNF1A==", + "node_modules/@circle-fin/unified-balance-kit": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@circle-fin/unified-balance-kit/-/unified-balance-kit-1.7.0.tgz", + "integrity": "sha512-VEp7bUhQLs2x7yc9+Ll39inxRsOXJJBDvn585ur1YcGlqo3tKoAZ+lCB6mLplTDC3xjnSxKijKBE81Rga8l4Bg==", "dependencies": { + "@circle-fin/provider-cctp-v2": "1.14.0", + "@circle-fin/provider-fee-v1": "0.3.1", + "@circle-fin/provider-gateway-v1": "1.5.0", "@coral-xyz/anchor": "^0.31.1", - "@crcl-main/provider-cctp-v2": "1.13.0-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/provider-fee-v1": "0.3.0-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/provider-gateway-v1": "1.4.0-canary-feature-onramp-kit-sdk.1789055534", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1714,7 +1680,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/unified-balance-kit/node_modules/@noble/curves": { + "node_modules/@circle-fin/unified-balance-kit/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1726,7 +1692,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/unified-balance-kit/node_modules/@noble/hashes": { + "node_modules/@circle-fin/unified-balance-kit/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1738,7 +1704,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/unified-balance-kit/node_modules/uuid": { + "node_modules/@circle-fin/unified-balance-kit/node_modules/uuid": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", "integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==", @@ -1751,7 +1717,7 @@ "uuid": "dist-node/bin/uuid" } }, - "node_modules/@crcl-main/unified-balance-kit/node_modules/zod": { + "node_modules/@circle-fin/unified-balance-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1760,6 +1726,91 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/@coral-xyz/anchor": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.31.1.tgz", + "integrity": "sha512-QUqpoEK+gi2S6nlYc2atgT2r41TT3caWr/cPUEL8n8Md9437trZ68STknq897b82p5mW0XrTBNOzRbmIRJtfsA==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@coral-xyz/anchor-errors": "^0.31.1", + "@coral-xyz/borsh": "^0.31.1", + "@noble/hashes": "^1.3.1", + "@solana/web3.js": "^1.69.0", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.2", + "camelcase": "^6.3.0", + "cross-fetch": "^3.1.5", + "eventemitter3": "^4.0.7", + "pako": "^2.0.3", + "superstruct": "^0.15.4", + "toml": "^3.0.0" + }, + "engines": { + "node": ">=17" + } + }, + "node_modules/@coral-xyz/anchor-errors": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz", + "integrity": "sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/@coral-xyz/borsh": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.31.1.tgz", + "integrity": "sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.69.0" + } + }, "node_modules/@dotenvx/dotenvx": { "version": "1.75.1", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz", diff --git a/package.json b/package.json index 266a27e..70005c2 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@1shotapi/ows-signer-utils": "^0.6.2", "@1shotapi/ows-types": "^0.8.0", "@1shotapi/ows-wallet-utils": "^0.5.1", - "@crcl-main/app-kit": "^1.14.0-canary-feature-onramp-kit-sdk.1789055534", + "@circle-fin/app-kit": "^1.15.2", "@fontsource-variable/geist": "^5.3.0", "@metamask/smart-accounts-kit": "^2.0.0", "@reown/walletkit": "^1.5.6", diff --git a/src/components/OnrampView.tsx b/src/components/OnrampView.tsx index c77c53d..edc04ca 100644 --- a/src/components/OnrampView.tsx +++ b/src/components/OnrampView.tsx @@ -3,7 +3,7 @@ import type { AppKitOnrampOperations, OnrampSession, OnrampWidget, -} from "@crcl-main/app-kit"; +} from "@circle-fin/app-kit"; import type { EVMAccountAddress } from "@1shotapi/ows-types"; import { Modal, type ModalAction } from "./Modal"; import { useCircle } from "../circle/CircleContext"; diff --git a/src/lib/implementations/utils/CircleProvider.ts b/src/lib/implementations/utils/CircleProvider.ts index 57c3194..d6c956b 100644 --- a/src/lib/implementations/utils/CircleProvider.ts +++ b/src/lib/implementations/utils/CircleProvider.ts @@ -1,4 +1,4 @@ -import { AppKit } from "@crcl-main/app-kit"; +import { AppKit } from "@circle-fin/app-kit"; import type { ICircleProvider } from "../../interfaces/utils/ICircleProvider"; import type { IConfigProvider } from "../../interfaces/utils/IConfigProvider"; diff --git a/src/lib/interfaces/utils/ICircleProvider.ts b/src/lib/interfaces/utils/ICircleProvider.ts index a1b43cf..6fd00cf 100644 --- a/src/lib/interfaces/utils/ICircleProvider.ts +++ b/src/lib/interfaces/utils/ICircleProvider.ts @@ -1,4 +1,4 @@ -import type { AppKit } from "@crcl-main/app-kit"; +import type { AppKit } from "@circle-fin/app-kit"; /** * Utility-level Circle AppKit lifecycle. Lazily constructs a single AppKit and From 9e9fc20cb4a2ecd4504fdb17de9ee9972c39f95e Mon Sep 17 00:00:00 2001 From: Todd Chapman Date: Sun, 20 Sep 2026 16:12:58 -0700 Subject: [PATCH 04/11] update onramp start UI --- .github/workflows/Deploy Dev.yaml | 3 - .github/workflows/Deploy Prod.yaml | 3 - .npmrc | 2 - AGENTS.md | 2 +- Dockerfile | 7 +- README.md | 15 - ...alletConfiguratorTextTabWalletSections.tsx | 48 ++ host/src/styleForm.ts | 53 ++ package-lock.json | 616 +++++++++--------- package.json | 4 +- .../images/platforms/circle-logo-stacked.svg | 41 ++ src/circle/onrampTypes.ts | 2 + src/components/AssetDetails.tsx | 2 + src/components/Modal.tsx | 2 +- src/components/ModalHost.tsx | 2 + src/components/OnrampView.tsx | 254 +++++++- .../data/HardcodedKnownAssetRepository.ts | 8 + .../data/relayerKnownAssets.ts | 17 + .../implementations/utils/CircleProvider.ts | 2 +- .../interfaces/data/IKnownAssetRepository.ts | 6 + src/lib/interfaces/utils/ICircleProvider.ts | 2 +- src/style/applyStyle.ts | 5 + src/style/configureSchemas.ts | 16 + src/style/defaults.ts | 14 + src/style/index.ts | 1 + src/style/types.ts | 1 + 26 files changed, 739 insertions(+), 389 deletions(-) delete mode 100644 .npmrc create mode 100644 src/assets/images/platforms/circle-logo-stacked.svg diff --git a/.github/workflows/Deploy Dev.yaml b/.github/workflows/Deploy Dev.yaml index 0da3bff..c227225 100644 --- a/.github/workflows/Deploy Dev.yaml +++ b/.github/workflows/Deploy Dev.yaml @@ -55,11 +55,8 @@ jobs: gcloud --quiet auth configure-docker $GAR_LOCATION-docker.pkg.dev - name: Build Wallet - env: - CLOUDSMITH_TOKEN: ${{ secrets.CLOUDSMITH_TOKEN }} run: |- docker build \ - --secret id=cloudsmith_token,env=CLOUDSMITH_TOKEN \ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:$GITHUB_SHA" \ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:${GITHUB_REF##*/}" \ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:latest" \ diff --git a/.github/workflows/Deploy Prod.yaml b/.github/workflows/Deploy Prod.yaml index 0811ddd..0ba27c3 100644 --- a/.github/workflows/Deploy Prod.yaml +++ b/.github/workflows/Deploy Prod.yaml @@ -53,11 +53,8 @@ jobs: gcloud --quiet auth configure-docker $GAR_LOCATION-docker.pkg.dev - name: Build Wallet - env: - CLOUDSMITH_TOKEN: ${{ secrets.CLOUDSMITH_TOKEN }} run: |- docker build \ - --secret id=cloudsmith_token,env=CLOUDSMITH_TOKEN \ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:$GITHUB_SHA" \ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:${GITHUB_REF##*/}" \ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:latest" \ diff --git a/.npmrc b/.npmrc deleted file mode 100644 index e87ec79..0000000 --- a/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -@crcl-main:registry=https://npm.cloudsmith.io/circle/common-private/ -//npm.cloudsmith.io/circle/common-private/:_authToken=${CLOUDSMITH_TOKEN} diff --git a/AGENTS.md b/AGENTS.md index 4806eae..a3c0686 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ Prefer **clean code over backwards compatibility**. Do not add legacy redirects, Test Host Layer: `host/` (`npm run dev:host`). Browser extension: `extension/` (`npm run dev:extension`) — see [`extension/README.md`](extension/README.md). Branding / host config via Host RPC `configure`, not in-wallet debug knobs. -Fiat onramp: Asset Details **Buy** and host RPC `onramp({ chainId?, amount? })` open `OnrampView` (Circle AppKit). Sessions come from Relayer `POST /wallet/onramp` — never put the Circle kit key in this SPA. When Branding is nested in a Host iframe (`window.self !== window.top`), UI prefers `openWindow` (prefetch session, then a sync click) because Transak `frame-ancestors` cannot authorize arbitrary hosts. Top-level Branding uses `mountIframe`. Override with `localStorage.circlePopup = "true"|"false"`. Cloudsmith: set `CLOUDSMITH_TOKEN` before `npm install` (see README). +Fiat onramp: Asset Details **Buy** and host RPC `onramp({ chainId?, amount? })` open `OnrampView` (Circle AppKit). Sessions come from Relayer `POST /wallet/onramp` — never put the Circle kit key in this SPA. When Branding is nested in a Host iframe (`window.self !== window.top`), UI prefers `openWindow` (prefetch session, then a sync click) because Transak `frame-ancestors` cannot authorize arbitrary hosts. Top-level Branding uses `mountIframe`. Override with `localStorage.circlePopup = "true"|"false"`. Circle App Kit is the public npm package `@circle-fin/app-kit`. CCTP bridge: Asset Details **Bridge** (native USDC with `useCCTPBridge`) and host RPC `bridge({ amount?, sourceChainId?, destinationChainId? })` open `CCTPBridge`. Source omit → session chain. Burns via `TokenMessengerV2.depositForBurnWithHook` + `cctp-forward` hook through the EIP-7710 relayer (same USDC fee as Send). Destination mint is Circle’s Forwarding Service — no dest-chain signature, no native gas, no BridgeKit. diff --git a/Dockerfile b/Dockerfile index 78f54aa..c57ef0b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,13 +5,8 @@ WORKDIR /app COPY package.json package-lock.json ./ COPY host/package.json ./host/ -COPY .npmrc ./ -# Circle @crcl-main/* requires CLOUDSMITH_TOKEN BuildKit secret -RUN --mount=type=secret,id=cloudsmith_token \ - CLOUDSMITH_TOKEN="$(cat /run/secrets/cloudsmith_token)" \ - npm ci \ - && rm -f .npmrc +RUN npm ci COPY index.html vite.config.ts tsconfig.json tsconfig.node.json components.json ./ COPY src ./src diff --git a/README.md b/README.md index 05c049b..aa0dc29 100644 --- a/README.md +++ b/README.md @@ -29,21 +29,6 @@ Production deliverable: a static **nginx** Docker image (no server-side runtime) ## Setup -Circle’s private AppKit canary requires Cloudsmith auth. The repo includes `.npmrc`; -export your token before installing: - -```bash -# Linux / macOS -export CLOUDSMITH_TOKEN= -``` - -```powershell -# Windows PowerShell -$env:CLOUDSMITH_TOKEN = "" -``` - -CI Docker builds expect GitHub Actions secret `CLOUDSMITH_TOKEN`. - ```bash npm install cp .env.example .env # set NGROK_AUTHTOKEN (and optional NGROK_DOMAIN) diff --git a/host/src/components/WalletConfiguratorTextTabWalletSections.tsx b/host/src/components/WalletConfiguratorTextTabWalletSections.tsx index 26f0a2f..5c95b6e 100644 --- a/host/src/components/WalletConfiguratorTextTabWalletSections.tsx +++ b/host/src/components/WalletConfiguratorTextTabWalletSections.tsx @@ -171,6 +171,54 @@ export function WalletConfiguratorTextTabWalletSections({ value={form.confirmTransferReject} onChange={(value) => patch("confirmTransferReject", value)} /> + patch("onrampTitle", value)} + /> + patch("onrampBody", value)} + /> + patch("onrampDestinationLabel", value)} + /> + patch("onrampCloseLabel", value)} + /> + patch("onrampOpenLabel", value)} + /> + patch("onrampReopenLabel", value)} + /> + patch("onrampPopupReadyBody", value)} + /> + patch("onrampPopupOpenedBody", value)} + /> copy.confirmTransfer = confirmTransfer; } + const onramp: Record = {}; + put(onramp, "title", form.onrampTitle); + put(onramp, "body", form.onrampBody); + put(onramp, "destinationLabel", form.onrampDestinationLabel); + put(onramp, "closeLabel", form.onrampCloseLabel); + put(onramp, "openLabel", form.onrampOpenLabel); + put(onramp, "reopenLabel", form.onrampReopenLabel); + put(onramp, "loadingLabel", form.onrampLoadingLabel); + put(onramp, "preparingLabel", form.onrampPreparingLabel); + put(onramp, "popupReadyBody", form.onrampPopupReadyBody); + put(onramp, "popupOpenedBody", form.onrampPopupOpenedBody); + if (Object.keys(onramp).length > 0) { + copy.onramp = onramp; + } + const transferTokens: Record = {}; put(transferTokens, "title", form.transferTokensTitle); put(transferTokens, "sendLabel", form.transferTokensSend); diff --git a/package-lock.json b/package-lock.json index bb942f5..1fd871e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@1shotapi/ows-signer-utils": "^0.6.2", "@1shotapi/ows-types": "^0.8.0", "@1shotapi/ows-wallet-utils": "^0.5.1", - "@crcl-main/app-kit": "^1.14.0-canary-feature-onramp-kit-sdk.1789055534", + "@circle-fin/app-kit": "^1.15.2", "@fontsource-variable/geist": "^5.3.0", "@metamask/smart-accounts-kit": "^2.0.0", "@reown/walletkit": "^1.5.6", @@ -529,6 +529,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -1025,103 +1026,18 @@ "win32" ] }, - "node_modules/@coral-xyz/anchor": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.31.1.tgz", - "integrity": "sha512-QUqpoEK+gi2S6nlYc2atgT2r41TT3caWr/cPUEL8n8Md9437trZ68STknq897b82p5mW0XrTBNOzRbmIRJtfsA==", - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "@coral-xyz/anchor-errors": "^0.31.1", - "@coral-xyz/borsh": "^0.31.1", - "@noble/hashes": "^1.3.1", - "@solana/web3.js": "^1.69.0", - "bn.js": "^5.1.2", - "bs58": "^4.0.1", - "buffer-layout": "^1.2.2", - "camelcase": "^6.3.0", - "cross-fetch": "^3.1.5", - "eventemitter3": "^4.0.7", - "pako": "^2.0.3", - "superstruct": "^0.15.4", - "toml": "^3.0.0" - }, - "engines": { - "node": ">=17" - } - }, - "node_modules/@coral-xyz/anchor-errors": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz", - "integrity": "sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/@coral-xyz/anchor/node_modules/base-x": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", - "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@coral-xyz/anchor/node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "license": "MIT", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/@coral-xyz/anchor/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@coral-xyz/anchor/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/@coral-xyz/borsh": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.31.1.tgz", - "integrity": "sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw==", - "license": "Apache-2.0", - "dependencies": { - "bn.js": "^5.1.2", - "buffer-layout": "^1.2.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@solana/web3.js": "^1.69.0" - } - }, - "node_modules/@crcl-main/app-kit": { - "version": "1.14.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/app-kit/-/app-kit-1.14.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-04GjiZqTZAiNz+KXvafEk/4TS3pXEWfZPk6HUoyKzIwpMsz8JD6ixNeBy4wuBLEcSCNSJd1A9IF25AWoHKODZQ==", - "dependencies": { + "node_modules/@circle-fin/app-kit": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@circle-fin/app-kit/-/app-kit-1.15.2.tgz", + "integrity": "sha512-7+MaJVcdUJ5Mr1Tonz5/b56gm1F1K2HML04iOimMUmKT3q3mfixNKztAZVH2bv+K6ZXMHXQ0SkI7ZYraOPsseg==", + "dependencies": { + "@circle-fin/bridge-kit": "1.15.1", + "@circle-fin/earn-kit": "1.7.0", + "@circle-fin/onramp-kit": "1.0.1", + "@circle-fin/provider-gateway-v1": "1.5.0", + "@circle-fin/swap-kit": "1.7.0", + "@circle-fin/unified-balance-kit": "1.7.0", "@coral-xyz/anchor": "^0.31.1", - "@crcl-main/bridge-kit": "1.14.1-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/earn-kit": "1.6.1-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/onramp-kit": "0.0.1-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/provider-gateway-v1": "1.4.0-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/swap-kit": "1.6.1-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/unified-balance-kit": "1.6.0-canary-feature-onramp-kit-sdk.1789055534", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1140,7 +1056,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/app-kit/node_modules/@noble/curves": { + "node_modules/@circle-fin/app-kit/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1152,7 +1068,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/app-kit/node_modules/@noble/hashes": { + "node_modules/@circle-fin/app-kit/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1164,7 +1080,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/app-kit/node_modules/pino": { + "node_modules/@circle-fin/app-kit/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1186,7 +1102,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/app-kit/node_modules/zod": { + "node_modules/@circle-fin/app-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1195,13 +1111,14 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/bridge-kit": { - "version": "1.14.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/bridge-kit/-/bridge-kit-1.14.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-oBze4ODsutrppjkhL0jq1JtxdFFgo4qYDSK3yScrDiE3CcohyOom5S8MvN/QY72xUteQKzCiDgXWpQqsB7PaSQ==", + "node_modules/@circle-fin/bridge-kit": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@circle-fin/bridge-kit/-/bridge-kit-1.15.1.tgz", + "integrity": "sha512-CnMoBmZJL4NXYDZ11uHxdQJ+9/h9JQSJPOXmFw0EJRn45m0NqWJFWJgyOH+1Kmt8csDOcBgNLWii4WSn3wS4qA==", "dependencies": { - "@crcl-main/provider-cctp-v2": "1.13.0-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/provider-fee-v1": "0.3.0-canary-feature-onramp-kit-sdk.1789055534", + "@circle-fin/provider-cctp-v2": "^1.14.0", + "@circle-fin/provider-cctpx": "^1.0.0", + "@circle-fin/provider-fee-v1": "^0.3.1", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1216,7 +1133,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/bridge-kit/node_modules/pino": { + "node_modules/@circle-fin/bridge-kit/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1238,7 +1155,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/bridge-kit/node_modules/zod": { + "node_modules/@circle-fin/bridge-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1247,12 +1164,12 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/earn-kit": { - "version": "1.6.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/earn-kit/-/earn-kit-1.6.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-5YwMUf2PK8VsFlOoQv38ykp8gDxWbVqQkHFCgb9OaqfHmmPn9f/xwQDdluvAkuA85pj2UI9iJKc00VZyT50a5g==", + "node_modules/@circle-fin/earn-kit": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@circle-fin/earn-kit/-/earn-kit-1.7.0.tgz", + "integrity": "sha512-Yb04OT4UEX6IJoCApkWuuB6VTL8+dJpyvKb0ZcAKokKKvvnr8SwuWUlx1yYqd2x6Y9GJR6GyfYz5PZx1zyZiQA==", "dependencies": { - "@crcl-main/provider-earn-service": "1.5.1-canary-feature-onramp-kit-sdk.1789055534", + "@circle-fin/provider-earn-service": "^1.6.0", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1268,7 +1185,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/earn-kit/node_modules/pino": { + "node_modules/@circle-fin/earn-kit/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1290,7 +1207,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/earn-kit/node_modules/zod": { + "node_modules/@circle-fin/earn-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1299,10 +1216,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/onramp-kit": { - "version": "0.0.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/onramp-kit/-/onramp-kit-0.0.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-xEpJA3oRgNGOKAZaDcjobmEZnxfDunFjNeWH3gZzCJJrj4mboNHPJVOJMpHPLOErfSBuilZcU+sQuvrwAJ0OrA==", + "node_modules/@circle-fin/onramp-kit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@circle-fin/onramp-kit/-/onramp-kit-1.0.1.tgz", + "integrity": "sha512-N8Mok3ybsEFl2+ZoDmAjjAPpqasQQu6FHIqPMwIups0eqaOEvu7I+GCg3P+wxelqm9eQLSZORXt6tUcUH9Ojzg==", "dependencies": { "pino": "10.1.0", "zod": "3.25.67" @@ -1311,7 +1228,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/onramp-kit/node_modules/pino": { + "node_modules/@circle-fin/onramp-kit/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1333,7 +1250,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/onramp-kit/node_modules/zod": { + "node_modules/@circle-fin/onramp-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1342,10 +1259,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-cctp-v2": { - "version": "1.13.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-cctp-v2/-/provider-cctp-v2-1.13.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-Q6HmGkmeGee4OqogbJ9cP/hgfIlj2NNIHh03F5i20Oq8FGag81jbFlaSGTNzMTFMyRSy//zyc8vSANkmeBY2tA==", + "node_modules/@circle-fin/provider-cctp-v2": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-cctp-v2/-/provider-cctp-v2-1.14.0.tgz", + "integrity": "sha512-AX4fQueRft5TfS6XnYjPkHa6El+Dyf7Q2SyifLC6sfOT1kh2FdMvPKc4cWMdn5cBVQczDXlgXiP+PlOSrpK2qw==", "dependencies": { "@coral-xyz/anchor": "^0.31.1", "@ethersproject/abi": "^5.8.0", @@ -1372,7 +1289,7 @@ } } }, - "node_modules/@crcl-main/provider-cctp-v2/node_modules/@noble/curves": { + "node_modules/@circle-fin/provider-cctp-v2/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1384,7 +1301,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-cctp-v2/node_modules/@noble/hashes": { + "node_modules/@circle-fin/provider-cctp-v2/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1396,7 +1313,57 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-cctp-v2/node_modules/pino": { + "node_modules/@circle-fin/provider-cctp-v2/node_modules/pino": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", + "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/@circle-fin/provider-cctp-v2/node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@circle-fin/provider-cctpx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-cctpx/-/provider-cctpx-1.0.1.tgz", + "integrity": "sha512-mGbnNJTAj4H26NAZxZr8RyxQtMS2nNwWsl1hDzXau0OQARKieCQV/GIwhDXeVI+BzfgBbFLnQOtrAmkWV3owVA==", + "dependencies": { + "@ethersproject/abi": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/units": "^5.8.0", + "@solana/web3.js": "^1.98.4", + "abitype": "^1.1.0", + "bs58": "6.0.0", + "pino": "10.1.0", + "zod": "3.25.67" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@circle-fin/provider-cctpx/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1418,7 +1385,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/provider-cctp-v2/node_modules/zod": { + "node_modules/@circle-fin/provider-cctpx/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1427,10 +1394,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-earn-service": { - "version": "1.5.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-earn-service/-/provider-earn-service-1.5.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-YwP9w5ly42cgW1GngCxCfVqLZh0s1INa2ZpzxgKEzsetKUvvIPhyVjGulY7czYeGj5KKHYZ2ZXQqrC9T5qLSpQ==", + "node_modules/@circle-fin/provider-earn-service": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-earn-service/-/provider-earn-service-1.6.0.tgz", + "integrity": "sha512-XPhnAMHfANPdA/ENtF87xykKrp2TUezTc/jzJYzdagwV4JKpNmKN8XdJOUFNekS3QEff+ab7jyC131VywUrzIQ==", "dependencies": { "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", @@ -1447,7 +1414,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/provider-earn-service/node_modules/pino": { + "node_modules/@circle-fin/provider-earn-service/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1469,7 +1436,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/provider-earn-service/node_modules/zod": { + "node_modules/@circle-fin/provider-earn-service/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1478,10 +1445,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-fee-v1": { - "version": "0.3.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-fee-v1/-/provider-fee-v1-0.3.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-3TFMhB3BQgP632LEN7ZK1GiqeX2aTtwEXR7FdksKOjPwTCrfrzvH9D76aYGzLn4SBR9tV+nQjIQnzJwz5GWz8Q==", + "node_modules/@circle-fin/provider-fee-v1": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-fee-v1/-/provider-fee-v1-0.3.1.tgz", + "integrity": "sha512-KHWU3pj7RLIw1QQ7EJE4SWaJbmEtuz0Vs9RjANWBkC+sI/PjxnzCTEOraFdNAK3QpxftZoDX21dqT6v/a0c4YA==", "dependencies": { "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", @@ -1494,7 +1461,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/provider-fee-v1/node_modules/zod": { + "node_modules/@circle-fin/provider-fee-v1/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1503,10 +1470,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-gateway-v1": { - "version": "1.4.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-gateway-v1/-/provider-gateway-v1-1.4.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-dGq3koCcmVP+Zh1ez0dupDN/Sb0bMt7h+WfWQJGpMcJao/dfGjf3Dx8X366CWgxlYM/emD0RVPEYw7UcakJVxw==", + "node_modules/@circle-fin/provider-gateway-v1": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-gateway-v1/-/provider-gateway-v1-1.5.0.tgz", + "integrity": "sha512-NiEISO6jKbc4fmDG1Qhd5vma5VA3S1o7uz/U4hW9L/5nAnW5LZJfO1SrfX1jHQfgnmvY79qR3Q7Qi0TGDbuzIg==", "dependencies": { "@coral-xyz/anchor": "^0.31.1", "@ethersproject/abi": "^5.8.0", @@ -1526,7 +1493,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/provider-gateway-v1/node_modules/@noble/curves": { + "node_modules/@circle-fin/provider-gateway-v1/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1538,7 +1505,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-gateway-v1/node_modules/@noble/hashes": { + "node_modules/@circle-fin/provider-gateway-v1/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1550,7 +1517,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-gateway-v1/node_modules/pino": { + "node_modules/@circle-fin/provider-gateway-v1/node_modules/pino": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", @@ -1572,7 +1539,7 @@ "pino": "bin.js" } }, - "node_modules/@crcl-main/provider-gateway-v1/node_modules/zod": { + "node_modules/@circle-fin/provider-gateway-v1/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1581,10 +1548,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/provider-stablecoin-service-swap": { - "version": "1.5.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/provider-stablecoin-service-swap/-/provider-stablecoin-service-swap-1.5.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-lgwYtOhKQ61kHOtkoV5YtPzLjSx5xrUrE2UwK4K0q84ecoMF6oS6r4q6SHNeopVHxakaDMHVM7ZHUghLGz2KEA==", + "node_modules/@circle-fin/provider-stablecoin-service-swap": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@circle-fin/provider-stablecoin-service-swap/-/provider-stablecoin-service-swap-1.6.0.tgz", + "integrity": "sha512-WVA6LY4rYgSCXoVd0ukbTQXjX9RwbKzoq8IJzr9kqSKUllTMMrf449G94WUZKVihSUGs4uPUkhlDBDH2ZVwRng==", "dependencies": { "@coral-xyz/anchor": "^0.31.1", "@ethersproject/address": "^5.8.0", @@ -1601,7 +1568,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/provider-stablecoin-service-swap/node_modules/@noble/curves": { + "node_modules/@circle-fin/provider-stablecoin-service-swap/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1613,7 +1580,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-stablecoin-service-swap/node_modules/@noble/hashes": { + "node_modules/@circle-fin/provider-stablecoin-service-swap/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1625,7 +1592,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/provider-stablecoin-service-swap/node_modules/zod": { + "node_modules/@circle-fin/provider-stablecoin-service-swap/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1634,13 +1601,13 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/swap-kit": { - "version": "1.6.1-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/swap-kit/-/swap-kit-1.6.1-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-I6VqJ5oml3Rh2SnE/VHm/B17KFFNx6naMuYv1Hgaem783e7812lSlWZ17dwxMz2wnJ2/CNzzM6vrrMn0BOQkSQ==", + "node_modules/@circle-fin/swap-kit": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@circle-fin/swap-kit/-/swap-kit-1.7.0.tgz", + "integrity": "sha512-kzACxFS+x7QEtFNE/UBe9A+RjeUDN5vKws6ztXA5XaBlazIqIMQwvXNfAiBlCtBmcYTLihsAKA7RARBiwQ9SaQ==", "dependencies": { + "@circle-fin/provider-stablecoin-service-swap": "^1.6.0", "@coral-xyz/anchor": "^0.31.1", - "@crcl-main/provider-stablecoin-service-swap": "1.5.1-canary-feature-onramp-kit-sdk.1789055534", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1656,7 +1623,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/swap-kit/node_modules/@noble/curves": { + "node_modules/@circle-fin/swap-kit/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1668,7 +1635,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/swap-kit/node_modules/@noble/hashes": { + "node_modules/@circle-fin/swap-kit/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1680,7 +1647,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/swap-kit/node_modules/zod": { + "node_modules/@circle-fin/swap-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1689,15 +1656,15 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@crcl-main/unified-balance-kit": { - "version": "1.6.0-canary-feature-onramp-kit-sdk.1789055534", - "resolved": "https://npm.cloudsmith.io/circle/common-private/@crcl-main/unified-balance-kit/-/unified-balance-kit-1.6.0-canary-feature-onramp-kit-sdk.1789055534.tgz", - "integrity": "sha512-fFOiusRzjGdXcti/bOkMu1aPYQowtC9VJ9QxCaBjfoIT8hf/szl9k/i0DAEpFZeTWRcOiiGznH8orM/hjoNF1A==", + "node_modules/@circle-fin/unified-balance-kit": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@circle-fin/unified-balance-kit/-/unified-balance-kit-1.7.0.tgz", + "integrity": "sha512-VEp7bUhQLs2x7yc9+Ll39inxRsOXJJBDvn585ur1YcGlqo3tKoAZ+lCB6mLplTDC3xjnSxKijKBE81Rga8l4Bg==", "dependencies": { + "@circle-fin/provider-cctp-v2": "1.14.0", + "@circle-fin/provider-fee-v1": "0.3.1", + "@circle-fin/provider-gateway-v1": "1.5.0", "@coral-xyz/anchor": "^0.31.1", - "@crcl-main/provider-cctp-v2": "1.13.0-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/provider-fee-v1": "0.3.0-canary-feature-onramp-kit-sdk.1789055534", - "@crcl-main/provider-gateway-v1": "1.4.0-canary-feature-onramp-kit-sdk.1789055534", "@ethersproject/abi": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -1714,7 +1681,7 @@ "node": ">=20.0.0" } }, - "node_modules/@crcl-main/unified-balance-kit/node_modules/@noble/curves": { + "node_modules/@circle-fin/unified-balance-kit/node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", @@ -1726,7 +1693,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/unified-balance-kit/node_modules/@noble/hashes": { + "node_modules/@circle-fin/unified-balance-kit/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", @@ -1738,7 +1705,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@crcl-main/unified-balance-kit/node_modules/uuid": { + "node_modules/@circle-fin/unified-balance-kit/node_modules/uuid": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", "integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==", @@ -1751,7 +1718,7 @@ "uuid": "dist-node/bin/uuid" } }, - "node_modules/@crcl-main/unified-balance-kit/node_modules/zod": { + "node_modules/@circle-fin/unified-balance-kit/node_modules/zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", @@ -1760,6 +1727,91 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/@coral-xyz/anchor": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.31.1.tgz", + "integrity": "sha512-QUqpoEK+gi2S6nlYc2atgT2r41TT3caWr/cPUEL8n8Md9437trZ68STknq897b82p5mW0XrTBNOzRbmIRJtfsA==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@coral-xyz/anchor-errors": "^0.31.1", + "@coral-xyz/borsh": "^0.31.1", + "@noble/hashes": "^1.3.1", + "@solana/web3.js": "^1.69.0", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.2", + "camelcase": "^6.3.0", + "cross-fetch": "^3.1.5", + "eventemitter3": "^4.0.7", + "pako": "^2.0.3", + "superstruct": "^0.15.4", + "toml": "^3.0.0" + }, + "engines": { + "node": ">=17" + } + }, + "node_modules/@coral-xyz/anchor-errors": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz", + "integrity": "sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/@coral-xyz/borsh": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.31.1.tgz", + "integrity": "sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.69.0" + } + }, "node_modules/@dotenvx/dotenvx": { "version": "1.75.1", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz", @@ -1953,33 +2005,10 @@ "integrity": "sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==", "license": "BSD-3-Clause" }, - "node_modules/@emnapi/core": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", - "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", - "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "dev": true, "license": "MIT", "optional": true, @@ -2435,7 +2464,6 @@ "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -2455,7 +2483,6 @@ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2469,7 +2496,6 @@ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2480,7 +2506,6 @@ "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", @@ -2496,7 +2521,6 @@ "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@eslint/core": "^1.2.1" }, @@ -2510,7 +2534,6 @@ "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -2524,7 +2547,6 @@ "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } @@ -2535,7 +2557,6 @@ "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" @@ -3073,7 +3094,6 @@ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@humanfs/types": "^0.15.0" }, @@ -3087,7 +3107,6 @@ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", @@ -3103,7 +3122,6 @@ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18.18.0" } @@ -3114,7 +3132,6 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=12.22" }, @@ -3129,7 +3146,6 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18.18" }, @@ -3771,6 +3787,7 @@ "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -3794,6 +3811,7 @@ "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3810,6 +3828,7 @@ "integrity": "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.220.0", "import-in-the-middle": "^3.0.0", @@ -3863,6 +3882,7 @@ "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", @@ -4479,6 +4499,40 @@ "node": ">=14.0.0" } }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { "version": "11.24.2", "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.24.2.tgz", @@ -7187,6 +7241,7 @@ "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.99.0.tgz", "integrity": "sha512-QZYQ2T1z6xWisoyALPq25i/QZTsRlM02BABtAsfaQ1p8wX4SdTfxrKTRue/ZZqrhNVh5oL7T/DUFiTS9DRgxow==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.29.7", "@noble/curves": "^1.9.7", @@ -7479,27 +7534,6 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "dev": true, @@ -7776,8 +7810,7 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/lodash": { "version": "4.17.25", @@ -8030,21 +8063,6 @@ "ws": "^7.5.1" } }, - "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": { "version": "7.5.13", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", @@ -8668,6 +8686,7 @@ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8681,7 +8700,6 @@ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -9062,6 +9080,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.20", "caniuse-lite": "^1.0.30001810", @@ -9118,20 +9137,6 @@ "node": ">=4.5" } }, - "node_modules/bufferutil": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", - "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -9816,8 +9821,7 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -10260,6 +10264,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -10316,7 +10321,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -10442,7 +10446,6 @@ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -10460,7 +10463,6 @@ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -10478,7 +10480,6 @@ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -10491,8 +10492,7 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", @@ -10500,7 +10500,6 @@ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -10517,7 +10516,6 @@ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -10534,7 +10532,6 @@ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -10551,7 +10548,6 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -10562,7 +10558,6 @@ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", @@ -10640,7 +10635,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10793,6 +10787,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -10891,16 +10886,14 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-stable-stringify": { "version": "1.0.0", @@ -10971,7 +10964,6 @@ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flat-cache": "^4.0.0" }, @@ -11040,7 +11032,6 @@ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -11054,8 +11045,7 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz", "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/forwarded": { "version": "0.2.0", @@ -11347,6 +11337,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.0.tgz", "integrity": "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -11528,7 +11519,8 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.3.0.tgz", "integrity": "sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/ieee754": { "version": "1.2.1", @@ -11596,7 +11588,6 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.19" } @@ -11892,21 +11883,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, - "node_modules/jayson/node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/jayson/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -12008,8 +11984,7 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -12034,8 +12009,7 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -12080,7 +12054,6 @@ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -12106,7 +12079,6 @@ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -12513,6 +12485,7 @@ "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.29.3", "@babel/types": "^7.29.0", @@ -12798,8 +12771,7 @@ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/negotiator": { "version": "1.0.0", @@ -13093,7 +13065,6 @@ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -13192,6 +13163,7 @@ "integrity": "sha512-ov0NzaDCOInknS7mP1cwKdJERt3utPW8ldjtdUXQ8Ty0GEFD08wk422vCUN0d7pST6kqtV7dxoI9w1Zi0l/9TA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/types": "^0.143.0" }, @@ -13904,7 +13876,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -13999,7 +13970,6 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -14207,6 +14177,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14353,6 +14324,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -14571,6 +14543,7 @@ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.9" }, @@ -15464,6 +15437,7 @@ "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.28.0" }, @@ -15493,7 +15467,6 @@ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -15922,7 +15895,6 @@ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -16033,6 +16005,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", @@ -16058,6 +16031,7 @@ "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", @@ -16239,7 +16213,6 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -16289,6 +16262,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -16522,7 +16496,6 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -16569,6 +16542,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 266a27e..4bbe18d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "lint": "tsc -p tsconfig.json --noEmit", "test": "tsx --tsconfig tsconfig.test.json --test \"test/**/*.test.ts\"", "preview": "vite preview", - "dockerize": "docker build --secret id=cloudsmith_token,env=CLOUDSMITH_TOKEN -t oneshot-wallet .", + "dockerize": "docker build -t oneshot-wallet .", "doctor": "npx react-doctor@latest" }, "dependencies": { @@ -30,7 +30,7 @@ "@1shotapi/ows-signer-utils": "^0.6.2", "@1shotapi/ows-types": "^0.8.0", "@1shotapi/ows-wallet-utils": "^0.5.1", - "@crcl-main/app-kit": "^1.14.0-canary-feature-onramp-kit-sdk.1789055534", + "@circle-fin/app-kit": "^1.15.2", "@fontsource-variable/geist": "^5.3.0", "@metamask/smart-accounts-kit": "^2.0.0", "@reown/walletkit": "^1.5.6", diff --git a/src/assets/images/platforms/circle-logo-stacked.svg b/src/assets/images/platforms/circle-logo-stacked.svg new file mode 100644 index 0000000..fadb576 --- /dev/null +++ b/src/assets/images/platforms/circle-logo-stacked.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/circle/onrampTypes.ts b/src/circle/onrampTypes.ts index 5c2b61d..2f67736 100644 --- a/src/circle/onrampTypes.ts +++ b/src/circle/onrampTypes.ts @@ -6,4 +6,6 @@ export type IOnrampOpenRequest = { chainId?: number; amount?: string; tokenSymbol?: string; + tokenAddress?: EVMAccountAddress; + iconUrl?: string; }; diff --git a/src/components/AssetDetails.tsx b/src/components/AssetDetails.tsx index f7f72f9..9c78da5 100644 --- a/src/components/AssetDetails.tsx +++ b/src/components/AssetDetails.tsx @@ -124,6 +124,8 @@ export function AssetDetails({ asset: assetProp }: IAssetDetailsProps) { destinationAddress: evmAddress, chainId: Number(BigInt(asset.chainId)), tokenSymbol: asset.symbol, + tokenAddress: asset.address, + iconUrl: asset.iconUrl, }) .catch(() => { /* user closed or mint failed — OnrampView surfaces errors */ diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 9b8d09a..0c78c0e 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -20,7 +20,7 @@ export type ModalAction = { export type ModalPresentation = "page" | "overlay"; export type ModalProps = { - title: string; + title: ReactNode; children: ReactNode; actions?: ModalAction[]; /** Escape / overlay dismiss. Omit to lock until an action. */ diff --git a/src/components/ModalHost.tsx b/src/components/ModalHost.tsx index c8335d9..252539f 100644 --- a/src/components/ModalHost.tsx +++ b/src/components/ModalHost.tsx @@ -174,6 +174,8 @@ export function ModalHost() { chainId={activeModal.request.chainId} amount={activeModal.request.amount} tokenSymbol={activeModal.request.tokenSymbol} + tokenAddress={activeModal.request.tokenAddress} + iconUrl={activeModal.request.iconUrl} onClose={() => activeModal.resolve()} /> ); diff --git a/src/components/OnrampView.tsx b/src/components/OnrampView.tsx index c77c53d..113d469 100644 --- a/src/components/OnrampView.tsx +++ b/src/components/OnrampView.tsx @@ -1,20 +1,34 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, type ReactNode } from "react"; import type { AppKitOnrampOperations, OnrampSession, OnrampWidget, -} from "@crcl-main/app-kit"; -import type { EVMAccountAddress } from "@1shotapi/ows-types"; +} from "@circle-fin/app-kit"; +import { + ChainUtils, + EVMAccountAddress, + EVMChainId, + type EVMAccountAddress as EVMAccountAddressType, +} from "@1shotapi/ows-types"; +import { zeroAddress } from "viem"; import { Modal, type ModalAction } from "./Modal"; import { useCircle } from "../circle/CircleContext"; import { circleChainLabelFromChainId } from "../circle/circleChains"; import { isCirclePopupPreferred } from "../circle/circlePopup"; import type { IOnrampOpenRequest } from "../circle/onrampTypes"; +import { useStyle } from "../style/StyleProvider"; +import { useWallet } from "../wallet/WalletProvider"; +import { useWalletSessionStore } from "../wallet/sessionStore"; +import { AssetIdentityMark } from "./AssetIdentityMark"; +import { CopyableText } from "./CopyableText"; +import circleLogoStacked from "../assets/images/platforms/circle-logo-stacked.svg"; export type IOnrampViewProps = IOnrampOpenRequest & { onClose: () => void; }; +const PLACEHOLDER_TOKEN_ADDRESS = EVMAccountAddress(zeroAddress); + /** * Full-screen Circle AppKit onramp inside the Branding Layer shell. * Nested in a Host iframe (or `localStorage.circlePopup === "true"`): popup @@ -23,12 +37,20 @@ export type IOnrampViewProps = IOnrampOpenRequest & { */ export function OnrampView({ destinationAddress, - chainId, + chainId: chainIdProp, amount, - tokenSymbol, + tokenSymbol: tokenSymbolProp, + tokenAddress: tokenAddressProp, + iconUrl: iconUrlProp, onClose, }: IOnrampViewProps) { const circle = useCircle(); + const { style } = useStyle(); + const { resolveChain, knownAssetRepository } = useWallet(); + const sessionChainId = useWalletSessionStore((state) => state.chainId); + const copy = style.copy.onramp; + const accountCopy = style.copy.account; + const usePopup = isCirclePopupPreferred(); const containerRef = useRef(null); const widgetRef = useRef(null); @@ -38,12 +60,70 @@ export function OnrampView({ const [loading, setLoading] = useState(true); const [popupReady, setPopupReady] = useState(false); const [popupOpened, setPopupOpened] = useState(false); + const [catalogTokenAddress, setCatalogTokenAddress] = useState< + EVMAccountAddressType | null + >(null); + const [catalogIconUrl, setCatalogIconUrl] = useState(); + + const effectiveEvmChainId = resolveEffectiveEvmChainId( + chainIdProp, + sessionChainId, + ); + const tokenSymbol = (tokenSymbolProp?.trim() || "USDC").toUpperCase(); + const chain = effectiveEvmChainId + ? resolveChain(effectiveEvmChainId) + : null; + const networkLabel = chain?.label ?? "your network"; + const body = copy.body + .replaceAll("{token}", tokenSymbol) + .replaceAll("{network}", networkLabel); + + const tokenAddress = + tokenAddressProp ?? catalogTokenAddress ?? PLACEHOLDER_TOKEN_ADDRESS; + const iconUrl = iconUrlProp ?? catalogIconUrl; + + useEffect(() => { + if (tokenAddressProp || !effectiveEvmChainId) { + setCatalogTokenAddress(null); + setCatalogIconUrl(undefined); + return; + } + + let cancelled = false; + void knownAssetRepository + .getOnrampAsset(effectiveEvmChainId, tokenSymbol) + .then((asset) => { + if (cancelled) return; + if (asset) { + setCatalogTokenAddress(asset.address); + setCatalogIconUrl(asset.iconUrl); + } else { + setCatalogTokenAddress(null); + setCatalogIconUrl(undefined); + } + }) + .catch(() => { + if (!cancelled) { + setCatalogTokenAddress(null); + setCatalogIconUrl(undefined); + } + }); + + return () => { + cancelled = true; + }; + }, [ + effectiveEvmChainId, + knownAssetRepository, + tokenAddressProp, + tokenSymbol, + ]); useEffect(() => { let cancelled = false; const body = buildSessionBody({ destinationAddress, - chainId, + chainId: chainIdProp ?? decimalChainIdFromEvm(effectiveEvmChainId), amount, tokenSymbol, }); @@ -136,7 +216,15 @@ export function OnrampView({ widgetRef.current?.close(); widgetRef.current = null; }; - }, [amount, chainId, circle, destinationAddress, tokenSymbol, usePopup]); + }, [ + amount, + chainIdProp, + circle, + destinationAddress, + effectiveEvmChainId, + tokenSymbol, + usePopup, + ]); const openPopup = () => { const onramp = onrampRef.current; @@ -155,13 +243,16 @@ export function OnrampView({ void (async () => { try { const url = await circle.getSessionUrl(); - const body = buildSessionBody({ + const sessionBody = buildSessionBody({ destinationAddress, - chainId, + chainId: chainIdProp ?? decimalChainIdFromEvm(effectiveEvmChainId), amount, tokenSymbol, }); - sessionRef.current = await onramp.fetchSession({ url, body }); + sessionRef.current = await onramp.fetchSession({ + url, + body: sessionBody, + }); setLoading(false); setPopupReady(true); } catch (err: unknown) { @@ -188,51 +279,103 @@ export function OnrampView({ setPopupOpened(true); }; - const actions: ModalAction[] = []; + const actions: ModalAction[] = [ + { label: copy.closeLabel, onClick: onClose, variant: "secondary" }, + ]; if (usePopup && popupReady) { actions.push({ - label: popupOpened ? "Reopen onramp" : "Open onramp", + label: popupOpened ? copy.reopenLabel : copy.openLabel, onClick: openPopup, variant: "primary", + autoFocus: true, }); } - actions.push({ label: "Close", onClick: onClose, variant: "secondary" }); + + const statusMessage = (() => { + if (error) return null; + if (loading) { + return usePopup ? copy.preparingLabel : copy.loadingLabel; + } + if (usePopup && popupReady && !popupOpened) { + return copy.popupReadyBody; + } + if (usePopup && popupOpened) { + return copy.popupOpenedBody; + } + return null; + })(); return ( + + {copy.title} + + } onBackdropDismiss={onClose} contentClassName="z-50" actions={actions} > -
+
+

+ {renderOnrampBody(body, tokenSymbol)} +

+ + {effectiveEvmChainId ? ( +
+ +
+ + {amount ? `${amount} ` : ""} + {tokenSymbol} + + {chain ? ( + + {chain.label} + + ) : null} +
+
+ ) : null} + +
+ + {copy.destinationLabel} + + +
+ {error ? (

{error}

) : null} - {loading && !error ? ( -

- {usePopup ? "Preparing onramp…" : "Loading onramp…"} -

- ) : null} - {usePopup && popupReady && !popupOpened && !error ? ( -

- Circle opens in a popup (required when the wallet is host-iframed, or - for local/ngrok CSP bypass). Click Open onramp — browsers block - popups after an async delay. -

- ) : null} - {usePopup && popupOpened && !error ? ( -

- Onramp opened in a popup. Complete the purchase there, then close - this dialog. -

+ {statusMessage ? ( +

{statusMessage}

) : null} + {!usePopup ? (
) : null} @@ -241,6 +384,51 @@ export function OnrampView({ ); } +function resolveEffectiveEvmChainId( + chainIdProp: number | undefined, + sessionChainId: ReturnType["chainId"], +): EVMChainId | null { + if (chainIdProp != null && Number.isFinite(chainIdProp)) { + return EVMChainId(`0x${chainIdProp.toString(16)}` as `0x${string}`); + } + if (ChainUtils.isEVMChainId(sessionChainId)) { + return sessionChainId; + } + return null; +} + +function decimalChainIdFromEvm(chainId: EVMChainId | null): number | undefined { + if (!chainId) { + return undefined; + } + return Number(BigInt(chainId)); +} + +/** Keep “{token} 1-to-1” on one line even when hosts customize `copy.onramp.body`. */ +function renderOnrampBody(text: string, tokenSymbol: string): ReactNode { + const oneToOne = "1[\u2011-]to[\u2011-]1"; + const tokenUnit = `${escapeRegExp(tokenSymbol)}\\s+${oneToOne}`; + const pattern = new RegExp(`(${tokenUnit}|${oneToOne})`, "gi"); + const isNoBreakPart = new RegExp(`^(${tokenUnit}|${oneToOne})$`, "i"); + const parts = text.split(pattern); + if (parts.length === 1) { + return text; + } + return parts.map((part, index) => + isNoBreakPart.test(part) ? ( + + {part} + + ) : ( + part + ), + ); +} + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + function buildSessionBody(request: { destinationAddress: EVMAccountAddress; chainId?: number; diff --git a/src/lib/implementations/data/HardcodedKnownAssetRepository.ts b/src/lib/implementations/data/HardcodedKnownAssetRepository.ts index d03739c..94e7bd0 100644 --- a/src/lib/implementations/data/HardcodedKnownAssetRepository.ts +++ b/src/lib/implementations/data/HardcodedKnownAssetRepository.ts @@ -15,6 +15,7 @@ import { makeTrackedAssetId } from "@/lib/types/primitives"; import { RELAYER_KNOWN_ASSETS, getCctpBridgeAsset as lookupCctpBridgeAsset, + getOnrampAsset as lookupOnrampAsset, } from "./relayerKnownAssets"; import { HardcodedChainRepository } from "./HardcodedChainRepository"; import { registerKnownAssetIconResolver } from "../../utils/tokenIcons"; @@ -125,6 +126,13 @@ export class HardcodedKnownAssetRepository implements IKnownAssetRepository { return lookupCctpBridgeAsset(chainId); } + async getOnrampAsset( + chainId: EVMChainIdType, + symbol?: string, + ): Promise { + return lookupOnrampAsset(chainId, symbol); + } + async resolveForTracking( chainId: EVMChainIdType, address: EVMAccountAddressType, diff --git a/src/lib/implementations/data/relayerKnownAssets.ts b/src/lib/implementations/data/relayerKnownAssets.ts index 0d188dd..6267952 100644 --- a/src/lib/implementations/data/relayerKnownAssets.ts +++ b/src/lib/implementations/data/relayerKnownAssets.ts @@ -383,4 +383,21 @@ export function getCctpBridgeAsset( ); } +/** Circle onramp Buy asset on `chainId` (defaults to USDC). */ +export function getOnrampAsset( + chainId: EVMChainIdType, + symbol = "USDC", +): KnownAsset | null { + const chainKey = String(chainId).toLowerCase(); + const token = symbol.trim().toUpperCase(); + return ( + RELAYER_KNOWN_ASSETS.find( + (asset) => + asset.canBuy && + String(asset.chainId).toLowerCase() === chainKey && + asset.symbol.toUpperCase() === token, + ) ?? null + ); +} + registerKnownAssetIconResolver(getKnownAssetIconUrl); diff --git a/src/lib/implementations/utils/CircleProvider.ts b/src/lib/implementations/utils/CircleProvider.ts index 57c3194..d6c956b 100644 --- a/src/lib/implementations/utils/CircleProvider.ts +++ b/src/lib/implementations/utils/CircleProvider.ts @@ -1,4 +1,4 @@ -import { AppKit } from "@crcl-main/app-kit"; +import { AppKit } from "@circle-fin/app-kit"; import type { ICircleProvider } from "../../interfaces/utils/ICircleProvider"; import type { IConfigProvider } from "../../interfaces/utils/IConfigProvider"; diff --git a/src/lib/interfaces/data/IKnownAssetRepository.ts b/src/lib/interfaces/data/IKnownAssetRepository.ts index 8f219a9..bc3f0c9 100644 --- a/src/lib/interfaces/data/IKnownAssetRepository.ts +++ b/src/lib/interfaces/data/IKnownAssetRepository.ts @@ -10,6 +10,12 @@ export interface IKnownAssetRepository { /** Native Circle USDC on `chainId` when the catalog marks `useCCTPBridge`. */ getCctpBridgeAsset(chainId: EVMChainId): Promise; + /** Buyable stable on `chainId` for Circle onramp (defaults to USDC). */ + getOnrampAsset( + chainId: EVMChainId, + symbol?: string, + ): Promise; + /** * Catalog hit or on-chain ERC-20 probe → NewTrackedAsset. * Throws if the address is not a contract or not ERC-20. diff --git a/src/lib/interfaces/utils/ICircleProvider.ts b/src/lib/interfaces/utils/ICircleProvider.ts index a1b43cf..6fd00cf 100644 --- a/src/lib/interfaces/utils/ICircleProvider.ts +++ b/src/lib/interfaces/utils/ICircleProvider.ts @@ -1,4 +1,4 @@ -import type { AppKit } from "@crcl-main/app-kit"; +import type { AppKit } from "@circle-fin/app-kit"; /** * Utility-level Circle AppKit lifecycle. Lazily constructs a single AppKit and diff --git a/src/style/applyStyle.ts b/src/style/applyStyle.ts index 4b5b1cd..15728e4 100644 --- a/src/style/applyStyle.ts +++ b/src/style/applyStyle.ts @@ -50,6 +50,10 @@ export function mergeStyle( ...current.copy.confirmTransfer, ...patch.copy?.confirmTransfer, }, + onramp: { + ...current.copy.onramp, + ...patch.copy?.onramp, + }, transferTokens: { ...current.copy.transferTokens, ...patch.copy?.transferTokens, @@ -217,6 +221,7 @@ function cloneDefaultStyle(): IResolvedStyle { typedData: { ...DEFAULT_STYLE.copy.typedData }, sendTransaction: { ...DEFAULT_STYLE.copy.sendTransaction }, confirmTransfer: { ...DEFAULT_STYLE.copy.confirmTransfer }, + onramp: { ...DEFAULT_STYLE.copy.onramp }, transferTokens: { ...DEFAULT_STYLE.copy.transferTokens }, sendNativeToken: { ...DEFAULT_STYLE.copy.sendNativeToken }, cctpBridge: { ...DEFAULT_STYLE.copy.cctpBridge }, diff --git a/src/style/configureSchemas.ts b/src/style/configureSchemas.ts index ce964f8..62f8540 100644 --- a/src/style/configureSchemas.ts +++ b/src/style/configureSchemas.ts @@ -120,6 +120,19 @@ export const styleCopyConfirmTransferSchema = z.strictObject({ confirmLabel: z.string(), }); +export const styleCopyOnrampSchema = z.strictObject({ + title: z.string(), + body: z.string(), + destinationLabel: z.string(), + closeLabel: z.string(), + openLabel: z.string(), + reopenLabel: z.string(), + loadingLabel: z.string(), + preparingLabel: z.string(), + popupReadyBody: z.string(), + popupOpenedBody: z.string(), +}); + export const styleCopyTransferTokensSchema = z.strictObject({ title: z.string(), body: z.string(), @@ -485,6 +498,7 @@ export const styleCopyResolvedSchema = z.strictObject({ typedData: styleCopyTypedDataSchema, sendTransaction: styleCopySendTransactionSchema, confirmTransfer: styleCopyConfirmTransferSchema, + onramp: styleCopyOnrampSchema, transferTokens: styleCopyTransferTokensSchema, sendNativeToken: styleCopySendNativeTokenSchema, cctpBridge: styleCopyCctpBridgeSchema, @@ -532,6 +546,7 @@ export const styleCopyPatchSchema = z.strictObject({ typedData: styleCopyTypedDataSchema.partial().optional(), sendTransaction: styleCopySendTransactionSchema.partial().optional(), confirmTransfer: styleCopyConfirmTransferSchema.partial().optional(), + onramp: styleCopyOnrampSchema.partial().optional(), transferTokens: styleCopyTransferTokensSchema.partial().optional(), sendNativeToken: styleCopySendNativeTokenSchema.partial().optional(), cctpBridge: styleCopyCctpBridgeSchema.partial().optional(), @@ -594,6 +609,7 @@ export type IStyleCopySendTransaction = z.infer< export type IStyleCopyConfirmTransfer = z.infer< typeof styleCopyConfirmTransferSchema >; +export type IStyleCopyOnramp = z.infer; export type IStyleCopyTransferTokens = z.infer< typeof styleCopyTransferTokensSchema >; diff --git a/src/style/defaults.ts b/src/style/defaults.ts index cf24893..9eb45a6 100644 --- a/src/style/defaults.ts +++ b/src/style/defaults.ts @@ -119,6 +119,20 @@ export const DEFAULT_STYLE: IResolvedStyle = { rejectLabel: "Reject", confirmLabel: "Confirm", }, + onramp: { + title: "Onramp with Circle", + body: "Purchase {token} on {network} with Circle Onramp. Pay with card or bank transfer—you receive {token} 1-to-1.", + destinationLabel: "To", + closeLabel: "Close", + openLabel: "Start now", + reopenLabel: "Reopen onramp", + loadingLabel: "Loading onramp…", + preparingLabel: "Preparing onramp…", + popupReadyBody: + "Tap Start now to continue in a new window. Your browser may block the window if you wait too long after this screen appears.", + popupOpenedBody: + "Complete your purchase in the Circle window, then close this screen.", + }, transferTokens: { title: "Send", body: "Enter the recipient and amount to send.", diff --git a/src/style/index.ts b/src/style/index.ts index d6b973f..2e90679 100644 --- a/src/style/index.ts +++ b/src/style/index.ts @@ -13,6 +13,7 @@ export type { IStyleCopyTypedData, IStyleCopySendTransaction, IStyleCopyConfirmTransfer, + IStyleCopyOnramp, IStyleCopyTransferTokens, IStyleCopySendNativeToken, IStyleCopyCctpBridge, diff --git a/src/style/types.ts b/src/style/types.ts index 3cfd367..b89484e 100644 --- a/src/style/types.ts +++ b/src/style/types.ts @@ -17,6 +17,7 @@ export type { IStyleCopyTypedData, IStyleCopySendTransaction, IStyleCopyConfirmTransfer, + IStyleCopyOnramp, IStyleCopyTransferTokens, IStyleCopySendNativeToken, IStyleCopyCctpBridge, From 5cab371602beb0dc2f74b35ec3cf72dc665afb02 Mon Sep 17 00:00:00 2001 From: Todd Chapman Date: Sun, 20 Sep 2026 16:31:59 -0700 Subject: [PATCH 05/11] SIWE display updates --- ...lletConfiguratorTextTabSigningSections.tsx | 60 +++-- host/src/styleForm.ts | 44 ++-- src/components/modals/SignModals.tsx | 226 +++++++++++++----- src/lib/utils/siweDisplay.ts | 47 ++++ src/style/configureSchemas.ts | 9 +- src/style/defaults.ts | 13 +- test/lib/utils/siweDisplay.test.ts | 50 ++++ 7 files changed, 344 insertions(+), 105 deletions(-) create mode 100644 src/lib/utils/siweDisplay.ts create mode 100644 test/lib/utils/siweDisplay.test.ts diff --git a/host/src/components/WalletConfiguratorTextTabSigningSections.tsx b/host/src/components/WalletConfiguratorTextTabSigningSections.tsx index ea03d96..291119a 100644 --- a/host/src/components/WalletConfiguratorTextTabSigningSections.tsx +++ b/host/src/components/WalletConfiguratorTextTabSigningSections.tsx @@ -243,36 +243,18 @@ export function WalletConfiguratorTextTabSigningSections({ value={form.siweTitle} onChange={(value) => patch("siweTitle", value)} /> - patch("siweBody", value)} /> - patch("siweEstimatedChangesLabel", value)} - /> - patch("siweNoChangesLabel", value)} - /> patch("siweNetworkLabel", value)} /> - patch("siweRequestFromLabel", value)} - /> patch("siweUriLabel", value)} /> + patch("siweVersionLabel", value)} + /> + patch("siweNonceLabel", value)} + /> + patch("siweIssuedAtLabel", value)} + /> + patch("siweExpirationTimeLabel", value)} + /> + patch("siweNotBeforeLabel", value)} + /> + patch("siweResourcesLabel", value)} + /> patch("siweSignLabel", value)} /> diff --git a/host/src/styleForm.ts b/host/src/styleForm.ts index cdbf176..4da54bd 100644 --- a/host/src/styleForm.ts +++ b/host/src/styleForm.ts @@ -82,13 +82,16 @@ export interface IStyleFormState { // Text — SIWE (EIP-4361) siweTitle: string; siweBody: string; - siweEstimatedChangesLabel: string; - siweNoChangesLabel: string; siweNetworkLabel: string; - siweRequestFromLabel: string; siweSigningInWithLabel: string; siweMessageLabel: string; siweUriLabel: string; + siweVersionLabel: string; + siweNonceLabel: string; + siweIssuedAtLabel: string; + siweExpirationTimeLabel: string; + siweNotBeforeLabel: string; + siweResourcesLabel: string; siweRejectLabel: string; siweSignLabel: string; siweSigningHint: string; @@ -315,16 +318,19 @@ export const ACME_PRESET: IStyleFormState = { typedReject: "Reject", siweTitle: "Sign-in request", siweBody: - "A site wants you to sign in by proving you own this account. This will not spend tokens or change on-chain balances.", - siweEstimatedChangesLabel: "Estimated changes", - siweNoChangesLabel: "No changes", + "{domain} is requesting to sign in with your Ethereum account. This will not spend tokens or change on-chain balances.", siweNetworkLabel: "Network", - siweRequestFromLabel: "Request from", siweSigningInWithLabel: "Signing in with", siweMessageLabel: "Message", siweUriLabel: "URI", + siweVersionLabel: "Version", + siweNonceLabel: "Nonce", + siweIssuedAtLabel: "Issued at", + siweExpirationTimeLabel: "Expiration time", + siweNotBeforeLabel: "Not before", + siweResourcesLabel: "Resources", siweRejectLabel: "Cancel", - siweSignLabel: "Confirm", + siweSignLabel: "Sign in", siweSigningHint: "Confirm in the signing panel…", txTitle: "Approve transaction", txSignLabel: "Sign", @@ -513,16 +519,19 @@ export const DEFAULTS_PRESET: IStyleFormState = { txSignLabel: "Sign", siweTitle: "Sign-in request", siweBody: - "A site wants you to sign in by proving you own this account. This will not spend tokens or change on-chain balances.", - siweEstimatedChangesLabel: "Estimated changes", - siweNoChangesLabel: "No changes", + "{domain} is requesting to sign in with your Ethereum account. This will not spend tokens or change on-chain balances.", siweNetworkLabel: "Network", - siweRequestFromLabel: "Request from", siweSigningInWithLabel: "Signing in with", siweMessageLabel: "Message", siweUriLabel: "URI", + siweVersionLabel: "Version", + siweNonceLabel: "Nonce", + siweIssuedAtLabel: "Issued at", + siweExpirationTimeLabel: "Expiration time", + siweNotBeforeLabel: "Not before", + siweResourcesLabel: "Resources", siweRejectLabel: "Cancel", - siweSignLabel: "Confirm", + siweSignLabel: "Sign in", siweSigningHint: "Confirm in the signing panel…", credOfferTitle: "Accept credential offer?", credOfferBody: "", @@ -803,13 +812,16 @@ function buildNestedCopyFromForm(form: IStyleFormState): Record const siwe: Record = {}; put(siwe, "title", form.siweTitle); put(siwe, "body", form.siweBody); - put(siwe, "estimatedChangesLabel", form.siweEstimatedChangesLabel); - put(siwe, "noChangesLabel", form.siweNoChangesLabel); put(siwe, "networkLabel", form.siweNetworkLabel); - put(siwe, "requestFromLabel", form.siweRequestFromLabel); put(siwe, "signingInWithLabel", form.siweSigningInWithLabel); put(siwe, "messageLabel", form.siweMessageLabel); put(siwe, "uriLabel", form.siweUriLabel); + put(siwe, "versionLabel", form.siweVersionLabel); + put(siwe, "nonceLabel", form.siweNonceLabel); + put(siwe, "issuedAtLabel", form.siweIssuedAtLabel); + put(siwe, "expirationTimeLabel", form.siweExpirationTimeLabel); + put(siwe, "notBeforeLabel", form.siweNotBeforeLabel); + put(siwe, "resourcesLabel", form.siweResourcesLabel); put(siwe, "rejectLabel", form.siweRejectLabel); put(siwe, "signLabel", form.siweSignLabel); put(siwe, "signingHint", form.siweSigningHint); diff --git a/src/components/modals/SignModals.tsx b/src/components/modals/SignModals.tsx index d3793b9..399bf71 100644 --- a/src/components/modals/SignModals.tsx +++ b/src/components/modals/SignModals.tsx @@ -5,7 +5,6 @@ import type { } from "@1shotapi/ows-signer-utils"; import { ConversionUtils, - EVMChainId, HexString, OwsUserRejectedError, type EVMSignatureHex, @@ -24,6 +23,11 @@ import { useWallet } from "../../wallet/WalletProvider"; import { Modal } from "../Modal"; import { AssetIdentityMark } from "../AssetIdentityMark"; import { CopyableText } from "../CopyableText"; +import { SafeAssetImage } from "../SafeAssetImage"; +import { + isSiweUriRedundant, + resolveSiweEvmChainId, +} from "../../lib/utils/siweDisplay"; import { RelayerConfirmModalChrome } from "../RelayerConfirmModalChrome"; import { useRelayerConfirmSubmit } from "../useRelayerConfirmSubmit"; @@ -144,7 +148,15 @@ export function SiweModal({ const accountAddress = fields.address?.trim() || String(request.address); - const networkLabel = resolveSiweNetworkLabel(fields.chainId, resolveChain); + const body = siwe.body.replaceAll("{domain}", fields.domain); + const evmChainId = resolveSiweEvmChainId(fields.chainId); + const chain = evmChainId ? resolveChain(evmChainId) : null; + const networkDisplay = + (chain?.label ?? fields.chainId.trim()) || "Unknown network"; + const showUri = + Boolean(fields.uri?.trim()) && + !isSiweUriRedundant(fields.uri, fields.domain); + const statement = fields.statement?.trim(); const cancel = () => { signGenerationRef.current += 1; @@ -205,43 +217,116 @@ export function SiweModal({ : undefined } > -

{siwe.body}

- -
-

- {siwe.estimatedChangesLabel} +

+

+ {body}

-

{siwe.noChangesLabel}

-
- - -
- {siwe.signingInWithLabel} - -
+
+
+ + + {networkDisplay} + +
+ {fields.domain.trim() ? ( + + {fields.domain} + + ) : null} +
-
- {siwe.messageLabel} -

- {fields.statement?.trim() || siwe.body} -

- {fields.uri ? ( - <> - {siwe.uriLabel} -

{fields.uri}

- - ) : null} +
+ + {siwe.signingInWithLabel} + + +
+ +
+ {statement ? ( +
+ {siwe.messageLabel} +

+ {statement} +

+
+ ) : null} +
+ {showUri ? ( + + ) : null} + {fields.version ? ( + + ) : null} + {fields.nonce ? ( + + ) : null} + {fields.issuedAt ? ( + + ) : null} + {fields.expirationTime ? ( + + ) : null} + {fields.notBefore ? ( + + ) : null} +
+ {fields.resources && fields.resources.length > 0 ? ( +
+ {siwe.resourcesLabel} +
    + {fields.resources.map((resource) => ( +
  • + {resource} +
  • + ))} +
+
+ ) : null} +
{phase === "signing" ? ( -

+

{siwe.signingHint}

) : null} @@ -249,39 +334,60 @@ export function SiweModal({ ); } -function SiweDetailRow({ label, value }: { label: string; value: string }) { +function SiweSectionLabel({ children }: { children: string }) { return ( -
- {label} -

{value}

-
+ + {children} + ); } -function resolveSiweNetworkLabel( - chainIdRaw: string, - resolveChain: ( - chainId: EVMChainId, - ) => { label: string } | null, -): string { - const normalized = normalizeChainIdHex(chainIdRaw); - if (normalized) { - const match = resolveChain(EVMChainId(normalized as `0x${string}`)); - if (match) return match.label; - } - return chainIdRaw.trim() || "Unknown network"; +function SiweMetadataRow({ + label, + value, + compact = false, +}: { + label: string; + value: string; + compact?: boolean; +}) { + return ( +
+ {label} +

+ {value} +

+
+ ); } -function normalizeChainIdHex(raw: string): string | null { - const trimmed = raw.trim(); - if (!trimmed) return null; - if (/^0x[0-9a-fA-F]+$/.test(trimmed)) { - return `0x${BigInt(trimmed).toString(16)}`; - } - if (/^\d+$/.test(trimmed)) { - return `0x${BigInt(trimmed).toString(16)}`; - } - return null; +function NetworkIdentityMark({ + label, + logoUrl, + size = "md", +}: { + label: string; + logoUrl?: string; + size?: "sm" | "md"; +}) { + const letter = (label.trim()[0] ?? "?").toUpperCase(); + const box = size === "sm" ? "size-12" : "size-16"; + const text = size === "sm" ? "text-xl" : "text-2xl"; + return ( +
+ + {letter} +
+ } + /> +
+ ); } export function TypedDataModal({ diff --git a/src/lib/utils/siweDisplay.ts b/src/lib/utils/siweDisplay.ts new file mode 100644 index 0000000..2bd775d --- /dev/null +++ b/src/lib/utils/siweDisplay.ts @@ -0,0 +1,47 @@ +import { EVMChainId } from "@1shotapi/ows-types"; + +/** Lowercase host for SIWE origin comparison (strips port). */ +export function normalizeSiweHost(value: string): string { + const trimmed = value.trim().toLowerCase(); + if (!trimmed) { + return ""; + } + try { + const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`; + const url = new URL(withScheme); + return url.hostname.toLowerCase(); + } catch { + const withoutPort = trimmed.split(":")[0] ?? trimmed; + return withoutPort.replace(/^\[/, "").replace(/\]$/, ""); + } +} + +/** True when the SIWE URI points at the same host as `domain` (path/query ignored). */ +export function isSiweUriRedundant(uri: string, domain: string): boolean { + const uriHost = normalizeSiweHost(uri); + const domainHost = normalizeSiweHost(domain); + if (!uriHost || !domainHost) { + return false; + } + return uriHost === domainHost; +} + +/** Parse decimal or hex chain id from EIP-4361 fields into `EVMChainId`, or null. */ +export function resolveSiweEvmChainId(chainIdRaw: string): EVMChainId | null { + const trimmed = chainIdRaw.trim(); + if (!trimmed) { + return null; + } + let decimal: number; + if (/^0x[0-9a-fA-F]+$/i.test(trimmed)) { + decimal = Number(BigInt(trimmed)); + } else if (/^\d+$/.test(trimmed)) { + decimal = Number(trimmed); + } else { + return null; + } + if (!Number.isFinite(decimal) || decimal < 0) { + return null; + } + return EVMChainId(`0x${decimal.toString(16)}` as `0x${string}`); +} diff --git a/src/style/configureSchemas.ts b/src/style/configureSchemas.ts index 62f8540..724c7e3 100644 --- a/src/style/configureSchemas.ts +++ b/src/style/configureSchemas.ts @@ -75,13 +75,16 @@ export const styleCopyPersonalSignSchema = z.strictObject({ export const styleCopySiweSchema = z.strictObject({ title: z.string(), body: z.string(), - estimatedChangesLabel: z.string(), - noChangesLabel: z.string(), networkLabel: z.string(), - requestFromLabel: z.string(), signingInWithLabel: z.string(), messageLabel: z.string(), uriLabel: z.string(), + versionLabel: z.string(), + nonceLabel: z.string(), + issuedAtLabel: z.string(), + expirationTimeLabel: z.string(), + notBeforeLabel: z.string(), + resourcesLabel: z.string(), rejectLabel: z.string(), signLabel: z.string(), signingHint: z.string(), diff --git a/src/style/defaults.ts b/src/style/defaults.ts index 9eb45a6..83e2613 100644 --- a/src/style/defaults.ts +++ b/src/style/defaults.ts @@ -77,16 +77,19 @@ export const DEFAULT_STYLE: IResolvedStyle = { }, siwe: { title: "Sign-in request", - body: "A site wants you to sign in by proving you own this account. This will not spend tokens or change on-chain balances.", - estimatedChangesLabel: "Estimated changes", - noChangesLabel: "No changes", + body: "{domain} is requesting to sign in with your Ethereum account. This will not spend tokens or change on-chain balances.", networkLabel: "Network", - requestFromLabel: "Request from", signingInWithLabel: "Signing in with", messageLabel: "Message", uriLabel: "URI", + versionLabel: "Version", + nonceLabel: "Nonce", + issuedAtLabel: "Issued at", + expirationTimeLabel: "Expiration time", + notBeforeLabel: "Not before", + resourcesLabel: "Resources", rejectLabel: "Cancel", - signLabel: "Confirm", + signLabel: "Sign in", signingHint: "Confirm in the signing panel…", }, typedData: { diff --git a/test/lib/utils/siweDisplay.test.ts b/test/lib/utils/siweDisplay.test.ts new file mode 100644 index 0000000..3fbeafd --- /dev/null +++ b/test/lib/utils/siweDisplay.test.ts @@ -0,0 +1,50 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import { + isSiweUriRedundant, + normalizeSiweHost, + resolveSiweEvmChainId, +} from "@/lib/utils/siweDisplay.ts"; + +describe("normalizeSiweHost", () => { + it("strips scheme and path", () => { + assert.equal( + normalizeSiweHost("https://app.example.com/login"), + "app.example.com", + ); + }); + + it("normalizes bare domain", () => { + assert.equal(normalizeSiweHost("localhost:3000"), "localhost"); + }); +}); + +describe("isSiweUriRedundant", () => { + it("treats matching hosts as redundant", () => { + assert.equal( + isSiweUriRedundant("https://example.com/login", "example.com"), + true, + ); + assert.equal( + isSiweUriRedundant("https://app.example.com", "app.example.com"), + true, + ); + }); + + it("shows URI when hosts differ", () => { + assert.equal( + isSiweUriRedundant("https://auth.example.com", "app.example.com"), + false, + ); + }); +}); + +describe("resolveSiweEvmChainId", () => { + it("parses decimal chain id", () => { + assert.equal(String(resolveSiweEvmChainId("5042")), "0x13b2"); + }); + + it("parses hex chain id", () => { + assert.equal(String(resolveSiweEvmChainId("0x1")), "0x1"); + }); +}); From 1a811f142420188fb3a8244d80811b5ea0a97955 Mon Sep 17 00:00:00 2001 From: Todd Chapman Date: Sun, 20 Sep 2026 17:10:22 -0700 Subject: [PATCH 06/11] update to signed typed data display --- .../skills/oneshot-embedded-wallet/SKILL.md | 10 +- ...lletConfiguratorTextTabSigningSections.tsx | 12 ++ host/src/constants/signDemo.ts | 2 +- host/src/styleForm.ts | 31 ++- skills/oneshot-embedded-wallet/SKILL.md | 10 +- src/components/Eip712FieldTree.tsx | 115 +++++++++++ .../delegations/DelegationsList.tsx | 10 +- src/components/modals/SignModals.tsx | 184 +++++++++++++++--- src/lib/utils/eip712Display.ts | 117 +++++++++++ src/lib/utils/identityDisplay.ts | 10 + src/style/configureSchemas.ts | 8 +- src/style/defaults.ts | 10 +- test/lib/utils/eip712Display.test.ts | 64 ++++++ 13 files changed, 528 insertions(+), 55 deletions(-) create mode 100644 src/components/Eip712FieldTree.tsx create mode 100644 src/lib/utils/eip712Display.ts create mode 100644 src/lib/utils/identityDisplay.ts create mode 100644 test/lib/utils/eip712Display.test.ts diff --git a/.agents/skills/oneshot-embedded-wallet/SKILL.md b/.agents/skills/oneshot-embedded-wallet/SKILL.md index 35aac78..b5b1f7d 100644 --- a/.agents/skills/oneshot-embedded-wallet/SKILL.md +++ b/.agents/skills/oneshot-embedded-wallet/SKILL.md @@ -111,10 +111,14 @@ await proxy.rpc("configure", options); | `copy.personalSign.rejectLabel` | string | Reject button | | `copy.personalSign.signLabel` | string | Sign button | | `copy.typedData.title` | string | EIP-712 modal title | -| `copy.typedData.accountLabel` | string | Account field label | +| `copy.typedData.body` | string | Intro paragraph | +| `copy.typedData.networkLabel` | string | Network summary row label | +| `copy.typedData.requestFromLabel` | string | Requesting domain row label | +| `copy.typedData.accountLabel` | string | Signing account row label | +| `copy.typedData.interactingWithLabel` | string | Verifying contract row label | | `copy.typedData.primaryTypeLabel` | string | Primary type label | -| `copy.typedData.domainLabel` | string | Domain label | -| `copy.typedData.messageLabel` | string | Message label | +| `copy.typedData.messageSectionLabel` | string | Message card heading | +| `copy.typedData.signingHint` | string | Hint shown while signing | | `copy.typedData.rejectLabel` | string | Reject button | | `copy.typedData.signLabel` | string | Sign button | | `copy.credentialOffer.title` | string | offer modal title | diff --git a/host/src/components/WalletConfiguratorTextTabSigningSections.tsx b/host/src/components/WalletConfiguratorTextTabSigningSections.tsx index 291119a..a76ef81 100644 --- a/host/src/components/WalletConfiguratorTextTabSigningSections.tsx +++ b/host/src/components/WalletConfiguratorTextTabSigningSections.tsx @@ -219,6 +219,12 @@ export function WalletConfiguratorTextTabSigningSections({ value={form.typedTitle} onChange={(value) => patch("typedTitle", value)} /> + patch("typedBody", value)} + /> patch("typedReject", value)} /> + patch("typedSigningHint", value)} + /> diff --git a/host/src/constants/signDemo.ts b/host/src/constants/signDemo.ts index 5f4b228..1634ae7 100644 --- a/host/src/constants/signDemo.ts +++ b/host/src/constants/signDemo.ts @@ -4,7 +4,7 @@ export const DEFAULT_EIP712_TYPED_DATA = { domain: { name: "Ether Mail", version: "1", - chainId: 421614, + chainId: 84532, verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", }, types: { diff --git a/host/src/styleForm.ts b/host/src/styleForm.ts index 4da54bd..69f0565 100644 --- a/host/src/styleForm.ts +++ b/host/src/styleForm.ts @@ -74,10 +74,18 @@ export interface IStyleFormState { signLabel: string; signReject: string; - // Text — Typed data + // Text — Typed data (EIP-712) typedTitle: string; + typedBody: string; + typedNetworkLabel: string; + typedRequestFromLabel: string; + typedAccountLabel: string; + typedInteractingWithLabel: string; + typedPrimaryTypeLabel: string; + typedMessageSectionLabel: string; typedSignLabel: string; typedReject: string; + typedSigningHint: string; // Text — SIWE (EIP-4361) siweTitle: string; @@ -313,9 +321,17 @@ export const ACME_PRESET: IStyleFormState = { signTitle: "Approve signature", signLabel: "Sign", signReject: "Reject", - typedTitle: "Approve typed data", + typedTitle: "Signature request", + typedBody: "Review request details before you confirm.", + typedNetworkLabel: "Network", + typedRequestFromLabel: "Request from", + typedAccountLabel: "Account", + typedInteractingWithLabel: "Interacting with", + typedPrimaryTypeLabel: "Primary type", + typedMessageSectionLabel: "Message", typedSignLabel: "Sign", typedReject: "Reject", + typedSigningHint: "Confirm in the signing panel…", siweTitle: "Sign-in request", siweBody: "{domain} is requesting to sign in with your Ethereum account. This will not spend tokens or change on-chain balances.", @@ -513,8 +529,7 @@ export const DEFAULTS_PRESET: IStyleFormState = { "Accept the Terms of Service and Privacy Policy to continue.", signTitle: "Sign message", signLabel: "Sign", - typedTitle: "Sign typed data", - typedSignLabel: "Sign", + typedTitle: "Signature request", txTitle: "Send transaction", txSignLabel: "Sign", siweTitle: "Sign-in request", @@ -805,8 +820,16 @@ function buildNestedCopyFromForm(form: IStyleFormState): Record const typedData: Record = {}; put(typedData, "title", form.typedTitle); + put(typedData, "body", form.typedBody); + put(typedData, "networkLabel", form.typedNetworkLabel); + put(typedData, "requestFromLabel", form.typedRequestFromLabel); + put(typedData, "accountLabel", form.typedAccountLabel); + put(typedData, "interactingWithLabel", form.typedInteractingWithLabel); + put(typedData, "primaryTypeLabel", form.typedPrimaryTypeLabel); + put(typedData, "messageSectionLabel", form.typedMessageSectionLabel); put(typedData, "signLabel", form.typedSignLabel); put(typedData, "rejectLabel", form.typedReject); + put(typedData, "signingHint", form.typedSigningHint); if (Object.keys(typedData).length > 0) copy.typedData = typedData; const siwe: Record = {}; diff --git a/skills/oneshot-embedded-wallet/SKILL.md b/skills/oneshot-embedded-wallet/SKILL.md index ae368ad..49bf7ed 100644 --- a/skills/oneshot-embedded-wallet/SKILL.md +++ b/skills/oneshot-embedded-wallet/SKILL.md @@ -111,10 +111,14 @@ await proxy.rpc("configure", options); | `copy.personalSign.rejectLabel` | string | Reject button | | `copy.personalSign.signLabel` | string | Sign button | | `copy.typedData.title` | string | EIP-712 modal title | -| `copy.typedData.accountLabel` | string | Account field label | +| `copy.typedData.body` | string | Intro paragraph | +| `copy.typedData.networkLabel` | string | Network summary row label | +| `copy.typedData.requestFromLabel` | string | Requesting domain row label | +| `copy.typedData.accountLabel` | string | Signing account row label | +| `copy.typedData.interactingWithLabel` | string | Verifying contract row label | | `copy.typedData.primaryTypeLabel` | string | Primary type label | -| `copy.typedData.domainLabel` | string | Domain label | -| `copy.typedData.messageLabel` | string | Message label | +| `copy.typedData.messageSectionLabel` | string | Message card heading | +| `copy.typedData.signingHint` | string | Hint shown while signing | | `copy.typedData.rejectLabel` | string | Reject button | | `copy.typedData.signLabel` | string | Sign button | | `copy.credentialOffer.title` | string | offer modal title | diff --git a/src/components/Eip712FieldTree.tsx b/src/components/Eip712FieldTree.tsx new file mode 100644 index 0000000..d90231f --- /dev/null +++ b/src/components/Eip712FieldTree.tsx @@ -0,0 +1,115 @@ +import type { ReactNode } from "react"; +import { + formatEip712Primitive, + humanizeEip712Key, + isEip712NestedValue, + isEvmAddressString, +} from "../lib/utils/eip712Display"; +import { truncateAddress } from "../lib/utils/identityDisplay"; + +const MAX_DEPTH = 12; + +export type IEip712FieldTreeProps = { + value: unknown; + depth?: number; + label?: string; +}; + +/** Renders a resolved EIP-712 `message` as nested label / value rows. */ +export function Eip712FieldTree({ + value, + depth = 0, + label, +}: IEip712FieldTreeProps) { + if (depth > MAX_DEPTH) { + return ( + + ); + } + + if (Array.isArray(value)) { + return ( + + {value.map((item, index) => ( + + ))} + + ); + } + + if (isEip712NestedValue(value)) { + return ( + + {Object.keys(value as Record).map((key) => ( + )[key]} + depth={depth + 1} + label={humanizeEip712Key(key)} + /> + ))} + + ); + } + + return ( + + ); +} + +function Eip712Group({ + label, + children, +}: { + label?: string; + children: ReactNode; +}) { + if (!label) { + return
{children}
; + } + return ( +
+

{label}

+
+ {children} +
+
+ ); +} + +function Eip712PrimitiveRow({ + label, + value, +}: { + label: string; + value: string; +}) { + if (!value) { + return null; + } + const isAddress = isEvmAddressString(value); + return ( +
+ + {label} + + + {isAddress ? truncateAddress(value) : value} + +
+ ); +} diff --git a/src/components/delegations/DelegationsList.tsx b/src/components/delegations/DelegationsList.tsx index 60f1da1..b0232dc 100644 --- a/src/components/delegations/DelegationsList.tsx +++ b/src/components/delegations/DelegationsList.tsx @@ -9,6 +9,7 @@ import type { IDelegationSummary } from "../../lib/types/domain/StoredDelegation import type { DelegationId } from "../../lib/types/primitives/DelegationId"; import { useStyle } from "../../style/StyleProvider"; import { useWallet } from "../../wallet/WalletProvider"; +import { faviconUrl, truncateAddress } from "../../lib/utils/identityDisplay"; function fillTemplate( template: string, @@ -17,11 +18,6 @@ function fillTemplate( return template.replace(/\{(\w+)\}/g, (_, key: string) => vars[key] ?? ""); } -function truncateAddress(address: string): string { - if (address.length <= 13) return address; - return `${address.slice(0, 6)}…${address.slice(-4)}`; -} - function formatDuration(seconds: number): string { if (seconds % 86_400 === 0) { const days = seconds / 86_400; @@ -38,10 +34,6 @@ function formatDuration(seconds: number): string { return seconds === 1 ? "1 second" : `${seconds} seconds`; } -function faviconUrl(domain: string): string { - return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=32`; -} - interface IDelegationGroup { hostDomain: string; rows: IDelegationSummary[]; diff --git a/src/components/modals/SignModals.tsx b/src/components/modals/SignModals.tsx index 399bf71..bea3e28 100644 --- a/src/components/modals/SignModals.tsx +++ b/src/components/modals/SignModals.tsx @@ -10,7 +10,8 @@ import { type EVMSignatureHex, type EVMTransactionHash, } from "@1shotapi/ows-types"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState, type ReactNode } from "react"; +import { ExternalLinkIcon } from "lucide-react"; import type { TypedDataDefinition } from "viem"; import type { ISiweFields } from "../../lib/types/domain/SiweFields"; import type { @@ -28,6 +29,19 @@ import { isSiweUriRedundant, resolveSiweEvmChainId, } from "../../lib/utils/siweDisplay"; +import { + domainFieldEntries, + formatEip712Primitive, + humanizeEip712Key, + isEvmAddressString, + readDomainChainId, + readVerifyingContract, +} from "../../lib/utils/eip712Display"; +import { + faviconUrl, + truncateAddress, +} from "../../lib/utils/identityDisplay"; +import { Eip712FieldTree } from "../Eip712FieldTree"; import { RelayerConfirmModalChrome } from "../RelayerConfirmModalChrome"; import { useRelayerConfirmSubmit } from "../useRelayerConfirmSubmit"; @@ -399,14 +413,44 @@ export function TypedDataModal({ onResolve: (signature: EVMSignatureHex) => void; onReject: (error: unknown) => void; }) { - const { getSigner } = useWallet(); + const { getSigner, resolveChain, configProvider } = useWallet(); const { style } = useStyle(); const { typedData: copy } = style.copy; const { typedData } = request; const [phase, setPhase] = useState<"confirm" | "signing">("confirm"); + const [hostDomain, setHostDomain] = useState(""); /** Bumped on cancel and each startSign so stale in-flight ops cannot settle. */ const signGenerationRef = useRef(0); + useEffect(() => { + void configProvider.getConfig().then((config) => { + setHostDomain(String(config.hostDomain)); + }); + }, [configProvider]); + + const domainChainRaw = readDomainChainId(typedData.domain); + const evmChainId = domainChainRaw + ? resolveSiweEvmChainId(domainChainRaw) + : null; + const chain = evmChainId ? resolveChain(evmChainId) : null; + const networkDisplay = + chain?.label ?? + (domainChainRaw ? `Chain ${domainChainRaw.trim()}` : "Unknown network"); + const verifyingContract = readVerifyingContract(typedData.domain); + const contractExplorerUrl = + chain && verifyingContract && isEvmAddressString(verifyingContract) + ? chain.addressExplorerUrl(verifyingContract) + : null; + /** Domain separator fields other than chain / contract (e.g. `Ether Mail · v1`). */ + const domainSummary = domainFieldEntries(typedData.domain) + .filter(({ key }) => key !== "verifyingContract") + .map(({ key, value }) => { + const text = formatEip712Primitive(value); + return text ? `${humanizeEip712Key(key)}: ${text}` : ""; + }) + .filter(Boolean) + .join(" · "); + const cancel = () => { signGenerationRef.current += 1; onReject(new OwsUserRejectedError("User rejected the signing request")); @@ -457,29 +501,121 @@ export function TypedDataModal({ : undefined } > - {copy.accountLabel} -

{request.address}

- - - +
+

+ {copy.body} +

+ +
+ + + + {networkDisplay} + + + + + + {truncateAddress(String(request.address))} + + + + {hostDomain.trim() ? ( + + + + {hostDomain} + + + ) : null} + + {verifyingContract ? ( + + {contractExplorerUrl ? ( + + + {truncateAddress(verifyingContract)} + + + + ) : ( + + {truncateAddress(verifyingContract)} + + )} + + ) : null} +
+ +
+
+ + {copy.messageSectionLabel} + + + {typedData.primaryType} + +
+ {domainSummary ? ( +

+ {domainSummary} +

+ ) : null} +
+ +
+
+
+ {phase === "signing" ? ( -

- Confirm in the signing panel… +

+ {copy.signingHint}

) : null} ); } +function TypedDataSummaryRow({ + label, + children, +}: { + label: string; + children: ReactNode; +}) { + return ( +
+
+ {label} +
+
+ {children} +
+
+ ); +} + export function SendTransactionModal({ request, execute, @@ -801,18 +937,6 @@ function LabeledBlock({ label, content }: { label: string; content: string }) { ); } -function formatJson(value: unknown): string { - try { - return JSON.stringify( - value, - (_key, v) => (typeof v === "bigint" ? v.toString() : v), - 2, - ); - } catch { - return String(value); - } -} - function formatMessageForDisplay(message: string): string { if (message.startsWith("0x") && message.length > 2) { try { diff --git a/src/lib/utils/eip712Display.ts b/src/lib/utils/eip712Display.ts new file mode 100644 index 0000000..d84c33a --- /dev/null +++ b/src/lib/utils/eip712Display.ts @@ -0,0 +1,117 @@ +const EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/; + +/** Present EIP-712 field keys for consent UI labels. */ +export function humanizeEip712Key(key: string): string { + const trimmed = key.trim(); + if (!trimmed) { + return ""; + } + const withSpaces = trimmed + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .replace(/_/g, " ") + .toLowerCase(); + return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1); +} + +export function isEvmAddressString(value: string): boolean { + return EVM_ADDRESS_RE.test(value.trim()); +} + +export function formatEip712Primitive(value: unknown): string { + if (value === null || value === undefined) { + return ""; + } + if (typeof value === "bigint") { + return value.toString(); + } + if (typeof value === "boolean") { + return value ? "true" : "false"; + } + if (typeof value === "number") { + return Number.isFinite(value) ? String(value) : ""; + } + if (typeof value === "string") { + return value; + } + try { + return JSON.stringify(value); + } catch { + return String(value); + } +} + +export function isEip712NestedValue(value: unknown): value is object { + return value !== null && typeof value === "object"; +} + +/** Read `chainId` from an EIP-712 domain object for network resolution. */ +export function readDomainChainId(domain: unknown): string | null { + if (!domain || typeof domain !== "object") { + return null; + } + const raw = (domain as Record).chainId; + if (raw === null || raw === undefined) { + return null; + } + if (typeof raw === "bigint") { + return raw.toString(); + } + if (typeof raw === "number" && Number.isFinite(raw)) { + return String(raw); + } + if (typeof raw === "string" && raw.trim()) { + return raw.trim(); + } + return null; +} + +export type IEip712DomainField = { + key: string; + value: unknown; +}; + +/** Domain rows in stable EIP-712 order; omits empty values and `chainId` (shown via network row). */ +export function domainFieldEntries(domain: unknown): IEip712DomainField[] { + if (!domain || typeof domain !== "object") { + return []; + } + const record = domain as Record; + const order = ["name", "version", "verifyingContract", "salt"] as const; + const entries: IEip712DomainField[] = []; + for (const key of order) { + if (!(key in record)) { + continue; + } + const value = record[key]; + if (value === null || value === undefined) { + continue; + } + if (typeof value === "string" && !value.trim()) { + continue; + } + entries.push({ key, value }); + } + for (const key of Object.keys(record)) { + if (key === "chainId" || order.includes(key as (typeof order)[number])) { + continue; + } + const value = record[key]; + if (value === null || value === undefined) { + continue; + } + entries.push({ key, value }); + } + return entries; +} + +export function readVerifyingContract(domain: unknown): string | null { + if (!domain || typeof domain !== "object") { + return null; + } + const raw = (domain as Record).verifyingContract; + if (typeof raw !== "string") { + return null; + } + const trimmed = raw.trim(); + return trimmed.length > 0 ? trimmed : null; +} diff --git a/src/lib/utils/identityDisplay.ts b/src/lib/utils/identityDisplay.ts new file mode 100644 index 0000000..589c7da --- /dev/null +++ b/src/lib/utils/identityDisplay.ts @@ -0,0 +1,10 @@ +/** Middle-truncated address for compact rows (full value stays in `title`). */ +export function truncateAddress(address: string): string { + if (address.length <= 13) return address; + return `${address.slice(0, 6)}…${address.slice(-4)}`; +} + +/** Favicon for a requesting host domain; loaded through `SafeAssetImage`. */ +export function faviconUrl(domain: string): string { + return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=64`; +} diff --git a/src/style/configureSchemas.ts b/src/style/configureSchemas.ts index 724c7e3..c6736f2 100644 --- a/src/style/configureSchemas.ts +++ b/src/style/configureSchemas.ts @@ -92,12 +92,16 @@ export const styleCopySiweSchema = z.strictObject({ export const styleCopyTypedDataSchema = z.strictObject({ title: z.string(), + body: z.string(), + networkLabel: z.string(), + requestFromLabel: z.string(), accountLabel: z.string(), + interactingWithLabel: z.string(), primaryTypeLabel: z.string(), - domainLabel: z.string(), - messageLabel: z.string(), + messageSectionLabel: z.string(), rejectLabel: z.string(), signLabel: z.string(), + signingHint: z.string(), }); export const styleCopySendTransactionSchema = z.strictObject({ diff --git a/src/style/defaults.ts b/src/style/defaults.ts index 83e2613..09b34a7 100644 --- a/src/style/defaults.ts +++ b/src/style/defaults.ts @@ -93,13 +93,17 @@ export const DEFAULT_STYLE: IResolvedStyle = { signingHint: "Confirm in the signing panel…", }, typedData: { - title: "Sign typed data", + title: "Signature request", + body: "Review request details before you confirm.", + networkLabel: "Network", + requestFromLabel: "Request from", accountLabel: "Account", + interactingWithLabel: "Interacting with", primaryTypeLabel: "Primary type", - domainLabel: "Domain", - messageLabel: "Message", + messageSectionLabel: "Message", rejectLabel: "Reject", signLabel: "Sign", + signingHint: "Confirm in the signing panel…", }, sendTransaction: { title: "Send transaction", diff --git a/test/lib/utils/eip712Display.test.ts b/test/lib/utils/eip712Display.test.ts new file mode 100644 index 0000000..9d9230b --- /dev/null +++ b/test/lib/utils/eip712Display.test.ts @@ -0,0 +1,64 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import { + domainFieldEntries, + formatEip712Primitive, + humanizeEip712Key, + isEvmAddressString, + readDomainChainId, + readVerifyingContract, +} from "@/lib/utils/eip712Display.ts"; + +describe("humanizeEip712Key", () => { + it("splits camelCase", () => { + assert.equal(humanizeEip712Key("verifyingContract"), "Verifying contract"); + }); +}); + +describe("isEvmAddressString", () => { + it("matches 20-byte hex addresses", () => { + assert.equal( + isEvmAddressString("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"), + true, + ); + assert.equal(isEvmAddressString("not-an-address"), false); + }); +}); + +describe("formatEip712Primitive", () => { + it("stringifies bigint", () => { + assert.equal(formatEip712Primitive(421614n), "421614"); + }); +}); + +describe("readDomainChainId", () => { + it("reads numeric chain id", () => { + assert.equal(readDomainChainId({ chainId: 421614 }), "421614"); + }); +}); + +describe("domainFieldEntries", () => { + it("omits chainId and empty fields", () => { + const entries = domainFieldEntries({ + name: "Ether Mail", + version: "1", + chainId: 421614, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }); + assert.deepEqual( + entries.map((entry) => entry.key), + ["name", "version", "verifyingContract"], + ); + }); +}); + +describe("readVerifyingContract", () => { + it("returns trimmed contract address", () => { + assert.equal( + readVerifyingContract({ + verifyingContract: " 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC ", + }), + "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + ); + }); +}); From c4f539315e341fd0a86fcce0efbf3592fe35db73 Mon Sep 17 00:00:00 2001 From: Todd Chapman Date: Sun, 20 Sep 2026 20:59:54 -0700 Subject: [PATCH 07/11] Update erc-20-periodic delegation --- .../skills/oneshot-embedded-wallet/SKILL.md | 10 + ...alletConfiguratorTextTabWalletSections.tsx | 20 + host/src/styleForm.ts | 48 +- skills/oneshot-embedded-wallet/SKILL.md | 10 + src/components/ConsentSummaryRow.tsx | 21 + .../modals/GrantExecutionPermissionModal.tsx | 427 +++++++----------- src/components/modals/SignModals.tsx | 38 +- src/lib/utils/delegationDisplay.ts | 96 ++++ src/style/configureSchemas.ts | 6 + src/style/defaults.ts | 16 +- test/lib/utils/delegationDisplay.test.ts | 58 +++ 11 files changed, 460 insertions(+), 290 deletions(-) create mode 100644 src/components/ConsentSummaryRow.tsx create mode 100644 src/lib/utils/delegationDisplay.ts create mode 100644 test/lib/utils/delegationDisplay.test.ts diff --git a/.agents/skills/oneshot-embedded-wallet/SKILL.md b/.agents/skills/oneshot-embedded-wallet/SKILL.md index b5b1f7d..62bbb39 100644 --- a/.agents/skills/oneshot-embedded-wallet/SKILL.md +++ b/.agents/skills/oneshot-embedded-wallet/SKILL.md @@ -121,6 +121,16 @@ await proxy.rpc("configure", options); | `copy.typedData.signingHint` | string | Hint shown while signing | | `copy.typedData.rejectLabel` | string | Reject button | | `copy.typedData.signLabel` | string | Sign button | +| `copy.grantExecutionPermission.title` | string | ERC-20 periodic grant modal title | +| `copy.grantExecutionPermission.permissionKindLabel` | string | Summary card eyebrow | +| `copy.grantExecutionPermission.amountLabel` | string | Summary amount row | +| `copy.grantExecutionPermission.transferWindowLabel` | string | Summary cadence row | +| `copy.grantExecutionPermission.toLabel` | string | Delegate summary row | +| `copy.grantExecutionPermission.viewOnExplorerLabel` | string | Delegate explorer link a11y | +| `copy.grantExecutionPermission.justificationLabel` | string | Host justification in terms card | +| `copy.grantExecutionPermission.advancedLabel` | string | Toggle for Unix start field | +| `copy.grantExecutionPermission.grantLabel` | string | Grant button | +| `copy.grantExecutionPermission.rejectLabel` | string | Reject button | | `copy.credentialOffer.title` | string | offer modal title | | `copy.credentialOffer.body` | string | supports `{issuerName}` `{issuerId}` | | `copy.credentialOffer.offeredHeading` | string | offered list heading | diff --git a/host/src/components/WalletConfiguratorTextTabWalletSections.tsx b/host/src/components/WalletConfiguratorTextTabWalletSections.tsx index 5c95b6e..9374783 100644 --- a/host/src/components/WalletConfiguratorTextTabWalletSections.tsx +++ b/host/src/components/WalletConfiguratorTextTabWalletSections.tsx @@ -273,6 +273,26 @@ export function WalletConfiguratorTextTabWalletSections({ value={form.grantPermissionTitle} onChange={(value) => patch("grantPermissionTitle", value)} /> + patch("grantPermissionKindLabel", value)} + /> + patch("grantPermissionAmountLabel", value)} + /> + + patch("grantPermissionTransferWindowLabel", value) + } + /> const grantExecutionPermission: Record = {}; put(grantExecutionPermission, "title", form.grantPermissionTitle); + put( + grantExecutionPermission, + "permissionKindLabel", + form.grantPermissionKindLabel, + ); + put(grantExecutionPermission, "amountLabel", form.grantPermissionAmountLabel); + put( + grantExecutionPermission, + "transferWindowLabel", + form.grantPermissionTransferWindowLabel, + ); + put( + grantExecutionPermission, + "justificationLabel", + form.grantPermissionJustificationLabel, + ); + put( + grantExecutionPermission, + "viewOnExplorerLabel", + form.grantPermissionViewOnExplorerLabel, + ); + put( + grantExecutionPermission, + "advancedLabel", + form.grantPermissionAdvancedLabel, + ); put(grantExecutionPermission, "grantLabel", form.grantPermissionGrant); put(grantExecutionPermission, "rejectLabel", form.grantPermissionReject); if (Object.keys(grantExecutionPermission).length > 0) { diff --git a/skills/oneshot-embedded-wallet/SKILL.md b/skills/oneshot-embedded-wallet/SKILL.md index 49bf7ed..114446b 100644 --- a/skills/oneshot-embedded-wallet/SKILL.md +++ b/skills/oneshot-embedded-wallet/SKILL.md @@ -121,6 +121,16 @@ await proxy.rpc("configure", options); | `copy.typedData.signingHint` | string | Hint shown while signing | | `copy.typedData.rejectLabel` | string | Reject button | | `copy.typedData.signLabel` | string | Sign button | +| `copy.grantExecutionPermission.title` | string | ERC-20 periodic grant modal title | +| `copy.grantExecutionPermission.permissionKindLabel` | string | Summary card eyebrow | +| `copy.grantExecutionPermission.amountLabel` | string | Summary amount row | +| `copy.grantExecutionPermission.transferWindowLabel` | string | Summary cadence row | +| `copy.grantExecutionPermission.toLabel` | string | Delegate summary row | +| `copy.grantExecutionPermission.viewOnExplorerLabel` | string | Delegate explorer link a11y | +| `copy.grantExecutionPermission.justificationLabel` | string | Host justification in terms card | +| `copy.grantExecutionPermission.advancedLabel` | string | Toggle for Unix start field | +| `copy.grantExecutionPermission.grantLabel` | string | Grant button | +| `copy.grantExecutionPermission.rejectLabel` | string | Reject button | | `copy.credentialOffer.title` | string | offer modal title | | `copy.credentialOffer.body` | string | supports `{issuerName}` `{issuerId}` | | `copy.credentialOffer.offeredHeading` | string | offered list heading | diff --git a/src/components/ConsentSummaryRow.tsx b/src/components/ConsentSummaryRow.tsx new file mode 100644 index 0000000..843e665 --- /dev/null +++ b/src/components/ConsentSummaryRow.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from "react"; + +/** Label-left / value-right row for consent summary cards (typed data, permissions). */ +export function ConsentSummaryRow({ + label, + children, +}: { + label: string; + children: ReactNode; +}) { + return ( +
+
+ {label} +
+
+ {children} +
+
+ ); +} diff --git a/src/components/modals/GrantExecutionPermissionModal.tsx b/src/components/modals/GrantExecutionPermissionModal.tsx index 56b53c7..c9faa72 100644 --- a/src/components/modals/GrantExecutionPermissionModal.tsx +++ b/src/components/modals/GrantExecutionPermissionModal.tsx @@ -1,24 +1,29 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; +import { ExternalLinkIcon } from "lucide-react"; import { EVMAccountAddress, OwsUserRejectedError, type IExecutionPermission, } from "@1shotapi/ows-types"; -import { formatUnits, getAddress, hexToBigInt, parseUnits } from "viem"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; +import { formatUnits, getAddress, hexToBigInt } from "viem"; import { ERC20_TOKEN_PERIODIC } from "../../lib/interfaces/business/IDelegationService"; import { EAssetType } from "../../lib/types/enum/EAssetType"; +import { + formatUnixSecondsLabel, + humanizePeriodDuration, + parsePeriodDurationSeconds, +} from "../../lib/utils/delegationDisplay"; +import { faviconUrl, truncateAddress } from "../../lib/utils/identityDisplay"; +import { resolveAssetIconUrl } from "../../lib/utils/tokenIcons"; import { useStyle } from "../../style/StyleProvider"; import type { IGrantExecutionPermissionRequest, IGrantExecutionPermissionResult, } from "../../wallet/modalTypes"; import { useWallet } from "../../wallet/WalletProvider"; +import { ConsentSummaryRow } from "../ConsentSummaryRow"; import { Modal } from "../Modal"; -import { TokenAmountInput } from "../TokenAmountInput"; -import { CopyableText } from "../CopyableText"; +import { SafeAssetImage } from "../SafeAssetImage"; function readTokenAddress(data: Record): string | null { const raw = data.tokenAddress ?? data.token; @@ -44,9 +49,16 @@ function readAmountAtoms(data: Record): bigint | null { return null; } -function readInitialMemo(data: Record): string { - const raw = data.justification; - return typeof raw === "string" ? raw : ""; +function readHostMemoOrJustification(data: Record): string { + const memo = data.memo; + if (typeof memo === "string" && memo.trim()) { + return memo.trim(); + } + const justification = data.justification; + if (typeof justification === "string" && justification.trim()) { + return justification.trim(); + } + return ""; } function readDuration(data: Record): string { @@ -54,7 +66,7 @@ function readDuration(data: Record): string { if (typeof raw === "number" || typeof raw === "string") { return String(raw); } - return "86400"; + return ""; } function readStart(data: Record): string { @@ -66,7 +78,7 @@ function readStart(data: Record): string { } /** - * Host EIP-7715 grant consent — editable period fields when adjustment allowed. + * Host EIP-7715 grant consent — read-only terms; user grants or rejects as proposed. */ export function GrantExecutionPermissionModal({ request, @@ -79,179 +91,128 @@ export function GrantExecutionPermissionModal({ }) { const { style } = useStyle(); const copy = style.copy.grantExecutionPermission; - const { account } = style.copy; const { listTrackedAssets, resolveChain, getKnownAsset } = useWallet(); const permission = request.request.permission; - const adjustable = permission.isAdjustmentAllowed !== false; - const initialToken = readTokenAddress(permission.data); + const permissionData = permission.data as Record; + const tokenAddress = readTokenAddress(permissionData); + const hostMessage = readHostMemoOrJustification(permissionData); - const [tokenOptions, setTokenOptions] = useState< - Array<{ address: string; symbol: string; decimals: number; label: string }> - >([]); - const [tokenAddress, setTokenAddress] = useState(initialToken ?? ""); - const [amountText, setAmountText] = useState(""); - const [durationText, setDurationText] = useState( - readDuration(permission.data), - ); - const [startText, setStartText] = useState(readStart(permission.data)); - const [memo, setMemo] = useState(() => readInitialMemo(permission.data)); - const userEditedAmount = useRef(false); + const [tokenSymbol, setTokenSymbol] = useState("TOKEN"); + const [tokenDecimals, setTokenDecimals] = useState(6); + const [tokenIconUrl, setTokenIconUrl] = useState(); useEffect(() => { + if (!tokenAddress) return; let cancelled = false; - void listTrackedAssets().then(async (assets) => { + const chainId = request.request.chainId; + const checksummed = getAddress(tokenAddress as `0x${string}`); + + void (async () => { + const assets = await listTrackedAssets(); if (cancelled) return; - const onChain = assets.filter( + const tracked = assets.find( (a) => a.type === EAssetType.Erc20 && - String(a.chainId).toLowerCase() === - String(request.request.chainId).toLowerCase(), + String(a.chainId).toLowerCase() === String(chainId).toLowerCase() && + getAddress(String(a.address)).toLowerCase() === checksummed.toLowerCase(), ); - const options = onChain.map((a) => ({ - address: getAddress(String(a.address)), - symbol: a.symbol, - decimals: a.decimals ?? 6, - label: `${a.symbol} (${a.name})`, - })); - if ( - initialToken && - !options.some( - (o) => o.address.toLowerCase() === initialToken.toLowerCase(), - ) - ) { - try { - const known = await getKnownAsset( - request.request.chainId, - EVMAccountAddress(getAddress(initialToken as `0x${string}`)), + if (tracked) { + setTokenSymbol(tracked.symbol); + setTokenDecimals(tracked.decimals ?? 6); + setTokenIconUrl( + resolveAssetIconUrl( + chainId, + EVMAccountAddress(checksummed), + tracked.symbol, + tracked.iconUrl, + ), + ); + return; + } + try { + const known = await getKnownAsset( + chainId, + EVMAccountAddress(checksummed), + ); + if (cancelled) return; + if (known) { + setTokenSymbol(known.symbol); + setTokenDecimals(known.decimals ?? 6); + setTokenIconUrl( + resolveAssetIconUrl( + chainId, + EVMAccountAddress(checksummed), + known.symbol, + known.iconUrl, + ), ); - options.unshift({ - address: getAddress(initialToken as `0x${string}`), - symbol: known?.symbol ?? "TOKEN", - decimals: known?.decimals ?? 6, - label: known ? `${known.symbol} (${known.name})` : initialToken, - }); - } catch { - // leave options as-is + } else { + setTokenIconUrl(undefined); } + } catch { + setTokenIconUrl(undefined); } - if (cancelled) return; - setTokenOptions(options); - if (!tokenAddress && options[0]) { - setTokenAddress(options[0].address); - } - }); + })(); + return () => { cancelled = true; }; }, [ - initialToken, getKnownAsset, listTrackedAssets, request.request.chainId, - request.request.to, tokenAddress, ]); - const selected = useMemo(() => { - const match = tokenOptions.find( - (o) => o.address.toLowerCase() === tokenAddress.toLowerCase(), - ); - return match ?? { address: tokenAddress, symbol: "TOKEN", decimals: 6, label: tokenAddress }; - }, [tokenAddress, tokenOptions]); - - useEffect(() => { - userEditedAmount.current = false; - setMemo(readInitialMemo(permission.data)); - setDurationText(readDuration(permission.data)); - setStartText(readStart(permission.data)); - setTokenAddress(initialToken ?? ""); - }, [ - initialToken, - permission.data, - request.request.chainId, - request.request.to, - ]); + const amountAtoms = readAmountAtoms(permissionData); + const durationSeconds = parsePeriodDurationSeconds( + readDuration(permissionData), + ); + const startDisplay = formatUnixSecondsLabel( + readStart(permissionData) || undefined, + ); - useEffect(() => { - if (!tokenAddress || userEditedAmount.current) return; - const atoms = readAmountAtoms(permission.data); - if (atoms === null) return; + const summaryAmount = useMemo(() => { + if (amountAtoms === null || amountAtoms <= 0n) return null; try { - setAmountText(formatUnits(atoms, selected.decimals)); + const amount = formatUnits(amountAtoms, tokenDecimals); + return `${amount} ${tokenSymbol}`; } catch { - setAmountText(""); - } - }, [ - permission.data, - request.request.chainId, - request.request.to, - selected.decimals, - tokenAddress, - ]); - - const amountError = useMemo(() => { - const trimmed = amountText.trim(); - if (!trimmed) return null; - try { - const parsed = parseUnits(trimmed, selected.decimals); - if (parsed <= 0n) return copy.invalidAmountError; return null; - } catch { - return copy.invalidAmountError; } - }, [amountText, copy.invalidAmountError, selected.decimals]); + }, [amountAtoms, tokenDecimals, tokenSymbol]); - const durationError = useMemo(() => { - const trimmed = durationText.trim(); - if (!trimmed) return null; - const n = Number(trimmed); - if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) { - return copy.invalidDurationError; - } - return null; - }, [copy.invalidDurationError, durationText]); + const summaryWindow = + durationSeconds === null + ? "—" + : humanizePeriodDuration(durationSeconds); - const formReady = + const termsValid = Boolean(tokenAddress) && - amountText.trim() !== "" && - amountError === null && - durationText.trim() !== "" && - durationError === null; + amountAtoms !== null && + amountAtoms > 0n && + durationSeconds !== null; - const chainLabel = - resolveChain(request.request.chainId)?.label ?? request.chainName; - - const body = copy.body - .replace("{domain}", request.domain) - .replace("{to}", request.request.to) - .replace("{chainName}", chainLabel) - .replace("{permissionType}", permission.type); + const chain = resolveChain(request.request.chainId); + const chainLabel = chain?.label ?? request.chainName; + const delegateAddress = String(request.request.to); + const delegateExplorerUrl = chain?.addressExplorerUrl(delegateAddress); const reject = () => { onReject(new OwsUserRejectedError("User rejected the permission request")); }; const grant = () => { - if (!formReady) return; - const periodAmount = parseUnits(amountText.trim(), selected.decimals); - const periodDuration = Number(durationText.trim()); - const startTrimmed = startText.trim(); - const data: Record = { - tokenAddress: EVMAccountAddress( - getAddress(tokenAddress as `0x${string}`), - ), - periodAmount: `0x${periodAmount.toString(16)}`, - periodDuration, - }; - if (startTrimmed) { - data.startDate = Number(startTrimmed); - } + if (!termsValid) return; const nextPermission: IExecutionPermission = { type: ERC20_TOKEN_PERIODIC, isAdjustmentAllowed: permission.isAdjustmentAllowed, - data, + data: permissionData, }; - onResolve({ permission: nextPermission, memo: memo.trim() }); + onResolve({ + permission: nextPermission, + memo: hostMessage, + }); }; return ( @@ -271,125 +232,81 @@ export function GrantExecutionPermissionModal({ : copy.grantLabel, variant: "primary", autoFocus: true, - disabled: !formReady, + disabled: !termsValid, onClick: grant, }, ]} > -

{body}

-
-
-
- {copy.hostLabel} -
-
{request.domain}
-
-
-
- {copy.toLabel} -
-
- -
-
-
-
- {copy.chainLabel} -
-
{chainLabel}
-
-
-
- {copy.permissionTypeLabel} -
-
- {permission.type} -
-
-
- -
-
- - {adjustable && tokenOptions.length > 0 ? ( - - ) : ( -

{tokenAddress || "—"}

- )} -
- - { - userEditedAmount.current = true; - setAmountText(value); - }} - disabled={!adjustable} - error={amountError} - /> - -
- - setDurationText(event.target.value)} - aria-invalid={Boolean(durationError)} +
+

+ {copy.permissionKindLabel} +

+
+ -

- {copy.periodDurationHint} + + {request.domain} + +

+ {hostMessage ? ( +

+ {hostMessage}

- {durationError ? ( -

- {durationError} -

+ ) : null} +
+ + {tokenIconUrl ? ( + + ) : null} + + {summaryAmount ?? "—"} + + + + {summaryWindow} + + {startDisplay ? ( + + {startDisplay} + ) : null} -
- -
- - setStartText(event.target.value)} - /> -
- -
- -