diff --git a/docs/cow-protocol/integrate/README.mdx b/docs/cow-protocol/integrate/README.mdx
index 58342a334..06a54453f 100644
--- a/docs/cow-protocol/integrate/README.mdx
+++ b/docs/cow-protocol/integrate/README.mdx
@@ -9,6 +9,7 @@ CoW Protocol offers multiple integration approaches to suit different developmen
| **Widget** | Quick integration, minimal setup | Low | Basic styling and configuration |
| **SDK** | Full control, custom UI | Medium | Complete control over UI/UX |
| **API** | Backend integration, custom logic | High | Maximum flexibility |
+| **[x402](./x402.mdx)** | Paid quote requests from scripts and agents | Low | Prepaid payments without an API key |
## Choose Your Integration Method
@@ -35,4 +36,4 @@ Best for backend services, advanced integrations, and when you need maximum cont
---
-Choose the integration method that best fits your project's requirements and development timeline. Each approach is designed to provide progressively more control and customization options.
\ No newline at end of file
+Choose the integration method that best fits your project's requirements and development timeline. Each approach is designed to provide progressively more control and customization options.
diff --git a/docs/cow-protocol/integrate/api.mdx b/docs/cow-protocol/integrate/api.mdx
index b0c5c141a..f763c7850 100644
--- a/docs/cow-protocol/integrate/api.mdx
+++ b/docs/cow-protocol/integrate/api.mdx
@@ -1,5 +1,8 @@
# API Integration
+For scripts and agents that want to pay per quote without an API key, see
+[Paid quotes with x402](./x402.mdx). The guide covers prepaid funding, quote requests and withdrawals.
+
The CoW Protocol API provides direct access to the protocol's functionality through RESTful endpoints. This approach offers maximum flexibility and control for backend integrations and custom implementations.
## Overview
@@ -217,4 +220,3 @@ For complete API documentation including all endpoints, parameters, and response
- **[API Explorer](https://api.cow.fi/docs/)** - Interactive documentation
- **[GitHub Examples](https://github.com/cowprotocol/cow-sdk/tree/main/examples)** - Code examples
- **[Order Signing Guide](../reference/core/signing_schemes.mdx)** - Cryptographic signing details
-
diff --git a/docs/cow-protocol/integrate/x402.mdx b/docs/cow-protocol/integrate/x402.mdx
new file mode 100644
index 000000000..338ed13fb
--- /dev/null
+++ b/docs/cow-protocol/integrate/x402.mdx
@@ -0,0 +1,176 @@
+---
+title: Paid quotes with x402
+description: Get CoW Protocol quotes using prepaid x402 payments and withdraw unused funds.
+sidebar_position: 4
+---
+
+import CodeBlock from '@theme/CodeBlock'
+import inspectSource from '!!raw-loader!@site/static/code-examples/x402/inspect.mjs'
+import quoteSource from '!!raw-loader!@site/static/code-examples/x402/quote.mts'
+import withdrawSource from '!!raw-loader!@site/static/code-examples/x402/withdraw.mts'
+
+# Paid quotes with x402
+
+Get CoW Protocol quotes from a script or agent without an API key. Deposit tokens into a prepaid channel, then authorise each quote with an off-chain signature. The deposit funds future requests; it is not an extra quote fee. CoW collects the charges in batches.
+
+## Get a quote
+
+This example uses **Node.js 22** and **USDC on Base** to buy an Ethereum quote.
+The payment and quote chains are independent. It returns a quote; it does not execute a swap.
+Browser paid retries are not supported.
+
+### 1. Check the terms
+
+Save inspect.mjs and run `node inspect.mjs`.
+It shows the current price, minimum deposit and withdrawal delay without a key, signature or payment.
+Fund your wallet with enough Base USDC if you accept the terms.
+
+
+Read-only terms preview
+
+
+ {inspectSource}
+
+
+
+
+For illustration, a 1 USDC deposit at 0.001 USDC per quote funds 1,000 quotes while that price is unchanged.
+Always read current terms from the service.
+
+### 2. Set up the example
+
+```bash
+mkdir x402-quickstart
+cd x402-quickstart
+npm init -y
+npm install @x402/fetch@2.26.0 @x402/evm@2.26.0 @x402/core@2.26.0 viem
+npm install --save-dev tsx
+umask 077
+touch .env
+chmod 600 .env
+```
+
+Edit `.env` locally with your payer key and a Base RPC URL:
+
+```text
+PRIVATE_KEY=YOUR_PAYER_PRIVATE_KEY
+RPC_URL=YOUR_BASE_RPC_URL
+```
+
+Keep `.env`, `x402-state/`, `x402-channels/` and `x402-attempts/` out of source control.
+Saved signed attempts are sensitive too.
+
+The example defaults to **0.001 USDC per quote**, **1 USDC per deposit or top-up**, and a **24-hour maximum accepted withdrawal delay**. These are client limits, not guaranteed server terms. If an offer exceeds them, the script stops before signing.
+
+
+Optional limits
+
+```text
+# Optional overrides; these are the example's defaults.
+MAX_QUOTE_USDC=0.001
+MAX_DEPOSIT_USDC=1
+MAX_WITHDRAW_DELAY_HOURS=24
+```
+
+`MAX_WITHDRAW_DELAY_HOURS` only limits which offers your client accepts. It does not choose the waiting period: a new channel takes the delay advertised by the server, and keeps that delay for its lifetime. You can omit this setting; the example accepts delays up to 24 hours.
+
+Enter price and deposit limits in human USDC units.
+A per-deposit limit applies to each top-up; it is not a lifetime spending cap.
+
+
+
+### 3. Request a quote
+
+Save quote.mts in the project directory.
+It checks payment terms, uses SDK receipt validation and saves channel state and recovery files.
+The example uses `@x402/*` **2.26.0**.
+
+
+Complete quote example
+
+
+ {quoteSource}
+
+
+
+
+```bash
+node --env-file=.env --import tsx quote.mts
+```
+
+A successful run prints `status: 200`, `quoteReceived: true`, the charge, a saved channel-file path and the quote JSON.
+The first request funds the channel; later requests reuse it and top up when needed, within your limits.
+CoW pays deposit and collection gas.
+
+Run one request at a time per channel and keep the saved files across restarts.
+
+:::info Uncertain payment outcomes
+
+If a request times out or returns a settlement error, keep the state, signed attempt and transaction hash. A charge or deposit may have succeeded even without a quote response. Do not repeatedly rerun it or create another deposit; check the original transaction and use the SDK’s validated recovery. Each new successful request can incur another charge.
+The [API reference](https://x402.cow.fi/docs/) describes the payment response and recovery errors.
+Never reset your charged total to on-chain claims alone: they omit charges awaiting collection.
+
+:::
+
+## Withdraw unused funds
+
+You initiate a withdrawal, wait for the saved channel's deadline, then finalize it.
+These are **two transactions**, paid by your wallet in Base ETH for this example.
+Finalization returns tokens to the payer; it is not automatic.
+
+Save withdraw.mts in the same project directory.
+In `.env`, set `CHANNEL` to the channel-file path printed by the quote example.
+Keep `RPC_URL` on that channel's payment chain.
+
+```text
+CHANNEL=./x402-channels/YOUR_CHANNEL_ID.json
+```
+
+
+Complete withdrawal example
+
+
+ {withdrawSource}
+
+
+
+
+**1. Check status** without spending gas:
+
+```bash
+node --env-file=.env --import tsx withdraw.mts status
+```
+
+**2. Stop using the channel and initiate** the withdrawal:
+
+```bash
+node --env-file=.env --import tsx withdraw.mts initiate
+node --env-file=.env --import tsx withdraw.mts status
+```
+
+**3. Wait until `finalizeAt`**, calculated from the on-chain initiation time and that channel's original delay.
+A new offer cannot change it.
+
+**4. Finalize**, then check the receipt and your token balance:
+
+```bash
+node --env-file=.env --import tsx withdraw.mts finalize
+node --env-file=.env --import tsx withdraw.mts status
+```
+
+CoW may collect outstanding charges during the wait, reducing the returned amount.
+Collection happens only when economic; uncollected fees do not extend the withdrawal deadline.
+A pending withdrawal cannot be cancelled.
+If a transaction times out, check its hash and status before sending another.
+Resolve uncertain payments before withdrawing.
+
+## Other payment options and help
+
+The current options are Base USDC/COW, Ethereum USDC and BNB USDC.
+COW and BNB USDC require an explicit SDK asset entry and Permit2 approval.
+Approval and withdrawal gas is paid in the payment chain's native token.
+COW amounts have no guaranteed dollar equivalent.
+
+Use the [live API reference](https://x402.cow.fi/docs/) for payment fields, networks and errors.
+Contact [CoW Protocol Discord](https://discord.com/invite/cowprotocol) with your network, channel ID and transaction hash;
+never share keys or signed payments.
diff --git a/package.json b/package.json
index 1ac9fb925..75da204ce 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
"docusaurus-plugin-typedoc": "^0.21.0",
"dotenv": "^16.3.1",
"prettier": "^3.0.3",
+ "raw-loader": "4.0.2",
"typedoc": "^0.25.3",
"typedoc-plugin-markdown": "^3.17.0",
"typescript": "~5.2.2"
diff --git a/static/code-examples/x402/inspect.mjs b/static/code-examples/x402/inspect.mjs
new file mode 100644
index 000000000..1cddc0d10
--- /dev/null
+++ b/static/code-examples/x402/inspect.mjs
@@ -0,0 +1,26 @@
+// inspect.mjs — read-only; no private key, SDK, signature or payment.
+const response = await fetch("https://x402.cow.fi/mainnet/api/v1/quote", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: "{}",
+});
+const header = response.headers.get("PAYMENT-REQUIRED");
+if (response.status !== 402 || !header) throw new Error("No payment challenge received");
+const challenge = JSON.parse(Buffer.from(header, "base64").toString("utf8"));
+const option = challenge.accepts.find(r =>
+ r.scheme === "batch-settlement" && r.network === "eip155:8453" &&
+ r.asset.toLowerCase() === "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
+);
+if (!option) throw new Error("Base USDC is not currently offered");
+function usdc(atomic) {
+ if (typeof atomic !== "string" || !/^\d+$/.test(atomic)) throw new Error("Invalid amount");
+ const digits = BigInt(atomic).toString().padStart(7, "0");
+ return (digits.slice(0, -6) + "." + digits.slice(-6)).replace(/\.?0+$/, "") + " USDC";
+}
+console.table({
+ payment: "USDC on Base",
+ quotePrice: usdc(option.amount),
+ minimumDepositOrTopUp: usdc(option.extra.minDeposit),
+ newChannelWithdrawalDelay: option.extra.withdrawDelay / 3600 + " hours",
+ depositAuthorizationValidity: option.maxTimeoutSeconds + " seconds",
+});
diff --git a/static/code-examples/x402/quote.mts b/static/code-examples/x402/quote.mts
new file mode 100644
index 000000000..9c77fe13c
--- /dev/null
+++ b/static/code-examples/x402/quote.mts
@@ -0,0 +1,127 @@
+import { randomUUID } from "node:crypto";
+import { decodePaymentRequiredHeader, decodePaymentResponseHeader } from "@x402/core/http";
+import { mkdirSync, writeFileSync } from "node:fs";
+import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
+import { BatchSettlementEvmScheme, computeChannelId } from "@x402/evm/batch-settlement/client";
+import { FileClientChannelStorage } from "@x402/evm/batch-settlement/client/file-storage";
+import { privateKeyToAccount } from "viem/accounts";
+import { createPublicClient, formatUnits, http, parseUnits } from "viem";
+import { base } from "viem/chains";
+import { toClientEvmSigner } from "@x402/evm";
+
+const NETWORK = "eip155:8453";
+const ASSET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
+const PAY_TO = "0xaa0a2b5E68351C46474f4CE142A1357e25EedBA9";
+const RECEIVER_AUTHORIZER = "0x0DA62b1f9904AB97be179579A32533210d7ebBA5";
+function usdcLimit(name: string, fallback: string) {
+ const value = process.env[name] ?? fallback;
+ if (!/^\d+(\.\d{1,6})?$/.test(value)) throw new Error(`Set ${name} in USDC (up to 6 decimals)`);
+ return parseUnits(value, 6);
+}
+const maxQuote = usdcLimit("MAX_QUOTE_USDC", "0.001");
+const maxDeposit = usdcLimit("MAX_DEPOSIT_USDC", "1");
+const maxDelay = Number(process.env.MAX_WITHDRAW_DELAY_HOURS ?? "24") * 3600;
+if (maxQuote <= 0n || maxDeposit < maxQuote || !Number.isSafeInteger(maxDelay) || maxDelay <= 0) {
+ throw new Error("Set your quote, deposit and withdrawal-delay limits");
+}
+
+if (!process.env.RPC_URL) throw new Error("Set RPC_URL for Base");
+if (!/^0x[0-9a-fA-F]{64}$/.test(process.env.PRIVATE_KEY ?? "")) throw new Error("Set PRIVATE_KEY securely in .env");
+const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
+const publicClient = createPublicClient({ chain: base, transport: http(process.env.RPC_URL) });
+if (await publicClient.getChainId() !== base.id) throw new Error("RPC_URL points to the wrong chain");
+const signer = toClientEvmSigner(account, publicClient);
+const client = new x402Client().register(
+ NETWORK,
+ new BatchSettlementEvmScheme(signer, {
+ storage: new FileClientChannelStorage({ directory: "./x402-state" }),
+ depositStrategy: ({ depositAmount }) => {
+ if (BigInt(depositAmount) > maxDeposit) throw new Error("Deposit exceeds your limit");
+ return depositAmount;
+ },
+ }),
+);
+// Sign only the terms you expect.
+client.registerPolicy((_version, requirements) =>
+ requirements.filter(
+ (r) =>
+ r.scheme === "batch-settlement" &&
+ r.network === NETWORK &&
+ r.asset.toLowerCase() === ASSET.toLowerCase() &&
+ r.payTo.toLowerCase() === PAY_TO.toLowerCase() &&
+ typeof r.extra?.receiverAuthorizer === "string" &&
+ r.extra.receiverAuthorizer.toLowerCase() === RECEIVER_AUTHORIZER.toLowerCase() &&
+ BigInt(r.amount) > 0n && BigInt(r.amount) <= maxQuote &&
+ typeof r.extra?.minDeposit === "string" &&
+ /^\d+$/.test(r.extra.minDeposit) &&
+ BigInt(r.extra.minDeposit) > 0n && BigInt(r.extra.minDeposit) <= maxDeposit &&
+ typeof r.extra?.withdrawDelay === "number" &&
+ Number.isSafeInteger(r.extra.withdrawDelay) &&
+ r.extra.withdrawDelay > 0 && r.extra.withdrawDelay <= maxDelay,
+ ),
+);
+// Allow the selected token; the policy caps quote prices and withdrawal delay.
+// depositStrategy checks the actual deposit amount before signing it.
+client.setSpendControls({
+ allowedAssets: [{ network: NETWORK, asset: ASSET, maxAmountPerPayment: maxDeposit.toString() }],
+});
+// Save the original configuration and signed attempt before sending.
+let latestAttemptFile: string | undefined;
+let latestChannelFile: string | undefined;
+client.onAfterPaymentCreation(async ({ paymentPayload }) => {
+ const config = paymentPayload.payload.channelConfig as Parameters[0];
+ const network = paymentPayload.accepted.network;
+ const channelId = computeChannelId(config, network);
+ mkdirSync("./x402-channels", { recursive: true, mode: 0o700 });
+ mkdirSync("./x402-attempts", { recursive: true, mode: 0o700 });
+ latestAttemptFile = `./x402-attempts/${randomUUID()}.json`;
+ writeFileSync(latestAttemptFile, JSON.stringify(paymentPayload), { mode: 0o600, flag: "wx" });
+ latestChannelFile = `./x402-channels/${channelId}.json`;
+ writeFileSync(latestChannelFile, JSON.stringify({ network, channelId, channelConfig: config }, null, 2), { mode: 0o600 });
+});
+const paidFetch = wrapFetchWithPayment(fetch, client);
+
+try {
+ const res = await paidFetch("https://x402.cow.fi/mainnet/api/v1/quote", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ sellToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
+ buyToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
+ from: "0x0000000000000000000000000000000000000000",
+ receiver: "0x0000000000000000000000000000000000000000",
+ kind: "sell",
+ sellAmountBeforeFee: "1000000000000000000",
+ }),
+ });
+ const requiredHeader = res.headers.get("PAYMENT-REQUIRED");
+ const responseHeader = res.headers.get("PAYMENT-RESPONSE");
+ const challenge = requiredHeader ? decodePaymentRequiredHeader(requiredHeader) : undefined;
+ const receipt = responseHeader ? decodePaymentResponseHeader(responseHeader) : undefined;
+ const body = await res.text();
+ if (res.status !== 200 || receipt?.success !== true) {
+ console.error({
+ status: res.status,
+ error: receipt?.errorReason ?? challenge?.error,
+ transaction: receipt?.transaction,
+ savedAttempt: latestAttemptFile,
+ body,
+ });
+ throw new Error("Payment outcome needs checking; follow Troubleshooting before retrying");
+ }
+ const result = JSON.parse(body);
+ if (!result.quote) throw new Error("Expected a quote in the successful response");
+ const charged = receipt.extra?.chargedAmount ?? "0";
+ if (typeof charged !== "string" || !/^\d+$/.test(charged)) throw new Error("Invalid receipt charge");
+ console.log({
+ status: res.status,
+ quoteReceived: true,
+ paymentNetwork: receipt.network,
+ chargedUSDC: formatUnits(BigInt(charged), 6),
+ channelFile: latestChannelFile,
+ });
+ console.log(result);
+} catch (error) {
+ console.error({ savedAttempt: latestAttemptFile, message: "Keep state and check Troubleshooting before rerunning an unresolved attempt." });
+ throw error;
+}
diff --git a/static/code-examples/x402/withdraw.mts b/static/code-examples/x402/withdraw.mts
new file mode 100644
index 000000000..8312e7a23
--- /dev/null
+++ b/static/code-examples/x402/withdraw.mts
@@ -0,0 +1,61 @@
+// withdraw.mts — status is read-only; initiate/finalize each send one transaction.
+import { readFileSync } from "node:fs";
+import { createPublicClient, createWalletClient, http, parseAbi } from "viem";
+import { base, mainnet, bsc } from "viem/chains";
+import { privateKeyToAccount } from "viem/accounts";
+import { computeChannelId } from "@x402/evm/batch-settlement/client";
+
+const action = process.argv[2] ?? "status";
+if (!["status", "initiate", "finalize"].includes(action)) throw new Error("Use status, initiate or finalize");
+if (!process.env.CHANNEL || !process.env.RPC_URL) throw new Error("Set CHANNEL and RPC_URL");
+const saved = JSON.parse(readFileSync(process.env.CHANNEL, "utf8"));
+const { network, channelConfig: config } = saved;
+const chain = [base, mainnet, bsc].find(c => network === `eip155:${c.id}`);
+if (!chain) throw new Error("Unsupported payment chain");
+const channelId = computeChannelId(config, network);
+if (saved.channelId && saved.channelId.toLowerCase() !== channelId.toLowerCase()) throw new Error("Channel configuration mismatch");
+const ESCROW: `0x${string}` = "0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003";
+const abi = parseAbi([
+ "struct ChannelConfig { address payer; address payerAuthorizer; address receiver; address receiverAuthorizer; address token; uint40 withdrawDelay; bytes32 salt; }",
+ "function channels(bytes32 channelId) view returns (uint128 balance, uint128 totalClaimed)",
+ "function pendingWithdrawals(bytes32 channelId) view returns (uint128 amount, uint40 initiatedAt)",
+ "function initiateWithdraw(ChannelConfig config, uint128 amount)",
+ "function finalizeWithdraw(ChannelConfig config)",
+]);
+const publicClient = createPublicClient({ chain, transport: http(process.env.RPC_URL) });
+if (await publicClient.getChainId() !== chain.id) throw new Error("RPC_URL points to the wrong chain");
+const block = await publicClient.getBlock();
+const [balance, totalClaimed] = await publicClient.readContract({ address: ESCROW, abi, functionName: "channels", args: [channelId], blockNumber: block.number });
+const [requested, initiatedAt] = await publicClient.readContract({ address: ESCROW, abi, functionName: "pendingWithdrawals", args: [channelId], blockNumber: block.number });
+const finalizeAt = initiatedAt ? BigInt(initiatedAt) + BigInt(config.withdrawDelay) : null;
+console.log({ channelId, balanceAtomic: balance.toString(), claimedAtomic: totalClaimed.toString(), requestedAtomic: requested.toString(), finalizeAt: finalizeAt ? new Date(Number(finalizeAt) * 1000).toISOString() : null });
+
+if (action !== "status") {
+ const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
+ if (![config.payer, config.payerAuthorizer].some((a: string) => a.toLowerCase() === account.address.toLowerCase())) throw new Error("Wrong payer key");
+ const wallet = createWalletClient({ account, chain, transport: http(process.env.RPC_URL) });
+ if (action === "initiate") {
+ if (initiatedAt) throw new Error("Withdrawal already pending");
+ if (balance <= totalClaimed) throw new Error("Nothing remains to withdraw");
+ } else {
+ if (!finalizeAt || block.timestamp < finalizeAt) throw new Error("No pending withdrawal, or its on-chain delay has not elapsed");
+ }
+ const fees = await publicClient.estimateFeesPerGas({ type: "eip1559" });
+ const floor = chain.id === 8453 ? 1_000_000n : chain.id === 56 ? 50_000_000n : 100_000_000n;
+ const extraTip = fees.maxPriorityFeePerGas < floor ? floor - fees.maxPriorityFeePerGas : 0n;
+ const pricing = { maxPriorityFeePerGas: fees.maxPriorityFeePerGas + extraTip, maxFeePerGas: fees.maxFeePerGas + extraTip };
+ let hash: `0x${string}`;
+ if (action === "initiate") {
+ const call = { address: ESCROW, abi, functionName: "initiateWithdraw", args: [config, balance - totalClaimed], account } as const;
+ await publicClient.simulateContract(call);
+ hash = await wallet.writeContract({ ...call, ...pricing });
+ } else {
+ const call = { address: ESCROW, abi, functionName: "finalizeWithdraw", args: [config], account } as const;
+ await publicClient.simulateContract(call);
+ hash = await wallet.writeContract({ ...call, ...pricing });
+ }
+ console.log({ action, transaction: hash }); // Keep this hash if receipt waiting times out.
+ const receipt = await publicClient.waitForTransactionReceipt({ hash, timeout: 60_000 });
+ if (receipt.status !== "success") throw new Error(`Transaction reverted: ${hash}`);
+ console.log({ status: receipt.status, transaction: hash });
+}
diff --git a/tsconfig.json b/tsconfig.json
index 24f5b963d..173d88cf8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,7 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig",
+ "exclude": ["static/code-examples/x402"],
"compilerOptions": {
"baseUrl": ".",
"resolveJsonModule": true,
diff --git a/yarn.lock b/yarn.lock
index e072a3928..e83db01e3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9656,6 +9656,14 @@ raw-body@~2.5.3:
iconv-lite "~0.4.24"
unpipe "~1.0.0"
+raw-loader@4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6"
+ integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==
+ dependencies:
+ loader-utils "^2.0.0"
+ schema-utils "^3.0.0"
+
rc@1.2.8, rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"