diff --git a/.changeset/eleven-phones-notice.md b/.changeset/eleven-phones-notice.md new file mode 100644 index 00000000..530ec23a --- /dev/null +++ b/.changeset/eleven-phones-notice.md @@ -0,0 +1,5 @@ +--- +"@mods/zora-nft-minter": minor +--- + +fix: button layout diff --git a/.changeset/lemon-spoons-raise.md b/.changeset/lemon-spoons-raise.md new file mode 100644 index 00000000..2266e3ab --- /dev/null +++ b/.changeset/lemon-spoons-raise.md @@ -0,0 +1,7 @@ +--- +"@mod-protocol/react-ui-shadcn": patch +"@mod-protocol/react": patch +"@mod-protocol/core": patch +--- + +feat: add optional `href` to avatar component diff --git a/.changeset/orange-spies-teach.md b/.changeset/orange-spies-teach.md new file mode 100644 index 00000000..ecbf2bee --- /dev/null +++ b/.changeset/orange-spies-teach.md @@ -0,0 +1,5 @@ +--- +"@mod-protocol/core": patch +--- + +feat: add `SETSTATE` action diff --git a/.changeset/purple-zebras-stare.md b/.changeset/purple-zebras-stare.md new file mode 100644 index 00000000..f362b353 --- /dev/null +++ b/.changeset/purple-zebras-stare.md @@ -0,0 +1,7 @@ +--- +"@mod-protocol/react-ui-shadcn": patch +"@mod-protocol/react": patch +"@mod-protocol/core": patch +--- + +feat: skeleton loading state on layouts diff --git a/.changeset/silly-students-compete.md b/.changeset/silly-students-compete.md new file mode 100644 index 00000000..b3357d5b --- /dev/null +++ b/.changeset/silly-students-compete.md @@ -0,0 +1,5 @@ +--- +"@mod-protocol/core": patch +--- + +feat: support multiple async actions diff --git a/.changeset/slimy-starfishes-march.md b/.changeset/slimy-starfishes-march.md new file mode 100644 index 00000000..deedfb12 --- /dev/null +++ b/.changeset/slimy-starfishes-march.md @@ -0,0 +1,6 @@ +--- +"@mod-protocol/react-ui-shadcn": patch +"@mod-protocol/core": patch +--- + +feat: add `Padding` element diff --git a/.changeset/wicked-rabbits-beg.md b/.changeset/wicked-rabbits-beg.md new file mode 100644 index 00000000..574dc96f --- /dev/null +++ b/.changeset/wicked-rabbits-beg.md @@ -0,0 +1,5 @@ +--- +"@mod-protocol/react-ui-shadcn": patch +--- + +fix: padding on link element diff --git a/examples/api/package.json b/examples/api/package.json index 3dcf32b7..b7cc8d47 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -16,10 +16,12 @@ "@lit-protocol/types": "^2.2.61", "@mod-protocol/core": "^0.1.1", "@reservoir0x/reservoir-sdk": "^1.8.4", + "@uniswap/smart-order-router": "^3.20.1", "@vercel/postgres-kysely": "^0.6.0", "@zoralabs/protocol-sdk": "^0.5.0", "chatgpt": "^5.2.5", "cheerio": "^1.0.0-rc.12", + "ethers": "^5.7.2", "kysely": "^0.26.3", "next": "^13.5.6", "nft.storage": "^7.1.1", @@ -27,9 +29,11 @@ "pg": "^8.11.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "reverse-mirage": "^1.0.3", "siwe": "^1.1.6", "uint8arrays": "^3.0.0", - "viem": "1.20.1" + "viem": "1.20.1", + "viem2": "npm:viem@^2.0.6" }, "devDependencies": { "@types/node": "^17.0.12", diff --git a/examples/api/src/app/api/erc-20/balances/route.ts b/examples/api/src/app/api/erc-20/balances/route.ts new file mode 100644 index 00000000..c7705ad0 --- /dev/null +++ b/examples/api/src/app/api/erc-20/balances/route.ts @@ -0,0 +1,54 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createPublicClient, formatEther, http } from "viem2"; +import { + chainByName, + getEthUsdPrice, + numberWithCommas, + parseInfoRequestParams, +} from "../lib/utils"; + +export async function GET(request: NextRequest) { + const { blockchain, tokenAddress } = parseInfoRequestParams(request); + const userAddress = request.nextUrl.searchParams + .get("walletAddress") + ?.toLowerCase(); + const buyOptionsUsd = request.nextUrl.searchParams + .get("buyOptionsUsd") + .split(",") + .map((x) => parseFloat(x)); + + if (!tokenAddress || !blockchain) { + return new Response("Missing tokenAddress or blockchain", { + status: 400, + }); + } + + // Get eth balance on blockchain + const chain = chainByName[blockchain]; + const client = createPublicClient({ + transport: http(), + chain, + }); + + const [balance, ethPriceUsd] = await Promise.all([ + client.getBalance({ + address: userAddress as `0x${string}`, + }), + getEthUsdPrice(), + ]); + + const ethBalanceUsd = parseFloat(formatEther(balance)) * Number(ethPriceUsd); + + return NextResponse.json({ + ethBalance: numberWithCommas( + parseFloat(formatEther(balance)).toPrecision(4) + ), + ethPriceUsd, + ethBalanceUsd: numberWithCommas(ethBalanceUsd.toFixed(2)), + chain: { + id: chain.id, + name: chain.name, + }, + buyOptionsUsd: buyOptionsUsd.map((x) => ethBalanceUsd > x), + }); +} diff --git a/examples/api/src/app/api/erc-20/buy/route.ts b/examples/api/src/app/api/erc-20/buy/route.ts new file mode 100644 index 00000000..b7159d7f --- /dev/null +++ b/examples/api/src/app/api/erc-20/buy/route.ts @@ -0,0 +1,53 @@ +import { NextRequest, NextResponse } from "next/server"; +import { fromHex } from "viem2"; +import { + chainByName, + getEthUsdPrice, + getSwapTransaction, + parseInfoRequestParams, +} from "../lib/utils"; + +export async function POST(request: NextRequest) { + const { blockchain, tokenAddress } = parseInfoRequestParams(request); + + const userAddress = request.nextUrl.searchParams + .get("walletAddress") + ?.toLowerCase(); + const buyAmountUsd = parseFloat( + request.nextUrl.searchParams.get("buyAmountUsd") + ); + + if (!tokenAddress || !blockchain) { + return new Response("Missing tokenAddress or blockchain", { + status: 400, + }); + } + + const chain = chainByName[blockchain]; + + const ethPriceUsd = await getEthUsdPrice(); + const ethInputAmount = (buyAmountUsd / ethPriceUsd).toString(); + + const swapRoute = await getSwapTransaction({ + blockchain, + ethInputAmountFormatted: ethInputAmount, + outTokenAddress: tokenAddress, + recipientAddress: userAddress, + feePercentageInt: 5, + feeRecipientAddress: process.env.ERC_20_FEE_RECIPIENT, + }); + + const tx = swapRoute.methodParameters; + + return NextResponse.json({ + transaction: { + from: userAddress, + to: tx.to, + value: fromHex(tx.value as `0x${string}`, { + to: "bigint", + }).toString(), + data: tx.calldata, + }, + explorer: chain.blockExplorers.default, + }); +} diff --git a/examples/api/src/app/api/erc-20/info/holders/route.ts b/examples/api/src/app/api/erc-20/info/holders/route.ts new file mode 100644 index 00000000..759eeb39 --- /dev/null +++ b/examples/api/src/app/api/erc-20/info/holders/route.ts @@ -0,0 +1,27 @@ +import { NextRequest } from "next/server"; +import { + getFollowingHolderInfo, + parseInfoRequestParams, +} from "../../lib/utils"; + +export async function GET(request: NextRequest) { + const { fid, tokenAddress, blockchain } = parseInfoRequestParams(request); + const result = await getFollowingHolderInfo({ + fid, + blockchain, + tokenAddress, + }); + + if (!tokenAddress || !blockchain) { + return new Response("Missing tokenAddress or blockchain", { + status: 400, + }); + } + + return Response.json(result, { + headers: new Headers({ + // Cache for 1 day + "Cache-Control": "public, max-age=86400, immutable", + }), + }); +} diff --git a/examples/api/src/app/api/erc-20/info/price/route.ts b/examples/api/src/app/api/erc-20/info/price/route.ts new file mode 100644 index 00000000..c00f3847 --- /dev/null +++ b/examples/api/src/app/api/erc-20/info/price/route.ts @@ -0,0 +1,23 @@ +import { NextRequest } from "next/server"; +import { getPriceData, parseInfoRequestParams } from "../../lib/utils"; + +export async function GET(request: NextRequest) { + const { tokenAddress, blockchain } = parseInfoRequestParams(request); + const result = await getPriceData({ + blockchain, + tokenAddress, + }); + + if (!tokenAddress || !blockchain) { + return new Response("Missing tokenAddress or blockchain", { + status: 400, + }); + } + + return Response.json(result, { + headers: new Headers({ + // Cache for 1 hour + "Cache-Control": "public, max-age=3600, immutable", + }), + }); +} diff --git a/examples/api/src/app/api/erc-20/info/token/route.ts b/examples/api/src/app/api/erc-20/info/token/route.ts new file mode 100644 index 00000000..46feb05c --- /dev/null +++ b/examples/api/src/app/api/erc-20/info/token/route.ts @@ -0,0 +1,23 @@ +import { NextRequest } from "next/server"; +import { getTokenInfo, parseInfoRequestParams } from "../../lib/utils"; + +export async function GET(request: NextRequest) { + const { tokenAddress, blockchain } = parseInfoRequestParams(request); + const result = await getTokenInfo({ + blockchain, + tokenAddress, + }); + + if (!tokenAddress || !blockchain) { + return new Response("Missing tokenAddress or blockchain", { + status: 400, + }); + } + + return Response.json(result, { + headers: new Headers({ + // Cache for 1 month + "Cache-Control": "public, max-age=2592000, immutable", + }), + }); +} diff --git a/examples/api/src/app/api/erc-20/lib/utils.ts b/examples/api/src/app/api/erc-20/lib/utils.ts new file mode 100644 index 00000000..623d0d7e --- /dev/null +++ b/examples/api/src/app/api/erc-20/lib/utils.ts @@ -0,0 +1,452 @@ +import { FarcasterUser } from "@mod-protocol/core"; +import { Protocol } from "@uniswap/router-sdk"; +import { Percent, Token, TradeType } from "@uniswap/sdk-core"; +import { + AlphaRouter, + AlphaRouterConfig, + CurrencyAmount, + SwapOptions, + SwapType, + nativeOnChain, +} from "@uniswap/smart-order-router"; +import { ethers } from "ethers"; +import JSBI from "jsbi"; +import { NextRequest } from "next/server"; +import { publicActionReverseMirage } from "reverse-mirage"; +import { createPublicClient, http } from "viem2"; +import * as chains from "viem2/chains"; + +export function numberWithCommas(x: string | number) { + var parts = x.toString().split("."); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); + return parts.join("."); +} + +export async function getFollowingHolderInfo({ + fid, + tokenAddress, + blockchain, +}: { + fid: string; + tokenAddress: string; + blockchain: string; +}): Promise<{ + holders: { user: FarcasterUser; amount: number }[]; + holdersCount: number; +}> { + const { ERC_20_AIRSTACK_API_KEY } = process.env; + const AIRSTACK_API_URL = "https://api.airstack.xyz/gql"; + const airstackQuery = ` +query MyQuery($identity: Identity!, $token_address: Address!, $blockchain: TokenBlockchain, $cursor: String) { + SocialFollowings( + input: { + filter: { + identity: {_eq: $identity}, + dappName: {_eq: farcaster} + }, + blockchain: ALL, + limit: 200, + cursor: $cursor + } + ) { + pageInfo { + hasNextPage + nextCursor + } + Following { + followingProfileId, + followingAddress { + socials { + profileDisplayName + profileName + profileImage + profileBio + } + tokenBalances( + input: { + filter: { + tokenAddress: {_eq: $token_address}, + formattedAmount: {_gt: 0} + }, + blockchain: $blockchain + } + ) { + owner { + identity + } + formattedAmount + } + } + } + } +} +`; + + const acc: any[] = []; + + let hasNextPage = true; + let cursor = ""; + + try { + while (hasNextPage) { + hasNextPage = false; + const res = await fetch(AIRSTACK_API_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: ERC_20_AIRSTACK_API_KEY, // Add API key to Authorization header + }, + body: JSON.stringify({ + query: airstackQuery, + variables: { + identity: `fc_fid:${fid}`, + token_address: tokenAddress, + blockchain, + cursor, + }, + }), + }); + const json = await res?.json(); + const result = json?.data.SocialFollowings.Following.filter( + (item) => item.followingAddress.tokenBalances?.length > 0 + ); + acc.push(...result); + + hasNextPage = json?.data.SocialFollowings.pageInfo.hasNextPage; + cursor = json?.data.SocialFollowings.pageInfo.nextCursor; + } + } catch (error) { + console.error(error); + } + + const result = acc + .map((item) => { + const socialData = item.followingAddress.socials[0]; + return { + user: { + displayName: socialData.profileDisplayName, + username: socialData.profileName, + fid: item.followingProfileId, + pfp: socialData.profileImage, + } as FarcasterUser, + amount: item.followingAddress.tokenBalances[0].formattedAmount, + }; + }) + .sort((a, b) => Number(b.amount) - Number(a.amount)); + + return { holders: result, holdersCount: result.length }; +} + +export async function getPriceData({ + tokenAddress, + blockchain, +}: { + tokenAddress: string; + blockchain: string; +}): Promise<{ + unitPriceUsd: string; + marketCapUsd?: string; + volume24hUsd?: string; + change24h?: string; +}> { + // https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=0xd7c1eb0fe4a30d3b2a846c04aa6300888f087a5f&vs_currencies=usd&points&vs_currencies=usd&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true&include_last_updated_at=true + const params = new URLSearchParams({ + contract_addresses: tokenAddress, + vs_currencies: "usd", + include_market_cap: "true", + include_24hr_vol: "true", + include_24hr_change: "true", + include_last_updated_at: "true", + }); + const coingecko = await fetch( + `https://api.coingecko.com/api/v3/simple/token_price/${blockchain}?${params.toString()}` + ); + const coingeckoJson = await coingecko.json(); + + if (coingeckoJson[tokenAddress]) { + const { + usd: unitPriceUsd, + usd_market_cap: marketCapUsd, + usd_24h_vol: volume24hUsd, + usd_24h_change: change24h, + } = coingeckoJson[tokenAddress]; + + const unitPriceUsdFormatted = `${numberWithCommas( + parseFloat(unitPriceUsd).toPrecision(4) + )}`; + const marketCapUsdFormatted = `${parseFloat( + parseFloat(marketCapUsd).toFixed(0) + ).toLocaleString()}`; + const volume24hUsdFormatted = `${parseFloat( + parseFloat(volume24hUsd).toFixed(0) + ).toLocaleString()}`; + + const change24hNumber = parseFloat(change24h); + const change24hPartial = parseFloat( + change24hNumber.toFixed(2) + ).toLocaleString(); + const change24hFormatted = + change24hNumber > 0 ? `+${change24hPartial}%` : `-${-change24hPartial}%`; + + return { + unitPriceUsd: unitPriceUsdFormatted, + marketCapUsd: marketCapUsdFormatted, + volume24hUsd: volume24hUsdFormatted, + change24h: change24hFormatted, + }; + } + + // Use on-chain data as fallback + // TODO: Query uniswap contracts directly + const chain = chainByName[blockchain.toLowerCase()]; + const url = `https://api.1inch.dev/price/v1.1/${chain.id}/${tokenAddress}?currency=USD`; + const res = await fetch(url, { + headers: { + Authorization: `Bearer ${process.env["ERC_20_1INCH_API_KEY"]}`, + }, + }); + const resJson = await res.json(); + + return { + unitPriceUsd: parseFloat(resJson[tokenAddress]).toPrecision(4), + }; +} + +export async function getTokenInfo({ + tokenAddress, + blockchain, +}: { + tokenAddress: string; + blockchain: string; +}): Promise<{ + symbol: string; + name: string; + url: string; + image?: string; +}> { + const res = await fetch( + `https://api.coingecko.com/api/v3/coins/${blockchain}/contract/${tokenAddress}` + ); + + if (res.ok) { + const json = await res?.json(); + return { + symbol: json.symbol.toUpperCase(), + name: json.name, + image: json.image?.small, + url: `https://www.coingecko.com/en/coins/${json.id}`, + }; + } + + // Use on-chain data as fallback + const chain = chainByName[blockchain]; + const client = createPublicClient({ + transport: http(), + chain, + }).extend(publicActionReverseMirage); + + const token = await client.getERC20({ + erc20: { + address: tokenAddress as `0x${string}`, + chainID: chain.id, + }, + }); + + return { + symbol: token.symbol.toUpperCase(), + name: token.name, + url: `https://app.uniswap.org/tokens/${blockchain}/${tokenAddress}`, + }; +} +export const chainByName: { [key: string]: chains.Chain } = Object.entries( + chains +).reduce( + (acc: { [key: string]: chains.Chain }, [key, chain]) => { + acc[key] = chain; + return acc; + }, + { ethereum: chains.mainnet } // Convenience for ethereum, which is 'homestead' otherwise +); + +export function parseTokenParam(tokenParam: string) { + let tokenAddress: string; + let blockchain: string; + + // Splitting the string at '/erc20:' + const parts = tokenParam.split("/erc20:"); + + // Extracting the chain ID + const chainIdPart = parts[0]; + const chainId = chainIdPart.split(":")[1]; + + // The token address is the second part of the split, but without '0x' if present + tokenAddress = parts[1]; + + const [blockchainName] = Object.entries(chainByName).find( + ([, value]) => value.id.toString() == chainId + ); + blockchain = blockchainName; + + return { + tokenAddress, + blockchain, + }; +} + +export async function getEthUsdPrice(): Promise { + const client = createPublicClient({ + transport: http(), + chain: chains.mainnet, + }); + + // roundId uint80, answer int256, startedAt uint256, updatedAt uint256, answeredInRound uint80 + const [, answer] = await client.readContract({ + abi: [ + { + inputs: [], + name: "latestRoundData", + outputs: [ + { internalType: "uint80", name: "roundId", type: "uint80" }, + { internalType: "int256", name: "answer", type: "int256" }, + { internalType: "uint256", name: "startedAt", type: "uint256" }, + { internalType: "uint256", name: "updatedAt", type: "uint256" }, + { internalType: "uint80", name: "answeredInRound", type: "uint80" }, + ], + stateMutability: "view", + type: "function", + }, + ], + functionName: "latestRoundData", + // https://docs.chain.link/data-feeds/price-feeds/addresses?network=ethereum&page=1&search=usdc#ethereum-mainnet + address: "0x986b5E1e1755e3C2440e960477f25201B0a8bbD4", + }); + + const ethPriceUsd = (1 / Number(answer)) * 1e18; + + return ethPriceUsd; +} + +export async function getSwapTransaction({ + outTokenAddress, + blockchain, + ethInputAmountFormatted, + recipientAddress, + feeRecipientAddress, + feePercentageInt, +}: { + outTokenAddress: string; + blockchain: string; + ethInputAmountFormatted: string; + recipientAddress: string; + feePercentageInt?: number; + feeRecipientAddress?: string; +}) { + const tokenOut = await getUniswapToken({ + tokenAddress: outTokenAddress, + blockchain, + }); + const chain = chainByName[blockchain]; + const provider = new ethers.providers.JsonRpcProvider( + chain.rpcUrls.default.http[0] + ); + + const router = new AlphaRouter({ + chainId: chain.id, + provider, + }); + + const tokenIn = nativeOnChain(chain.id); + const amountIn = CurrencyAmount.fromRawAmount( + tokenIn, + JSBI.BigInt( + ethers.utils.parseUnits(ethInputAmountFormatted, tokenIn.decimals) + ) + ); + + let swapOptions: SwapOptions = { + type: SwapType.UNIVERSAL_ROUTER, + recipient: recipientAddress, + slippageTolerance: new Percent(5, 100), + deadlineOrPreviousBlockhash: parseDeadline("360"), + fee: + feeRecipientAddress && feePercentageInt + ? { + fee: new Percent(feePercentageInt, 100), + recipient: feeRecipientAddress, + } + : undefined, + }; + + const partialRoutingConfig: Partial = { + protocols: [Protocol.V2, Protocol.V3], + }; + + const quote = await router.route( + amountIn, + tokenOut, + TradeType.EXACT_INPUT, + swapOptions, + partialRoutingConfig + ); + + if (!quote) return; + return quote; +} + +async function getUniswapToken({ + tokenAddress, + blockchain, +}: { + tokenAddress: string; + blockchain: string; +}): Promise { + const chain = chainByName[blockchain]; + const client = createPublicClient({ + transport: http(), + chain, + }).extend(publicActionReverseMirage); + + const token = await client.getERC20({ + erc20: { + address: tokenAddress as `0x${string}`, + chainID: chain.id, + }, + }); + + const uniswapToken = new Token( + chain.id, + tokenAddress, + token.decimals, + token.symbol, + token.name + ); + + return uniswapToken; +} + +function parseDeadline(deadline: string): number { + return Math.floor(Date.now() / 1000) + parseInt(deadline); +} + +export function parseInfoRequestParams(request: NextRequest) { + const fid = request.nextUrl.searchParams.get("fid"); + const token = request.nextUrl.searchParams.get("token")?.toLowerCase(); + let tokenAddress = request.nextUrl.searchParams + .get("tokenAddress") + ?.toLowerCase(); + let blockchain = request.nextUrl.searchParams + .get("blockchain") + ?.toLowerCase(); + + if (token) { + const parsedToken = parseTokenParam(token); + tokenAddress = parsedToken.tokenAddress; + blockchain = parsedToken.blockchain; + } + + return { + fid, + tokenAddress, + blockchain, + }; +} diff --git a/examples/nextjs-shadcn/src/app/dummy-casts.ts b/examples/nextjs-shadcn/src/app/dummy-casts.ts index 3968f8f1..9694d3b4 100644 --- a/examples/nextjs-shadcn/src/app/dummy-casts.ts +++ b/examples/nextjs-shadcn/src/app/dummy-casts.ts @@ -165,4 +165,24 @@ export const dummyCastData: Array<{ }, ], }, + { + avatar_url: + "https://res.cloudinary.com/merkle-manufactory/image/fetch/c_fill,f_png,w_144/https%3A%2F%2Flh3.googleusercontent.com%2F-S5cdhOpZtJ_Qzg9iPWELEsRTkIsZ7qGYmVlwEORgFB00WWAtZGefRnS4Bjcz5ah40WVOOWeYfU5pP9Eekikb3cLMW2mZQOMQHlWhg", + display_name: "David Furlong", + username: "df", + timestamp: "2023-08-17 09:16:52.293739", + text: "Just bought this token 🚀🚀🚀", + embeds: [ + { + url: "eip155:1/erc20:0xd7c1eb0fe4a30d3b2a846c04aa6300888f087a5f", + status: "loaded", + metadata: {}, + }, + { + url: "eip155:8453/erc20:0x4ed4e862860bed51a9570b96d89af5e1b0efefed", + status: "loaded", + metadata: {}, + }, + ], + }, ]; diff --git a/examples/nextjs-shadcn/src/app/embeds.tsx b/examples/nextjs-shadcn/src/app/embeds.tsx index c753175b..81b5f05f 100644 --- a/examples/nextjs-shadcn/src/app/embeds.tsx +++ b/examples/nextjs-shadcn/src/app/embeds.tsx @@ -7,21 +7,22 @@ import { SendEthTransactionActionResolverInit, } from "@mod-protocol/core"; import { - richEmbedMods, defaultRichEmbedMod, + richEmbedMods, richEmbedModsExperimental, } from "@mod-protocol/mod-registry"; import { RichEmbed } from "@mod-protocol/react"; +import "@mod-protocol/react-ui-shadcn/dist/public/video-js.css"; import { renderers } from "@mod-protocol/react-ui-shadcn/dist/renderers"; import { sendTransaction, switchNetwork, waitForTransaction, + getNetwork, } from "@wagmi/core"; import { useMemo } from "react"; import { useAccount } from "wagmi"; import { useExperimentalMods } from "./use-experimental-mods"; -import "@mod-protocol/react-ui-shadcn/dist/public/video-js.css"; export function Embeds(props: { embeds: Array }) { const experimentalMods = useExperimentalMods(); @@ -41,7 +42,10 @@ export function Embeds(props: { embeds: Array }) { const parsedChainId = parseInt(chainId); // Switch chains if the user is not on the right one - await switchNetwork({ chainId: parsedChainId }); + // Note: silently fails if switching to the same chain + const network = getNetwork(); + if (network.chain.id !== parsedChainId) + await switchNetwork({ chainId: parsedChainId }); // Send the transaction const { hash } = await sendTransaction({ @@ -58,6 +62,7 @@ export function Embeds(props: { embeds: Array }) { onConfirmed(hash, status === "success"); } catch (e) { + console.error(e); onError(e); } }, @@ -71,6 +76,9 @@ export function Embeds(props: { embeds: Array }) { wallet: { address, }, + farcaster: { + fid: "1214", + }, }, }; }, [address]); diff --git a/examples/nextjs-shadcn/src/app/page.tsx b/examples/nextjs-shadcn/src/app/page.tsx index 27a6ed38..eb65a709 100644 --- a/examples/nextjs-shadcn/src/app/page.tsx +++ b/examples/nextjs-shadcn/src/app/page.tsx @@ -101,6 +101,8 @@ export default function Page() {

Embed renderers

+

ERC-20 Mod

+

NFT Mod with native minting

Video Mod

diff --git a/mods/erc-20/index.ts b/mods/erc-20/index.ts new file mode 100644 index 00000000..8e71bc78 --- /dev/null +++ b/mods/erc-20/index.ts @@ -0,0 +1 @@ +export { default as default } from "./src/manifest"; diff --git a/mods/erc-20/package.json b/mods/erc-20/package.json new file mode 100644 index 00000000..06db49fc --- /dev/null +++ b/mods/erc-20/package.json @@ -0,0 +1,10 @@ +{ + "name": "@mods/erc-20", + "main": "./index.ts", + "types": "./index.ts", + "version": "0.1.0", + "private": true, + "dependencies": { + "@mod-protocol/core": "^0.1.0" + } +} \ No newline at end of file diff --git a/mods/erc-20/src/buying.ts b/mods/erc-20/src/buying.ts new file mode 100644 index 00000000..4e5437b3 --- /dev/null +++ b/mods/erc-20/src/buying.ts @@ -0,0 +1,174 @@ +import { ModElement } from "@mod-protocol/core"; + +const buy: ModElement[] = [ + { + type: "padding", + elements: [ + // If no buy amount, go back to #view + { + type: "horizontal-layout", + elements: [ + { + if: { + value: "{{refs.buyAmountUsd}}", + match: { + equals: "", + }, + }, + then: { + type: "vertical-layout", + onload: "#view", + }, + }, + ], + }, + // + { + if: [ + { + value: "{{refs.swapTx.isSuccess}}", + match: { + NOT: { + equals: "true", + }, + }, + }, + ], + then: { + type: "horizontal-layout", + elements: [ + { + type: "circular-progress", + }, + { + if: { + value: "{{refs.swapTxDataReq.response.data}}", + match: { + NOT: { + equals: "", + }, + }, + }, + then: { + type: "text", + label: + "Buying ${{refs.buyAmountUsd}} of ${{refs.tokenReq.response.data.symbol}}...", + variant: "secondary", + }, + else: { + type: "text", + label: + "Calculating best swap route for ${{refs.tokenReq.response.data.symbol}}...", + variant: "secondary", + }, + }, + ], + }, + }, + + { + type: "vertical-layout", + onload: { + type: "POST", + url: "{{api}}/erc-20/buy?walletAddress={{user.wallet.address}}&token={{embed.url}}&buyAmountUsd={{refs.buyAmountUsd}}", + ref: "swapTxDataReq", + onsuccess: { + type: "SENDETHTRANSACTION", + ref: "swapTx", + txData: { + from: "{{refs.swapTxDataReq.response.data.transaction.from}}", + to: "{{refs.swapTxDataReq.response.data.transaction.to}}", + value: "{{refs.swapTxDataReq.response.data.transaction.value}}", + data: "{{refs.swapTxDataReq.response.data.transaction.data}}", + }, + chainId: "{{refs.balancesReq.response.data.chain.id}}", + onerror: { + type: "SETSTATE", + state: { + buyAmountUsd: "", + }, + }, + }, + onerror: { + type: "SETSTATE", + state: { + buyAmountUsd: "", + }, + }, + }, + }, + { + if: { + value: "{{refs.swapTx.hash}}", + match: { + NOT: { + equals: "", + }, + }, + }, + then: { + if: { + value: "{{refs.swapTx.isSuccess}}", + match: { + equals: "true", + }, + }, + then: { + type: "horizontal-layout", + elements: [ + { + type: "button", + label: "Back", + variant: "secondary", + onclick: { + type: "SETSTATE", + state: { + isBuying: "false", + buyAmountUsd: "", + }, + }, + }, + { + type: "text", + label: "Transaction successful", + variant: "secondary", + }, + { + type: "link", + label: "Explorer", + url: "{{refs.swapTxDataReq.response.data.explorer.url}}/tx/{{refs.swapTx.hash}}", + }, + ], + }, + else: { + if: { + value: "{{refs.swapTx.isSuccess}}", + match: { + equals: "false", + }, + }, + then: { + type: "link", + label: "Failed", + variant: "link", + url: "{{refs.swapTxDataReq.response.data.explorer.url}}/tx/{{refs.swapTx.hash}}", + }, + else: { + type: "horizontal-layout", + elements: [ + { + type: "link", + label: "Confirming...", + variant: "link", + url: "{{refs.swapTxDataReq.response.data.explorer.url}}/tx/{{refs.swapTx.hash}}", + }, + ], + }, + }, + }, + }, + ], + }, +]; + +export default buy; diff --git a/mods/erc-20/src/manifest.ts b/mods/erc-20/src/manifest.ts new file mode 100644 index 00000000..41eaa29c --- /dev/null +++ b/mods/erc-20/src/manifest.ts @@ -0,0 +1,35 @@ +import { ModManifest } from "@mod-protocol/core"; +import view from "./view"; +import buying from "./buying"; + +const manifest: ModManifest = { + slug: "erc-20", + name: "ERC-20", + custodyAddress: "0xdcC59cF0Adf4175973D4abc8c0715f83f90d2f1d", + version: "0.0.1", + logo: "", + custodyGithubUsername: "stephancill", + richEmbedEntrypoints: [ + { + if: { + value: "{{embed.url}}", + match: { + OR: [ + { startsWith: "https://app.uniswap.org/tokens/" }, + { + regex: "eip155:(\\d+)/erc20:0x([0-9a-fA-F]+)", + }, + ], + }, + }, + element: view, + }, + ], + elements: { + "#view": view, + "#buying": buying, + }, + permissions: ["user.wallet.address"], // "user.farcaster.fid" +}; + +export default manifest; diff --git a/mods/erc-20/src/view.ts b/mods/erc-20/src/view.ts new file mode 100644 index 00000000..8ea272bb --- /dev/null +++ b/mods/erc-20/src/view.ts @@ -0,0 +1,351 @@ +import { ModElement } from "@mod-protocol/core"; + +const view: ModElement[] = [ + { + type: "vertical-layout", + elements: [ + { + type: "padding", + elements: [ + // Go to #buying to execute order if there is a buyAmountUsd value + { + if: { + value: "{{refs.buyAmountUsd}}", + match: { + NOT: { + equals: "", + }, + }, + }, + then: { + type: "horizontal-layout", + onload: "#buying", + }, + }, + { + if: [ + { + value: "{{refs.tokenReq.response.data}}", + match: { + NOT: { + equals: "", + }, + }, + }, + { + value: "{{refs.priceReq.response.data}}", + match: { + NOT: { + equals: "", + }, + }, + }, + ], + then: { + type: "vertical-layout", + elements: [ + { + type: "horizontal-layout", + elements: [ + { + type: "horizontal-layout", + elements: [ + { + if: { + value: "{{refs.tokenReq.response.data.image}}", + match: { + NOT: { + equals: "", + }, + }, + }, + then: { + type: "avatar", + src: "{{refs.tokenReq.response.data.image}}", + href: "{{refs.tokenReq.response.data.url}}", + }, + }, + { + type: "link", + label: "${{refs.tokenReq.response.data.symbol}}", + variant: "link", + url: "{{refs.tokenReq.response.data.url}}", + }, + { + type: "text", + variant: "secondary", + label: + "${{refs.priceReq.response.data.unitPriceUsd}}", + }, + { + if: { + value: "{{refs.priceReq.response.data.change24h}}", + match: { + NOT: { + equals: "", + }, + }, + }, + then: { + type: "text", + variant: "secondary", + label: + "24h: {{refs.priceReq.response.data.change24h}}", + }, + }, + ], + }, + { + if: { + value: "{{user.wallet.address}}", + match: { + NOT: { + equals: "", + }, + }, + }, + then: { + if: { + value: "{{refs.isBuying}}", + match: { + NOT: { + equals: "true", + }, + }, + }, + then: { + type: "button", + label: "Buy", + variant: "secondary", + onclick: { + type: "SETSTATE", + state: { + isBuying: "true", + }, + }, + }, + }, + }, + ], + }, + { + if: { + value: "{{refs.isBuying}}", + match: { + equals: "true", + }, + }, + then: { + type: "vertical-layout", + onload: { + type: "GET", + url: "{{api}}/erc-20/balances?walletAddress={{user.wallet.address}}&token={{embed.url}}&buyOptionsUsd=5,50,500", + ref: "balancesReq", + }, + elements: [ + { + type: "horizontal-layout", + elements: [ + { + if: { + value: "{{refs.balancesReq.response.data}}", + match: { + NOT: { + equals: "", + }, + }, + }, + then: { + type: "vertical-layout", + elements: [ + { + if: { + value: + "{{refs.balancesReq.response.data.buyOptionsUsd[0]}}", + match: { + equals: "true", + }, + }, + then: { + type: "horizontal-layout", + elements: [ + { + if: { + value: + "{{refs.balancesReq.response.data.buyOptionsUsd[0]}}", + match: { + equals: "true", + }, + }, + then: { + type: "button", + label: "$5.00", + onclick: { + type: "SETSTATE", + state: { + buyAmountUsd: "5.00", + }, + }, + }, + }, + { + if: { + value: + "{{refs.balancesReq.response.data.buyOptionsUsd[1]}}", + match: { + equals: "true", + }, + }, + then: { + type: "button", + label: "$50.00", + onclick: { + type: "SETSTATE", + state: { + buyAmountUsd: "50.00", + }, + }, + }, + }, + { + if: { + value: + "{{refs.balancesReq.response.data.buyOptionsUsd[2]}}", + match: { + equals: "true", + }, + }, + then: { + type: "button", + label: "$500.00", + onclick: { + type: "SETSTATE", + state: { + buyAmountUsd: "500.00", + }, + }, + }, + }, + { + if: { + value: "{{refs.isBuying}}", + match: { + equals: "true", + }, + }, + then: { + type: "button", + label: "Cancel", + variant: "secondary", + onclick: { + type: "SETSTATE", + state: { + isBuying: "false", + }, + }, + }, + }, + ], + }, + else: { + type: "text", + label: + "Not enough ETH on {{refs.balancesReq.response.data.chain.name}}", + }, + }, + ], + }, + else: { + type: "horizontal-layout", + loading: true, + }, + }, + ], + }, + ], + }, + }, + { + if: [ + { + value: "{{refs.isBuying}}", + match: { + equals: "true", + }, + }, + { + value: "{{refs.balancesReq.response.data.ethBalance}}", + match: { + NOT: { + equals: "", + }, + }, + }, + ], + then: { + type: "text", + label: + "{{refs.balancesReq.response.data.chain.name}} balance: {{refs.balancesReq.response.data.ethBalance}} ETH ({{refs.balancesReq.response.data.ethBalanceUsd}} USD)", + variant: "secondary", + }, + else: { + if: { + value: "{{refs.holdersReq.response.data}}", + match: { + NOT: { + equals: "", + }, + }, + }, + then: { + type: "text", + variant: "secondary", + label: + "{{refs.holdersReq.response.data.holdersCount}} holders you follow", + }, + else: { + type: "horizontal-layout", + loading: true, + }, + }, + }, + ], + }, + else: { type: "horizontal-layout", loading: true }, + }, + ], + }, + { + type: "horizontal-layout", + elements: [ + { + type: "horizontal-layout", + onload: { + type: "GET", + url: "{{api}}/erc-20/info/token?fid={{user.farcaster.fid}}&token={{embed.url}}", + ref: "tokenReq", + }, + }, + { + type: "horizontal-layout", + onload: { + type: "GET", + url: "{{api}}/erc-20/info/holders?fid={{user.farcaster.fid}}&token={{embed.url}}", + ref: "holdersReq", + }, + }, + { + type: "horizontal-layout", + onload: { + type: "GET", + url: "{{api}}/erc-20/info/price?fid={{user.farcaster.fid}}&token={{embed.url}}", + ref: "priceReq", + }, + }, + ], + }, + ], + }, +]; + +export default view; diff --git a/mods/erc-20/tsconfig.json b/mods/erc-20/tsconfig.json new file mode 100644 index 00000000..37906aab --- /dev/null +++ b/mods/erc-20/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "tsconfig/base.json", + "include": ["."], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/mods/zora-nft-minter/src/view.ts b/mods/zora-nft-minter/src/view.ts index 35ce74d1..5aea0543 100644 --- a/mods/zora-nft-minter/src/view.ts +++ b/mods/zora-nft-minter/src/view.ts @@ -8,104 +8,115 @@ const view: ModElement[] = [ topLeftBadge: "{{embed.metadata.nft.collection.creator.username}}", elements: [ { - type: "horizontal-layout", + type: "vertical-layout", elements: [ { - type: "avatar", - src: "{{api}}/nft-chain-logo?chain={{embed.metadata.nft.collection.chain}}", - }, - { - type: "text", - label: "{{embed.metadata.nft.collection.name}}", - }, - { - if: { - value: "{{user.wallet.address}}", - match: { - NOT: { - equals: "", - }, - }, - }, - then: { - if: { - value: "{{refs.mintTx.hash}}", - match: { - NOT: { - equals: "", + type: "horizontal-layout", + elements: [ + { + type: "horizontal-layout", + elements: [ + { + type: "avatar", + src: "{{api}}/nft-chain-logo?chain={{embed.metadata.nft.collection.chain}}", }, - }, + { + type: "text", + label: "{{embed.metadata.nft.collection.name}}", + }, + ], }, - then: { + { if: { - value: "{{refs.mintTx.isSuccess}}", + value: "{{user.wallet.address}}", match: { - equals: "true", + NOT: { + equals: "", + }, }, }, then: { - type: "link", - label: "View NFT", - url: "{{refs.txDataRequest.response.data.explorer.url}}/tx/{{refs.mintTx.hash}}", - }, - else: { if: { - value: "{{refs.mintTx.isSuccess}}", + value: "{{refs.mintTx.hash}}", match: { - equals: "false", + NOT: { + equals: "", + }, }, }, then: { - type: "link", - label: "Failed", - variant: "link", - url: "{{refs.txDataRequest.response.data.explorer.url}}/tx/{{refs.mintTx.hash}}", - }, - else: { - type: "horizontal-layout", - elements: [ - { + if: { + value: "{{refs.mintTx.isSuccess}}", + match: { + equals: "true", + }, + }, + then: { + type: "link", + label: "View NFT", + url: "{{refs.txDataRequest.response.data.explorer.url}}/tx/{{refs.mintTx.hash}}", + }, + else: { + if: { + value: "{{refs.mintTx.isSuccess}}", + match: { + equals: "false", + }, + }, + then: { type: "link", - label: "Confirming...", + label: "Failed", variant: "link", url: "{{refs.txDataRequest.response.data.explorer.url}}/tx/{{refs.mintTx.hash}}", }, - { - type: "circular-progress", + else: { + type: "horizontal-layout", + elements: [ + { + type: "link", + label: "Confirming...", + variant: "link", + url: "{{refs.txDataRequest.response.data.explorer.url}}/tx/{{refs.mintTx.hash}}", + }, + { + type: "circular-progress", + }, + ], }, - ], + }, }, - }, - }, - else: { - type: "button", - label: "Mint", - onclick: { - type: "GET", - ref: "txDataRequest", - url: "{{api}}/nft-minter?taker={{user.wallet.address}}&itemId={{embed.metadata.nft.collection.id}}/{{embed.metadata.nft.tokenId}}", - onsuccess: { - type: "SENDETHTRANSACTION", - ref: "mintTx", - txData: { - from: "{{refs.txDataRequest.response.data.data.from}}", - to: "{{refs.txDataRequest.response.data.data.to}}", - value: "{{refs.txDataRequest.response.data.data.value}}", - data: "{{refs.txDataRequest.response.data.data.data}}", + else: { + type: "button", + label: "Mint", + onclick: { + type: "GET", + ref: "txDataRequest", + url: "{{api}}/nft-minter?taker={{user.wallet.address}}&itemId={{embed.metadata.nft.collection.id}}/{{embed.metadata.nft.tokenId}}", + onsuccess: { + type: "SENDETHTRANSACTION", + ref: "mintTx", + txData: { + from: "{{refs.txDataRequest.response.data.data.from}}", + to: "{{refs.txDataRequest.response.data.data.to}}", + value: + "{{refs.txDataRequest.response.data.data.value}}", + data: "{{refs.txDataRequest.response.data.data.data}}", + }, + chainId: "{{refs.txDataRequest.response.data.chainId}}", + }, }, - chainId: "{{refs.txDataRequest.response.data.chainId}}", + }, + }, + else: { + type: "button", + label: "Mint", + onclick: { + type: "OPENLINK", + url: "{{embed.metadata.nft.mintUrl}}", }, }, }, - }, - else: { - type: "button", - label: "Mint", - onclick: { - type: "OPENLINK", - url: "{{embed.metadata.nft.mintUrl}}", - }, - }, + ], }, ], }, diff --git a/packages/core/src/manifest.ts b/packages/core/src/manifest.ts index 3e47b32c..12fcf04c 100644 --- a/packages/core/src/manifest.ts +++ b/packages/core/src/manifest.ts @@ -30,7 +30,9 @@ export type ModManifest = { /** A definition map of reusable elements, using their id as the key */ elements?: Record; /** Permissions requested by the Mod */ - permissions?: Array<"user.wallet.address" | "web3.eth.personal.sign">; + permissions?: Array< + "user.wallet.address" | "web3.eth.personal.sign" | "user.farcaster.fid" + >; }; export type ModEvent = @@ -130,6 +132,11 @@ type OpenLinkAction = BaseAction & { url: string; }; +type SetStateAction = BaseAction & { + type: "SETSTATE"; + state: { [key: string]: string }; +}; + export type EthPersonalSignData = { statement: string; version: string; @@ -171,6 +178,7 @@ export type ModAction = | AddEmbedAction | SetInputAction | OpenLinkAction + | SetStateAction | EthPersonalSignAction | SendEthTransactionAction | ExitAction; @@ -208,11 +216,13 @@ export type ModElement = type: "horizontal-layout"; elements?: string | ElementOrConditionalFlow[]; onload?: ModEvent; + loading?: boolean; } | { type: "vertical-layout"; elements?: string | ElementOrConditionalFlow[]; onload?: ModEvent; + loading?: boolean; } | { type: "textarea"; @@ -283,6 +293,7 @@ export type ModElement = | { type: "avatar"; src: string; + href?: string; } | ({ type: "card"; @@ -305,4 +316,9 @@ export type ModElement = bottomLeftBadge?: never; bottomRightBadge?: never; } - )); + )) + | { + type: "padding"; + elements?: string | ElementOrConditionalFlow[]; + onclick?: ModEvent; + }; diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts index adce7deb..848cdeeb 100644 --- a/packages/core/src/renderer.ts +++ b/packages/core/src/renderer.ts @@ -62,6 +62,7 @@ export type ModElementRef = onLoad: () => void; }; elements?: T[]; + isLoading?: boolean; } | { type: "vertical-layout"; @@ -69,6 +70,7 @@ export type ModElementRef = onLoad: () => void; }; elements?: T[]; + isLoading?: boolean; } | { type: "combobox"; @@ -143,6 +145,7 @@ export type ModElementRef = | { type: "avatar"; src: string; + href?: string; } | { type: "card"; @@ -156,6 +159,10 @@ export type ModElementRef = events: { onClick: () => void; }; + } + | { + type: "padding"; + elements?: T[]; }; export type BaseContext = { @@ -275,6 +282,11 @@ export interface OpenLinkActionResolver { ): void; } +export interface SetStateActionResolver { + ref: string; + value: string; +} + export type EthPersonalSignActionResolverInit = { data: EthPersonalSignData; }; @@ -444,13 +456,17 @@ export type RendererOptions = { } ); +const DEFAULT_ASYNC_ACTION_KEY = "__default"; + export class Renderer { private interrupted: boolean = false; private currentTree: ModElement[] = []; - private asyncAction: { - promise: Promise; - ref: ModAction; - } | null = null; + private asyncActions: { + [key: string]: { + promise: Promise; + ref: ModAction; + } | null; + } = {}; private refs: Record = {}; private context: Readonly; private manifestContext: Record = {}; @@ -599,6 +615,11 @@ export class Renderer { return options; } private executeAction(action: ModAction) { + const actionRef = + "ref" in action + ? action.ref || DEFAULT_ASYNC_ACTION_KEY + : DEFAULT_ASYNC_ACTION_KEY; + switch (action.type) { case "GET": case "POST": @@ -617,18 +638,18 @@ export class Renderer { onAbort: () => { // TODO: we should probably support this resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } }, onSuccess: (response) => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.ref) { set(this.refs, action.ref, { response, progress: 100 }); @@ -640,7 +661,10 @@ export class Renderer { } }, onUploadProgress: (progress) => { - if (action.ref && this.asyncAction?.promise === promise) { + if ( + action.ref && + this.asyncActions[action.ref]?.promise === promise + ) { set(this.refs, action.ref, { progress }); this.onTreeChange(); } @@ -648,7 +672,7 @@ export class Renderer { onError: (error) => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } @@ -657,7 +681,7 @@ export class Renderer { this.onTreeChange(); } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.onerror) { this.stepIntoOrTriggerAction(action.onerror); @@ -667,7 +691,7 @@ export class Renderer { }, 1); }); - this.asyncAction = { + this.asyncActions[actionRef] = { promise, ref: action, }; @@ -686,11 +710,11 @@ export class Renderer { onAbort: () => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.oncancel) { this.stepIntoOrTriggerAction(action.oncancel); @@ -701,11 +725,11 @@ export class Renderer { onSuccess: (files) => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (!files.length) { if (action.oncancel) { @@ -728,7 +752,7 @@ export class Renderer { onError: (error) => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } @@ -736,7 +760,7 @@ export class Renderer { set(this.refs, action.ref, { error }); } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.onerror) { this.stepIntoOrTriggerAction(action.onerror); @@ -749,7 +773,7 @@ export class Renderer { }, 1); }); - this.asyncAction = { + this.asyncActions[actionRef] = { promise, ref: action, }; @@ -766,11 +790,11 @@ export class Renderer { onSuccess: (input) => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.ref) { set(this.refs, action.ref, input); @@ -785,7 +809,7 @@ export class Renderer { }, 1); }); - this.asyncAction = { + this.asyncActions[actionRef] = { promise, ref: action, }; @@ -804,11 +828,11 @@ export class Renderer { onSuccess: () => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.onsuccess) { this.stepIntoOrTriggerAction(action.onsuccess); @@ -819,7 +843,7 @@ export class Renderer { }, 1); }); - this.asyncAction = { + this.asyncActions[actionRef] = { promise, ref: action, }; @@ -842,11 +866,11 @@ export class Renderer { onSuccess: () => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.onsuccess) { this.stepIntoOrTriggerAction(action.onsuccess); @@ -857,12 +881,19 @@ export class Renderer { }, 1); }); - this.asyncAction = { + this.asyncActions[actionRef] = { promise, ref: action, }; break; } + case "SETSTATE": { + Object.entries(action.state).map(([key, value]) => { + set(this.refs, key, this.replaceInlineContext(value)); + }); + this.onTreeChange(); + break; + } case "web3.eth.personal.sign": { const promise = new Promise((resolve) => { setTimeout(() => { @@ -881,11 +912,11 @@ export class Renderer { onSuccess: ({ signature, signedMessage, address }) => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.ref) { set(this.refs, action.ref, { @@ -902,7 +933,7 @@ export class Renderer { onError: (error) => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } @@ -910,7 +941,7 @@ export class Renderer { set(this.refs, action.ref, { error }); } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.onerror) { this.stepIntoOrTriggerAction(action.onerror); @@ -923,7 +954,7 @@ export class Renderer { }, 1); }); - this.asyncAction = { + this.asyncActions[actionRef] = { promise, ref: action, }; @@ -963,11 +994,11 @@ export class Renderer { onConfirmed: (txHash, isSuccess) => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.ref) { set(this.refs, action.ref, { @@ -986,7 +1017,7 @@ export class Renderer { onError: (error) => { resolve(); - if (this.asyncAction?.promise !== promise) { + if (this.asyncActions[actionRef]?.promise !== promise) { return; } @@ -994,7 +1025,7 @@ export class Renderer { set(this.refs, action.ref, { error }); } - this.asyncAction = null; + this.asyncActions[actionRef] = null; if (action.onerror) { this.stepIntoOrTriggerAction(action.onerror); @@ -1007,7 +1038,7 @@ export class Renderer { }, 1); }); - this.asyncAction = { + this.asyncActions[actionRef] = { promise, ref: action, }; @@ -1021,12 +1052,10 @@ export class Renderer { } } - if (this.asyncAction) { - if ( - "onloading" in this.asyncAction.ref && - this.asyncAction.ref.onloading - ) { - this.stepIntoOrTriggerAction(this.asyncAction.ref.onloading); + const asyncAction = this.asyncActions[actionRef]; + if (asyncAction) { + if ("onloading" in asyncAction.ref && asyncAction.ref.onloading) { + this.stepIntoOrTriggerAction(asyncAction.ref.onloading); } else { // Maybe we need to re-render this.onTreeChange(); @@ -1057,15 +1086,20 @@ export class Renderer { } if ("type" in maybeElementTreeOrAction) { - if (this.asyncAction) { + const actionRef = + "ref" in maybeElementTreeOrAction + ? maybeElementTreeOrAction.ref || DEFAULT_ASYNC_ACTION_KEY + : DEFAULT_ASYNC_ACTION_KEY; + const asyncAction = this.asyncActions[actionRef]; + if (asyncAction) { // eslint-disable-next-line no-console console.warn( - `Aborted in-flight action with type '${this.asyncAction.ref.type}' in favor of ` + + `Aborted in-flight action with type '${asyncAction.ref.type}' in favor of ` + `action with type '${maybeElementTreeOrAction.type}'` ); } - this.asyncAction = null; + this.asyncActions[actionRef] = null; return this.executeAction(maybeElementTreeOrAction); } @@ -1163,8 +1197,9 @@ export class Renderer { type: "button", loadingLabel: this.replaceInlineContext(el.loadingLabel ?? ""), label: this.replaceInlineContext(el.label), - isLoading: this.asyncAction?.ref === el.onclick, - isDisabled: Boolean(this.asyncAction), + isLoading: + this.asyncActions[DEFAULT_ASYNC_ACTION_KEY]?.ref === el.onclick, + isDisabled: Boolean(this.asyncActions[DEFAULT_ASYNC_ACTION_KEY]), variant: el.variant, events: { onClick: () => { @@ -1217,6 +1252,7 @@ export class Renderer { } }, }, + isLoading: el.loading, }, key ); @@ -1454,6 +1490,7 @@ export class Renderer { { type: "avatar", src: this.replaceInlineContext(el.src), + href: el.href ? this.replaceInlineContext(el.href) : undefined, }, key ); @@ -1493,6 +1530,18 @@ export class Renderer { key ); } + case "padding": { + return fn( + { + type: el.type, + elements: + el.elements && isArray(el.elements) + ? el.elements.map(mapper).filter(nonNullable) + : undefined, // TODO reference + }, + key + ); + } } }; return this.currentTree.map(mapper).filter(nonNullable); diff --git a/packages/mod-registry/src/index.ts b/packages/mod-registry/src/index.ts index 1b13ac01..63de2ddb 100644 --- a/packages/mod-registry/src/index.ts +++ b/packages/mod-registry/src/index.ts @@ -13,6 +13,7 @@ import ZoraNftMinter from "@mods/zora-nft-minter"; import ImgurUpload from "@mods/imgur-upload"; import DALLE from "@mods/dall-e"; import ZoraCreate from "@mods/zora-create"; +import ERC20 from "@mods/erc-20"; /** All - Stable, suitable for use */ @@ -54,6 +55,7 @@ export const allModsExperimental = [ ChatGPTShorten, ChatGPT, DALLE, + ERC20, ]; export const creationModsExperimental: ModManifest[] = diff --git a/packages/react-ui-shadcn/src/components/ui/button.tsx b/packages/react-ui-shadcn/src/components/ui/button.tsx index c979a7b4..05a6a5e2 100644 --- a/packages/react-ui-shadcn/src/components/ui/button.tsx +++ b/packages/react-ui-shadcn/src/components/ui/button.tsx @@ -20,7 +20,7 @@ const buttonVariants = cva( secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", + link: "text-primary underline-offset-4 hover:underline !px-0", }, size: { default: "h-9 px-4 py-2", diff --git a/packages/react-ui-shadcn/src/renderers/avatar.tsx b/packages/react-ui-shadcn/src/renderers/avatar.tsx index 1e6e21f4..e9b34dce 100644 --- a/packages/react-ui-shadcn/src/renderers/avatar.tsx +++ b/packages/react-ui-shadcn/src/renderers/avatar.tsx @@ -7,7 +7,14 @@ export const AvatarRenderer = ( ) => { return ( - + {props.href ? ( + + + + ) : ( + + )} + ); diff --git a/packages/react-ui-shadcn/src/renderers/horizontal-layout.tsx b/packages/react-ui-shadcn/src/renderers/horizontal-layout.tsx index 8e305fd4..e5c5d5b5 100644 --- a/packages/react-ui-shadcn/src/renderers/horizontal-layout.tsx +++ b/packages/react-ui-shadcn/src/renderers/horizontal-layout.tsx @@ -1,12 +1,17 @@ import React from "react"; import { Renderers } from "@mod-protocol/react"; +import { Skeleton } from "../components/ui/skeleton"; export const HorizontalLayoutRenderer = ( props: React.ComponentProps ) => { return ( -
- {props.children} +
+ {props.isLoading ? ( + + ) : ( + props.children + )}
); }; diff --git a/packages/react-ui-shadcn/src/renderers/index.tsx b/packages/react-ui-shadcn/src/renderers/index.tsx index 18921377..4718122f 100644 --- a/packages/react-ui-shadcn/src/renderers/index.tsx +++ b/packages/react-ui-shadcn/src/renderers/index.tsx @@ -18,6 +18,7 @@ import { ContainerRenderer } from "./container"; import { SelectRenderer } from "./select"; import { TextareaRenderer } from "./textarea"; import { ComboboxRenderer } from "./combobox"; +import { PaddingRenderer } from "./padding"; export const renderers: Renderers = { Select: SelectRenderer, @@ -25,6 +26,7 @@ export const renderers: Renderers = { Combobox: ComboboxRenderer, Textarea: TextareaRenderer, Container: ContainerRenderer, + Padding: PaddingRenderer, Text: TextRenderer, Image: ImageRenderer, Card: CardRenderer, diff --git a/packages/react-ui-shadcn/src/renderers/padding.tsx b/packages/react-ui-shadcn/src/renderers/padding.tsx new file mode 100644 index 00000000..c7872fc7 --- /dev/null +++ b/packages/react-ui-shadcn/src/renderers/padding.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import { Renderers } from "@mod-protocol/react"; + +export const PaddingRenderer = ( + props: React.ComponentProps +) => { + return
{props.children}
; +}; diff --git a/packages/react-ui-shadcn/src/renderers/text.tsx b/packages/react-ui-shadcn/src/renderers/text.tsx index 73dc7ce5..81e7e931 100644 --- a/packages/react-ui-shadcn/src/renderers/text.tsx +++ b/packages/react-ui-shadcn/src/renderers/text.tsx @@ -3,7 +3,7 @@ import { Renderers } from "@mod-protocol/react"; import { cva } from "class-variance-authority"; import { cn } from "../lib/utils"; -const textVariants = cva("my-0 flex-1 text-sm break-words", { +const textVariants = cva("my-0 text-sm break-words", { variants: { variant: { bold: "font-bold", diff --git a/packages/react-ui-shadcn/src/renderers/vertical-layout.tsx b/packages/react-ui-shadcn/src/renderers/vertical-layout.tsx index 22d7ea09..2c812f92 100644 --- a/packages/react-ui-shadcn/src/renderers/vertical-layout.tsx +++ b/packages/react-ui-shadcn/src/renderers/vertical-layout.tsx @@ -1,8 +1,17 @@ import React from "react"; import { Renderers } from "@mod-protocol/react"; +import { Skeleton } from "../components/ui/skeleton"; export const VerticalLayoutRenderer = ( props: React.ComponentProps ) => { - return
{props.children}
; + return ( +
+ {props.isLoading ? ( + + ) : ( + props.children + )} +
+ ); }; diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx index 7a24b53f..873715af 100644 --- a/packages/react/src/index.tsx +++ b/packages/react/src/index.tsx @@ -28,6 +28,7 @@ export * from "./render-embed"; export type Renderers = { Container: React.ComponentType<{ children: React.ReactNode }>; + Padding: React.ComponentType<{ children: React.ReactNode }>; Video: React.ComponentType<{ videoSrc: string; }>; @@ -65,8 +66,14 @@ export type Renderers = { onClick: () => void; }>; CircularProgress: React.ComponentType<{}>; - HorizontalLayout: React.ComponentType<{ children: React.ReactNode }>; - VerticalLayout: React.ComponentType<{ children: React.ReactNode }>; + HorizontalLayout: React.ComponentType<{ + children: React.ReactNode; + isLoading?: boolean; + }>; + VerticalLayout: React.ComponentType<{ + children: React.ReactNode; + isLoading?: boolean; + }>; Input: React.ComponentType<{ isClearable: boolean; placeholder?: string; @@ -101,6 +108,7 @@ export type Renderers = { Avatar: React.ComponentType<{ src: string; size?: "sm" | "md" | "lg"; + href?: string; }>; Card: React.ComponentType<{ imageSrc?: string; @@ -410,6 +418,16 @@ const WrappedCardRenderer = (props: { ); }; +const WrappedPaddingRenderer = (props: { + component: Renderers["Padding"]; + element: Extract, { type: "padding" }>; +}) => { + const { component: Component, element } = props; + const { type, elements, ...rest } = element; + + return {elements}; +}; + const useForceRerender = () => { const [, setValue] = React.useState(0); return React.useCallback(() => { @@ -726,6 +744,14 @@ export const Mod = (props: Props & { renderer: Renderer }) => { element={el} /> ); + case "padding": + return ( + + ); } })} diff --git a/turbo.json b/turbo.json index 4d0611b9..ccedb4b1 100644 --- a/turbo.json +++ b/turbo.json @@ -39,6 +39,9 @@ "IMGUR_CLIENT_ID", "NEXT_PUBLIC_EXPERIMENTAL_MODS", "ZORA_ADMIN_PRIVATE_KEY", - "NFT_STORAGE_API_KEY" + "NFT_STORAGE_API_KEY", + "ERC_20_AIRSTACK_API_KEY", + "ERC_20_1INCH_API_KEY", + "ERC_20_FEE_RECIPIENT" ] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8423fbd0..14adf0db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -970,7 +970,7 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.12", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -1009,7 +1009,7 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.0.0", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== @@ -1224,7 +1224,7 @@ elliptic "6.5.4" hash.js "1.1.7" -"@ethersproject/solidity@5.7.0": +"@ethersproject/solidity@5.7.0", "@ethersproject/solidity@^5.0.0", "@ethersproject/solidity@^5.0.9": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== @@ -2597,6 +2597,21 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts@3.4.1-solc-0.7-2": + version "3.4.1-solc-0.7-2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92" + integrity sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q== + +"@openzeppelin/contracts@3.4.2-solc-0.7": + version "3.4.2-solc-0.7" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635" + integrity sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA== + +"@openzeppelin/contracts@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.0.tgz#3092d70ea60e3d1835466266b1d68ad47035a2d5" + integrity sha512-52Qb+A1DdOss8QvJrijYYPSf32GUg2pGaG/yCxtaA3cu4jduouTdg4XZSMLW9op54m1jH7J8hoajhHKOPsoJFw== + "@openzeppelin/contracts@4.9.2": version "4.9.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1" @@ -4541,6 +4556,184 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@uniswap/default-token-list@^11.2.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-11.11.0.tgz#270c3cd817275b0c46258bc6a4630207fa9e75e4" + integrity sha512-UngPIUcycnKUkFUskRe2SLxAz5Gt/5i3G8XW8m9NJWTXAzfDMJD6wvOsk+gXnSfSBwpGlVPgiJy5f5FRWloTUA== + +"@uniswap/lib@^4.0.1-alpha": + version "4.0.1-alpha" + resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" + integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== + +"@uniswap/permit2-sdk@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@uniswap/permit2-sdk/-/permit2-sdk-1.2.0.tgz#ed86440a87a6c318169c8e6f161fc263ad040891" + integrity sha512-Ietv3FxN7+RCXcPSED/i/8b0a2GUZrMdyX05k3FsSztvYKyPFAMS/hBXojF0NZqYB1bHecqYc7Ej+7tV/rdYXg== + dependencies: + ethers "^5.3.1" + tiny-invariant "^1.3.1" + +"@uniswap/router-sdk@^1.6.0": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@uniswap/router-sdk/-/router-sdk-1.7.1.tgz#642d5804299cd50b1a3ba2fa0a87963320fb7f93" + integrity sha512-uBN9QX3t5lPLkxlkPoQPZpd0eN+GA0Ab9nq1pcPk/XDFuRnRxxVF629Ecz2SfTVm0gooOPO3aU3ETgyB3vuhYA== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@uniswap/sdk-core" "^4.0.7" + "@uniswap/swap-router-contracts" "^1.1.0" + "@uniswap/v2-sdk" "^3.2.0" + "@uniswap/v3-sdk" "^3.10.0" + +"@uniswap/sdk-core@^4", "@uniswap/sdk-core@^4.0.0", "@uniswap/sdk-core@^4.0.7": + version "4.0.10" + resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-4.0.10.tgz#6173cc39d4e6b5ed679775447bb96a5b3c3fb2f2" + integrity sha512-RiobXJKXvVVb+wfNM09Ik8djOMOuRQGfyRP5pHgUjojicK/7nscZILjZ87DjVCGjXEoD8yTSIps0UAQuz6pJIw== + dependencies: + "@ethersproject/address" "^5.0.2" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + jsbi "^3.1.4" + tiny-invariant "^1.1.0" + toformat "^2.0.0" + +"@uniswap/smart-order-router@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@uniswap/smart-order-router/-/smart-order-router-3.20.1.tgz#6e5343d56caac487d0eb022fd889111958463bd6" + integrity sha512-T+lKPthApVpWA2cV9svJRiIeqjtJSNq6OSA2J0fQjViQT1bYhwfBk6S+FaEHU780oRYpiRb7lBxhnRsSU6k99g== + dependencies: + "@uniswap/default-token-list" "^11.2.0" + "@uniswap/permit2-sdk" "^1.2.0" + "@uniswap/router-sdk" "^1.6.0" + "@uniswap/sdk-core" "^4.0.7" + "@uniswap/swap-router-contracts" "^1.3.0" + "@uniswap/token-lists" "^1.0.0-beta.31" + "@uniswap/universal-router" "^1.0.1" + "@uniswap/universal-router-sdk" "^1.5.8" + "@uniswap/v2-sdk" "^3.2.3" + "@uniswap/v3-sdk" "^3.10.0" + async-retry "^1.3.1" + await-timeout "^1.1.1" + axios "^0.21.1" + bunyan "^1.8.15" + bunyan-blackhole "^1.1.1" + ethers "^5.7.2" + graphql "^15.5.0" + graphql-request "^3.4.0" + lodash "^4.17.21" + mnemonist "^0.38.3" + node-cache "^5.1.2" + stats-lite "^2.2.0" + +"@uniswap/swap-router-contracts@^1.1.0", "@uniswap/swap-router-contracts@^1.2.1", "@uniswap/swap-router-contracts@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.3.1.tgz#0ebbb93eb578625618ed9489872de381f9c66fb4" + integrity sha512-mh/YNbwKb7Mut96VuEtL+Z5bRe0xVIbjjiryn+iMMrK2sFKhR4duk/86mEz0UO5gSx4pQIw9G5276P5heY/7Rg== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" + "@uniswap/v3-periphery" "^1.4.4" + dotenv "^14.2.0" + hardhat-watcher "^2.1.1" + +"@uniswap/token-lists@^1.0.0-beta.31": + version "1.0.0-beta.33" + resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.33.tgz#966ba96c9ccc8f0e9e09809890b438203f2b1911" + integrity sha512-JQkXcpRI3jFG8y3/CGC4TS8NkDgcxXaOQuYW8Qdvd6DcDiIyg2vVYCG9igFEzF0G6UvxgHkBKC7cWCgzZNYvQg== + +"@uniswap/universal-router-sdk@^1.5.8": + version "1.5.8" + resolved "https://registry.yarnpkg.com/@uniswap/universal-router-sdk/-/universal-router-sdk-1.5.8.tgz#16c62c3883e99073ba8b6e19188cf418b6551847" + integrity sha512-9tDDBTXarpdRfJStF5mDCNmsQrCfiIT6HCQN1EPq0tAm2b+JzjRkUzsLpbNpVef066FETc3YjPH6JDPB3CMyyA== + dependencies: + "@uniswap/permit2-sdk" "^1.2.0" + "@uniswap/router-sdk" "^1.6.0" + "@uniswap/sdk-core" "^4.0.0" + "@uniswap/universal-router" "1.4.3" + "@uniswap/v2-sdk" "^3.2.0" + "@uniswap/v3-sdk" "^3.10.0" + bignumber.js "^9.0.2" + ethers "^5.3.1" + +"@uniswap/universal-router@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@uniswap/universal-router/-/universal-router-1.4.3.tgz#7736cf7f8dc99435a6be87c2e80b5c5d4589d641" + integrity sha512-SZmYfhYZtsuxrTMCitcA39iJuG9sbe2nvm9iQfd70WjMpbB0+GuEs5OqSHc5tB/ujrVKzPJ1LOoNNGOs0xPEeA== + dependencies: + "@openzeppelin/contracts" "4.7.0" + "@uniswap/v2-core" "1.0.1" + "@uniswap/v3-core" "1.0.0" + +"@uniswap/universal-router@^1.0.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@uniswap/universal-router/-/universal-router-1.5.1.tgz#2ce832485eb85093b0cb94a53be20661e1aece70" + integrity sha512-+htTC/nHQXKfY/c+9C1XHMRs7Jz0bX9LQfYn9Hb7WZKZ/YHWhOsCZQylYhksieLYTRam5sQheow747hOZ+QpZQ== + dependencies: + "@openzeppelin/contracts" "4.7.0" + "@uniswap/v2-core" "1.0.1" + "@uniswap/v3-core" "1.0.0" + +"@uniswap/v2-core@1.0.1", "@uniswap/v2-core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" + integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== + +"@uniswap/v2-sdk@^3.2.0", "@uniswap/v2-sdk@^3.2.3": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@uniswap/v2-sdk/-/v2-sdk-3.3.0.tgz#76c95d234fe73ca6ad34ba9509f7451955ee0ce7" + integrity sha512-cf5PjoNQN5tNELIOVJsqV4/VeuDtxFw6Zl8oFmFJ6PNoQ8sx+XnGoO0aGKTB/o5II3oQ7820xtY3k47UsXgd6A== + dependencies: + "@ethersproject/address" "^5.0.0" + "@ethersproject/solidity" "^5.0.0" + "@uniswap/sdk-core" "^4.0.7" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + +"@uniswap/v3-core@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0.tgz#6c24adacc4c25dceee0ba3ca142b35adbd7e359d" + integrity sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA== + +"@uniswap/v3-core@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0" + integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ== + +"@uniswap/v3-periphery@^1.0.1", "@uniswap/v3-periphery@^1.1.1", "@uniswap/v3-periphery@^1.4.4": + version "1.4.4" + resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz#d2756c23b69718173c5874f37fd4ad57d2f021b7" + integrity sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@uniswap/lib" "^4.0.1-alpha" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" + base64-sol "1.0.1" + +"@uniswap/v3-sdk@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.10.0.tgz#962c9e598250ced00702d944783c2d9ee3fa12f6" + integrity sha512-sbmSA1O+Ct960r66Ie/c1rOmVadpwRu8nQ79pGICv0pZJdnFIQ/SReG3F+iC2C2UNNjNP6aC2WDUggXrjyrgnA== + dependencies: + "@ethersproject/abi" "^5.0.12" + "@ethersproject/solidity" "^5.0.9" + "@uniswap/sdk-core" "^4" + "@uniswap/swap-router-contracts" "^1.2.1" + "@uniswap/v3-periphery" "^1.1.1" + "@uniswap/v3-staker" "1.0.0" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + +"@uniswap/v3-staker@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-staker/-/v3-staker-1.0.0.tgz#9a6915ec980852479dfc903f50baf822ff8fa66e" + integrity sha512-JV0Qc46Px5alvg6YWd+UIaGH9lDuYG/Js7ngxPit1SPaIP30AlVer1UYB7BRYeUVVxE+byUyIeN5jeQ7LLDjIw== + dependencies: + "@openzeppelin/contracts" "3.4.1-solc-0.7-2" + "@uniswap/v3-core" "1.0.0" + "@uniswap/v3-periphery" "^1.0.1" + "@vanilla-extract/css@1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@vanilla-extract/css/-/css-1.9.1.tgz#337b79faa5f8f98915a90c3fe3c30b54be746c09" @@ -5310,6 +5503,11 @@ abitype@0.9.8: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== +abitype@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" + integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== + abitype@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.10.3.tgz#27ce7a7cdb9a80ccd732a3f3cf1ce6ff05266fce" @@ -5720,7 +5918,7 @@ async-mutex@^0.2.6: dependencies: tslib "^2.0.0" -async-retry@^1.3.3: +async-retry@^1.3.1, async-retry@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== @@ -5774,6 +5972,11 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +await-timeout@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/await-timeout/-/await-timeout-1.1.1.tgz#d42062ee6bc4eb271fe4d4f851eb658dae7e3906" + integrity sha512-gsDXAS6XVc4Jt+7S92MPX6Noq69bdeXUPEaXd8dk3+yVr629LTDLxNt4j1ycBbrU+AStK2PhKIyNIM+xzWMVOQ== + axe-core@=4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" @@ -5787,7 +5990,7 @@ axios@0.27.2, axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -axios@^0.21.2: +axios@^0.21.1, axios@^0.21.2: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -5897,6 +6100,11 @@ base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64-sol@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/base64-sol/-/base64-sol-1.0.1.tgz#91317aa341f0bc763811783c5729f1c2574600f6" + integrity sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg== + base64url@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" @@ -5919,6 +6127,11 @@ better-path-resolve@1.0.0: dependencies: is-windows "^1.0.0" +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + bigint-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" @@ -5926,7 +6139,7 @@ bigint-buffer@^1.1.5: dependencies: bindings "^1.3.0" -bignumber.js@*, bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.1.1: +bignumber.js@*, bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.0.2, bignumber.js@^9.1.1: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== @@ -6185,6 +6398,23 @@ bundle-require@^4.0.0: dependencies: load-tsconfig "^0.2.3" +bunyan-blackhole@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bunyan-blackhole/-/bunyan-blackhole-1.1.1.tgz#b9208586dc0b4e47f4f713215b1bddd65e4f6257" + integrity sha512-UwzNPhbbSqbzeJhCbygqjlAY7p0ZUdv1ADXPQvDh3CA7VW3C/rCc1gaQO/8j9QL4vsKQCQZQSQIEwX+lxioPAQ== + dependencies: + stream-blackhole "^1.0.3" + +bunyan@^1.8.15: + version "1.8.15" + resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.15.tgz#8ce34ca908a17d0776576ca1b2f6cbd916e93b46" + integrity sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig== + optionalDependencies: + dtrace-provider "~0.8" + moment "^2.19.3" + mv "~2" + safe-json-stringify "~1" + busboy@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -6545,6 +6775,11 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone@2.x: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -6798,7 +7033,7 @@ crelt@^1.0.0: resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== -cross-fetch@^3.1.4, cross-fetch@^3.1.5: +cross-fetch@^3.0.6, cross-fetch@^3.1.4, cross-fetch@^3.1.5: version "3.1.8" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== @@ -7252,6 +7487,11 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decimal.js-light@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decode-named-character-reference@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" @@ -7552,6 +7792,11 @@ dotenv@16.0.3: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== +dotenv@^14.2.0: + version "14.3.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.3.2.tgz#7c30b3a5f777c79a3429cb2db358eef6751e8369" + integrity sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ== + dotenv@^16.0.3, dotenv@^16.3.1: version "16.3.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" @@ -7561,6 +7806,13 @@ dotenv@^16.0.3, dotenv@^16.3.1: version "1.0.0" resolved "https://github.com/dapphub/ds-test#cd98eff28324bfac652e63a239a60632a761790b" +dtrace-provider@~0.8: + version "0.8.8" + resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" + integrity sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg== + dependencies: + nan "^2.14.0" + duplexify@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" @@ -8297,7 +8549,7 @@ eth-rpc-errors@^4.0.2: dependencies: fast-safe-stringify "^2.0.6" -ethers@^5.7.1: +ethers@^5.3.1, ethers@^5.7.1, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -8463,6 +8715,11 @@ external-editor@^3.0.3, external-editor@^3.1.0: iconv-lite "^0.4.24" tmp "^0.0.33" +extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== + eyes@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" @@ -8694,6 +8951,15 @@ form-data@4.0.0, form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fraction.js@^4.3.6: version "4.3.7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" @@ -8939,6 +9205,17 @@ glob@7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^6.0.1: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + integrity sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A== + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -9051,6 +9328,20 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +graphql-request@^3.4.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.7.0.tgz#c7406e537084f8b9788541e3e6704340ca13055b" + integrity sha512-dw5PxHCgBneN2DDNqpWu8QkbbJ07oOziy8z+bK/TAXufsOLaETuVO4GkXrbs0WjhdKhBMN3BkpN/RIvUHkmNUQ== + dependencies: + cross-fetch "^3.0.6" + extract-files "^9.0.0" + form-data "^3.0.0" + +graphql@^15.5.0: + version "15.8.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" + integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== + gray-matter@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" @@ -9108,6 +9399,13 @@ hard-rejection@^2.1.0: resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== +hardhat-watcher@^2.1.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hardhat-watcher/-/hardhat-watcher-2.5.0.tgz#3ee76c3cb5b99f2875b78d176207745aa484ed4a" + integrity sha512-Su2qcSMIo2YO2PrmJ0/tdkf+6pSt8zf9+4URR5edMVti6+ShI8T3xhPrwugdyTOFuyj8lKHrcTZNKUFYowYiyA== + dependencies: + chokidar "^3.5.3" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -10318,6 +10616,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isnumber@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isnumber/-/isnumber-1.0.0.tgz#0e3f9759b581d99dd85086f0ec2a74909cfadd01" + integrity sha512-JLiSz/zsZcGFXPrB4I/AGBvtStkt+8QmksyZBZnVXnnK9XdTEyz0tX8CRYljtwYDuIuZzih6DpHQdi+3Q6zHPw== + iso-url@^1.1.5: version "1.2.1" resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-1.2.1.tgz#db96a49d8d9a64a1c889fc07cc525d093afb1811" @@ -10944,6 +11247,11 @@ js-yaml@^4.0.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbi@^3.1.4: + version "3.2.5" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.2.5.tgz#b37bb90e0e5c2814c1c2a1bcd8c729888a2e37d6" + integrity sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -12399,7 +12707,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +"minimatch@2 || 3", minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -12432,7 +12740,7 @@ mixme@^0.5.1: resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.10.tgz#d653b2984b75d9018828f1ea333e51717ead5f51" integrity sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q== -mkdirp@^0.5.1: +mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -12449,6 +12757,18 @@ mlly@^1.2.0, mlly@^1.4.2: pkg-types "^1.0.3" ufo "^1.3.0" +mnemonist@^0.38.3: + version "0.38.5" + resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.5.tgz#4adc7f4200491237fe0fa689ac0b86539685cade" + integrity sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg== + dependencies: + obliterator "^2.0.0" + +moment@^2.19.3: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + motion@10.16.2: version "10.16.2" resolved "https://registry.yarnpkg.com/motion/-/motion-10.16.2.tgz#7dc173c6ad62210a7e9916caeeaf22c51e598d21" @@ -12571,6 +12891,15 @@ mux.js@^7.0.1: "@babel/runtime" "^7.11.2" global "^4.4.0" +mv@~2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" + integrity sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg== + dependencies: + mkdirp "~0.5.1" + ncp "~2.0.0" + rimraf "~2.4.0" + mz@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -12580,6 +12909,11 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" +nan@^2.14.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" + integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== + nanoid@^3.0.2, nanoid@^3.1.20, nanoid@^3.1.23, nanoid@^3.3.6, nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" @@ -12605,6 +12939,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +ncp@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" + integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA== + near-hd-key@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/near-hd-key/-/near-hd-key-1.2.1.tgz#f508ff15436cf8a439b543220f3cc72188a46756" @@ -12779,6 +13118,13 @@ node-addon-api@^7.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.0.0.tgz#8136add2f510997b3b94814f4af1cce0b0e3962e" integrity sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA== +node-cache@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d" + integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg== + dependencies: + clone "2.x" + node-fetch-native@^1.4.0, node-fetch-native@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.4.1.tgz#5a336e55b4e1b1e72b9927da09fecd2b374c9be5" @@ -12988,6 +13334,11 @@ object.values@^1.1.5, object.values@^1.1.6, object.values@^1.1.7: define-properties "^1.2.0" es-abstract "^1.22.1" +obliterator@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" + integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== + obuf@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -14701,6 +15052,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +reverse-mirage@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/reverse-mirage/-/reverse-mirage-1.0.3.tgz#3294f6030217b80a76c6f50d8ae10b78c413b489" + integrity sha512-mxMLfwKG18DKH2gcBBJhjRTgSQsB0yFy/k/XjiKOikDHdAZiMZ8srW7i0fcjIj5W/hlYh4m1KY3saG+q16uNew== + dependencies: + tiny-invariant "^1.3.1" + rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -14715,6 +15073,13 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@~2.4.0: + version "2.4.5" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" + integrity sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ== + dependencies: + glob "^6.0.1" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -14845,6 +15210,11 @@ safe-json-parse@4.0.0: dependencies: rust-result "^1.0.0" +safe-json-stringify@~1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" + integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== + safe-regex-test@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" @@ -15298,6 +15668,13 @@ standard-as-callback@^2.1.0: resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== +stats-lite@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/stats-lite/-/stats-lite-2.2.0.tgz#278a5571fa1d2e8b1691295dccc0235282393bbf" + integrity sha512-/Kz55rgUIv2KP2MKphwYT/NCuSfAlbbMRv2ZWw7wyXayu230zdtzhxxuXXcvsc6EmmhS8bSJl3uS1wmMHFumbA== + dependencies: + isnumber "~1.0.0" + "statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -15308,6 +15685,11 @@ std-env@^3.4.3: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.6.0.tgz#94807562bddc68fa90f2e02c5fd5b6865bb4e98e" integrity sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg== +stream-blackhole@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/stream-blackhole/-/stream-blackhole-1.0.3.tgz#6fc2e2c2e9d9fde6be8c68d3db88de09802e4d63" + integrity sha512-7NWl3dkmCd12mPkEwTbBPGxwvxj7L4O9DTjJudn02Fmk9K+RuPaDF8zeGo3kmjbsffU5E1aGpZ1dTR9AaRg6AQ== + stream-browserify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" @@ -15722,11 +16104,21 @@ timeout-abort-controller@^3.0.0: dependencies: retimer "^3.0.0" +tiny-invariant@^1.1.0, tiny-invariant@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" + integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== + tiny-typed-emitter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5" integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA== +tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tinycolor2@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" @@ -15808,6 +16200,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toformat@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/toformat/-/toformat-2.0.0.tgz#7a043fd2dfbe9021a4e36e508835ba32056739d8" + integrity sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ== + toggle-selection@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" @@ -16660,6 +17057,20 @@ videojs-vtt.js@0.15.5: dependencies: global "^4.3.1" +"viem2@npm:viem@^2.0.6": + version "2.4.0" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.4.0.tgz#977e6bf4ee67c0e616f853961fbb699cca19700d" + integrity sha512-CPP4ZBy0vKqJE1L2Dzrw/am3vD9p42H3nQwqNBk3o3R8jnM4vwncHjdu+V8tWdk3ZyM8now6Bdlqv76WPpZQhg== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "1.0.0" + isows "1.0.3" + ws "8.13.0" + viem@1.20.1, viem@^1.0.0, viem@^1.12.2, viem@^1.19.0: version "1.20.1" resolved "https://registry.yarnpkg.com/viem/-/viem-1.20.1.tgz#ea92f9bab2fded4be556be4d4be724805d11780e"