@zeroxyz/cli 0.0.40 → 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +339 -91
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { homedir as homedir8 } from "os";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as join10 } from "path";
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@zeroxyz/cli",
|
|
10
|
-
version: "0.0.
|
|
10
|
+
version: "0.0.41",
|
|
11
11
|
type: "module",
|
|
12
12
|
bin: {
|
|
13
13
|
zero: "dist/index.js",
|
|
@@ -360,6 +360,9 @@ var signResultSchema = z.object({
|
|
|
360
360
|
signature: z.string(),
|
|
361
361
|
walletAddress: z.string()
|
|
362
362
|
});
|
|
363
|
+
var migrateResultSchema = z.object({
|
|
364
|
+
transactionHash: z.string()
|
|
365
|
+
});
|
|
363
366
|
var deviceStartResultSchema = z.object({
|
|
364
367
|
deviceCode: z.string(),
|
|
365
368
|
userCode: z.string(),
|
|
@@ -605,6 +608,15 @@ var ApiService = class _ApiService {
|
|
|
605
608
|
const json = await this.request("POST", "/v1/users/me/wallets/provision");
|
|
606
609
|
return userWalletDtoSchema.parse(json);
|
|
607
610
|
};
|
|
611
|
+
// Hands a BYO-wallet-signed EIP-3009 authorization to the server, which
|
|
612
|
+
// broadcasts it on Base via the gas-paying relayer (sweeping USDC into the
|
|
613
|
+
// caller's provisioned wallet). Session-authed.
|
|
614
|
+
migrateWallet = async (authorization) => {
|
|
615
|
+
const json = await this.request("POST", "/v1/users/me/wallets/migrate", {
|
|
616
|
+
authorization
|
|
617
|
+
});
|
|
618
|
+
return migrateResultSchema.parse(json);
|
|
619
|
+
};
|
|
608
620
|
signTypedDataRemote = async (typedData) => {
|
|
609
621
|
const json = await this.request("POST", "/v1/users/me/sign-typed-data", {
|
|
610
622
|
typedData
|
|
@@ -931,6 +943,7 @@ import { Command as Command4 } from "commander";
|
|
|
931
943
|
import { formatUnits as formatUnits2 } from "viem";
|
|
932
944
|
|
|
933
945
|
// src/services/payment-service.ts
|
|
946
|
+
import { randomBytes } from "crypto";
|
|
934
947
|
import {
|
|
935
948
|
adaptViemWallet,
|
|
936
949
|
convertViemChainToRelayChain,
|
|
@@ -951,7 +964,7 @@ import {
|
|
|
951
964
|
formatUnits,
|
|
952
965
|
http
|
|
953
966
|
} from "viem";
|
|
954
|
-
import { base, baseSepolia } from "viem/chains";
|
|
967
|
+
import { base, baseSepolia, tempo as viemTempoChain } from "viem/chains";
|
|
955
968
|
var SessionCloseFailedError = class extends Error {
|
|
956
969
|
session;
|
|
957
970
|
response;
|
|
@@ -1025,6 +1038,32 @@ var ERC20_BALANCE_ABI = [
|
|
|
1025
1038
|
type: "function"
|
|
1026
1039
|
}
|
|
1027
1040
|
];
|
|
1041
|
+
var ERC20_TRANSFER_ABI = [
|
|
1042
|
+
{
|
|
1043
|
+
inputs: [
|
|
1044
|
+
{ name: "to", type: "address" },
|
|
1045
|
+
{ name: "amount", type: "uint256" }
|
|
1046
|
+
],
|
|
1047
|
+
name: "transfer",
|
|
1048
|
+
outputs: [{ name: "", type: "bool" }],
|
|
1049
|
+
stateMutability: "nonpayable",
|
|
1050
|
+
type: "function"
|
|
1051
|
+
}
|
|
1052
|
+
];
|
|
1053
|
+
var USDC_BASE_SWEEP_DOMAIN = { name: "USD Coin", version: "2" };
|
|
1054
|
+
var SWEEP_AUTHORIZATION_TTL_S = 900;
|
|
1055
|
+
var TEMPO_FEE_RESERVE_RAW = 10000n;
|
|
1056
|
+
var EIP3009_TRANSFER_TYPES = {
|
|
1057
|
+
// biome-ignore lint/style/useNamingConvention: EIP-712 primaryType must match the on-chain type name
|
|
1058
|
+
TransferWithAuthorization: [
|
|
1059
|
+
{ name: "from", type: "address" },
|
|
1060
|
+
{ name: "to", type: "address" },
|
|
1061
|
+
{ name: "value", type: "uint256" },
|
|
1062
|
+
{ name: "validAfter", type: "uint256" },
|
|
1063
|
+
{ name: "validBefore", type: "uint256" },
|
|
1064
|
+
{ name: "nonce", type: "bytes32" }
|
|
1065
|
+
]
|
|
1066
|
+
};
|
|
1028
1067
|
var decodeSessionReceiptHeader = (header) => {
|
|
1029
1068
|
if (!header) return null;
|
|
1030
1069
|
try {
|
|
@@ -1231,11 +1270,6 @@ var PaymentService = class {
|
|
|
1231
1270
|
`Insufficient pathUSD on Tempo testnet: have ${formatUnits(tempoBalance, 6)}, need ${capturedAmount}. Fund your wallet with the Tempo testnet faucet: https://docs.tempo.xyz/quickstart/faucet`
|
|
1232
1271
|
);
|
|
1233
1272
|
}
|
|
1234
|
-
if (this.config.managed) {
|
|
1235
|
-
throw new Error(
|
|
1236
|
-
`Insufficient USDC on Tempo: have ${formatUnits(tempoBalance, 6)}, need ${capturedAmount}. Managed wallets can't auto-bridge from Base yet \u2014 fund your Tempo wallet${this.account ? ` (${this.account.address})` : ""} directly, or set a local ZERO_PRIVATE_KEY to bridge.`
|
|
1237
|
-
);
|
|
1238
|
-
}
|
|
1239
1273
|
await this.bridgeToTempo(requiredRaw, onProgress);
|
|
1240
1274
|
}
|
|
1241
1275
|
return capturedAmount;
|
|
@@ -1475,6 +1509,115 @@ var PaymentService = class {
|
|
|
1475
1509
|
]);
|
|
1476
1510
|
return { amount: formatUnits(baseRaw + tempoRaw, 6), asset: "USDC" };
|
|
1477
1511
|
};
|
|
1512
|
+
// Total USDC across both legs (Base + Tempo) for the configured wallet — what
|
|
1513
|
+
// the migrate confirmation prompt quotes. Best-effort: a chain whose RPC read
|
|
1514
|
+
// fails counts as 0 rather than blocking the other leg.
|
|
1515
|
+
getSweepableBalance = async () => {
|
|
1516
|
+
const [baseRaw, tempoRaw] = await Promise.all([
|
|
1517
|
+
this.getBalanceRaw("base").catch(() => 0n),
|
|
1518
|
+
this.getBalanceRaw("tempo").catch(() => 0n)
|
|
1519
|
+
]);
|
|
1520
|
+
const raw = baseRaw + tempoRaw;
|
|
1521
|
+
return { raw, usdc: formatUnits(raw, 6) };
|
|
1522
|
+
};
|
|
1523
|
+
// Sweeps all USDC from the configured wallet into `to` across Base and Tempo.
|
|
1524
|
+
// Each leg is independent and never throws — failures are captured in the
|
|
1525
|
+
// returned per-chain result so the caller can decide whether to retire the
|
|
1526
|
+
// source key.
|
|
1527
|
+
migrateAllUsdc = async (to, relayer) => {
|
|
1528
|
+
const baseResult = await this.sweepBaseUsdc(to, relayer);
|
|
1529
|
+
const tempoResult = await this.sweepTempoUsdc(to);
|
|
1530
|
+
return [baseResult, tempoResult];
|
|
1531
|
+
};
|
|
1532
|
+
// Base leg: sign an EIP-3009 transferWithAuthorization for the full balance
|
|
1533
|
+
// and hand it to the relayer, which broadcasts it gaslessly.
|
|
1534
|
+
sweepBaseUsdc = async (to, relayer) => {
|
|
1535
|
+
try {
|
|
1536
|
+
if (!this.account) throw new Error("No wallet configured");
|
|
1537
|
+
const balance = await this.getBalanceRaw("base");
|
|
1538
|
+
if (balance <= 0n) return { chain: "base", status: "skipped" };
|
|
1539
|
+
const nowS = Math.floor(Date.now() / 1e3);
|
|
1540
|
+
const message = {
|
|
1541
|
+
from: this.account.address,
|
|
1542
|
+
to,
|
|
1543
|
+
value: balance,
|
|
1544
|
+
validAfter: 0n,
|
|
1545
|
+
validBefore: BigInt(nowS + SWEEP_AUTHORIZATION_TTL_S),
|
|
1546
|
+
nonce: `0x${randomBytes(32).toString("hex")}`
|
|
1547
|
+
};
|
|
1548
|
+
const signature = await this.account.signTypedData({
|
|
1549
|
+
domain: {
|
|
1550
|
+
name: USDC_BASE_SWEEP_DOMAIN.name,
|
|
1551
|
+
version: USDC_BASE_SWEEP_DOMAIN.version,
|
|
1552
|
+
chainId: BASE_CHAIN_ID,
|
|
1553
|
+
verifyingContract: USDC_BASE
|
|
1554
|
+
},
|
|
1555
|
+
types: EIP3009_TRANSFER_TYPES,
|
|
1556
|
+
primaryType: "TransferWithAuthorization",
|
|
1557
|
+
message
|
|
1558
|
+
});
|
|
1559
|
+
const { transactionHash } = await relayer.migrateWallet({
|
|
1560
|
+
from: message.from,
|
|
1561
|
+
to: message.to,
|
|
1562
|
+
value: message.value.toString(),
|
|
1563
|
+
validAfter: message.validAfter.toString(),
|
|
1564
|
+
validBefore: message.validBefore.toString(),
|
|
1565
|
+
nonce: message.nonce,
|
|
1566
|
+
signature
|
|
1567
|
+
});
|
|
1568
|
+
return {
|
|
1569
|
+
chain: "base",
|
|
1570
|
+
status: "swept",
|
|
1571
|
+
amount: formatUnits(balance, 6),
|
|
1572
|
+
txHash: transactionHash
|
|
1573
|
+
};
|
|
1574
|
+
} catch (err) {
|
|
1575
|
+
return { chain: "base", status: "failed", error: err.message };
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
// Tempo leg: self-broadcast an ERC-20 transfer paying the fee in USDC via
|
|
1579
|
+
// `feeToken` (no native gas token needed). Uses viem's tempo chain for its
|
|
1580
|
+
// type-0x76 serializer; reserves a tiny amount to cover the fee.
|
|
1581
|
+
sweepTempoUsdc = async (to) => {
|
|
1582
|
+
try {
|
|
1583
|
+
if (!this.account) throw new Error("No wallet configured");
|
|
1584
|
+
const balance = await this.getBalanceRaw("tempo");
|
|
1585
|
+
if (balance <= TEMPO_FEE_RESERVE_RAW) {
|
|
1586
|
+
return { chain: "tempo", status: "skipped" };
|
|
1587
|
+
}
|
|
1588
|
+
const amount = balance - TEMPO_FEE_RESERVE_RAW;
|
|
1589
|
+
const wallet = createWalletClient({
|
|
1590
|
+
account: this.account,
|
|
1591
|
+
chain: viemTempoChain,
|
|
1592
|
+
transport: http()
|
|
1593
|
+
});
|
|
1594
|
+
const txHash = await wallet.writeContract({
|
|
1595
|
+
address: USDC_TEMPO,
|
|
1596
|
+
abi: ERC20_TRANSFER_ABI,
|
|
1597
|
+
functionName: "transfer",
|
|
1598
|
+
args: [to, amount],
|
|
1599
|
+
feeToken: USDC_TEMPO
|
|
1600
|
+
// biome-ignore lint/suspicious/noExplicitAny: tempo feeToken param
|
|
1601
|
+
});
|
|
1602
|
+
const publicClient = createPublicClient({
|
|
1603
|
+
chain: viemTempoChain,
|
|
1604
|
+
transport: http()
|
|
1605
|
+
});
|
|
1606
|
+
await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
1607
|
+
return {
|
|
1608
|
+
chain: "tempo",
|
|
1609
|
+
status: "swept",
|
|
1610
|
+
amount: formatUnits(amount, 6),
|
|
1611
|
+
txHash
|
|
1612
|
+
};
|
|
1613
|
+
} catch (err) {
|
|
1614
|
+
return {
|
|
1615
|
+
chain: "tempo",
|
|
1616
|
+
status: "failed",
|
|
1617
|
+
error: err.message
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1478
1621
|
};
|
|
1479
1622
|
|
|
1480
1623
|
// src/util/infer-schema.ts
|
|
@@ -1726,55 +1869,6 @@ var fetchCommand = (appContext) => new Command4("fetch").description(
|
|
|
1726
1869
|
} else {
|
|
1727
1870
|
resolvedUrl = url;
|
|
1728
1871
|
}
|
|
1729
|
-
if (!url && options.capability && !stateService.findSearchContextByCapability(options.capability)) {
|
|
1730
|
-
try {
|
|
1731
|
-
let capName = resolvedCapabilityName;
|
|
1732
|
-
if (capName === void 0) {
|
|
1733
|
-
const cap = await apiService.getCapability(options.capability);
|
|
1734
|
-
capName = cap.name;
|
|
1735
|
-
}
|
|
1736
|
-
const searchResult = await apiService.search({ query: capName });
|
|
1737
|
-
const matchedEntry = searchResult.capabilities.find(
|
|
1738
|
-
(c) => c.slug === options.capability || c.id === options.capability
|
|
1739
|
-
);
|
|
1740
|
-
const slugFoundInResults = Boolean(matchedEntry);
|
|
1741
|
-
stateService.saveLastSearch({
|
|
1742
|
-
searchId: searchResult.searchId,
|
|
1743
|
-
capabilities: searchResult.capabilities.map((c) => ({
|
|
1744
|
-
position: c.position,
|
|
1745
|
-
id: c.id,
|
|
1746
|
-
slug: c.slug,
|
|
1747
|
-
url: c.url,
|
|
1748
|
-
urlTemplate: c.urlTemplate ?? null,
|
|
1749
|
-
displayCostAmount: c.cost.amount
|
|
1750
|
-
}))
|
|
1751
|
-
});
|
|
1752
|
-
analyticsService.capture("search_executed", {
|
|
1753
|
-
query: truncateQuery(capName),
|
|
1754
|
-
queryLength: capName.length,
|
|
1755
|
-
resultCount: searchResult.capabilities.length,
|
|
1756
|
-
searchId: searchResult.searchId,
|
|
1757
|
-
total: searchResult.total,
|
|
1758
|
-
hasMore: searchResult.hasMore,
|
|
1759
|
-
json: false,
|
|
1760
|
-
triggeredBy: "slug_handoff",
|
|
1761
|
-
slugFoundInResults
|
|
1762
|
-
});
|
|
1763
|
-
analyticsService.capture("capability_viewed", {
|
|
1764
|
-
// Existing field preserved as the raw --capability
|
|
1765
|
-
// input for back-compat.
|
|
1766
|
-
capabilityId: options.capability,
|
|
1767
|
-
// New canonical identifier fields hydrated from the
|
|
1768
|
-
// resolved search result (when the slug was found).
|
|
1769
|
-
capabilityUid: matchedEntry?.id,
|
|
1770
|
-
capabilitySlug: matchedEntry?.slug,
|
|
1771
|
-
fromLastSearch: false,
|
|
1772
|
-
searchId: searchResult.searchId,
|
|
1773
|
-
triggeredBy: "slug_handoff"
|
|
1774
|
-
});
|
|
1775
|
-
} catch {
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
1872
|
let resolvedBody;
|
|
1779
1873
|
try {
|
|
1780
1874
|
resolvedBody = resolveRequestBody(
|
|
@@ -1830,6 +1924,7 @@ var fetchCommand = (appContext) => new Command4("fetch").description(
|
|
|
1830
1924
|
const capabilitySlug = matchCtx?.capabilitySlug ?? null;
|
|
1831
1925
|
const searchId = matchCtx?.searchId;
|
|
1832
1926
|
const resultRank = matchCtx?.resultRank;
|
|
1927
|
+
const fetchOrigin = matchCtx ? "from_search" : options.capability ? "direct_slug" : "direct_url";
|
|
1833
1928
|
const matchedDisplayCostAmount = matchCtx?.displayCostAmount;
|
|
1834
1929
|
const skipReasons = [];
|
|
1835
1930
|
if (!apiService.walletAddress) {
|
|
@@ -2047,6 +2142,9 @@ var fetchCommand = (appContext) => new Command4("fetch").description(
|
|
|
2047
2142
|
resultRank: resultRank ?? void 0,
|
|
2048
2143
|
runId: runId ?? void 0,
|
|
2049
2144
|
runTracked: !!runId,
|
|
2145
|
+
// A NULL searchId on a direct_slug fetch is correct here,
|
|
2146
|
+
// not a funnel gap to paper over.
|
|
2147
|
+
fetchOrigin,
|
|
2050
2148
|
...fetchError && { error: truncateError(fetchError.message) }
|
|
2051
2149
|
});
|
|
2052
2150
|
const isFetchFailure = Boolean(fetchError) || !finalResponse || typeof status === "number" && (status < 200 || status >= 300);
|
|
@@ -3465,14 +3563,30 @@ Read the full terms at: ${TERMS_URL}
|
|
|
3465
3563
|
});
|
|
3466
3564
|
|
|
3467
3565
|
// src/commands/wallet-command.ts
|
|
3468
|
-
import { existsSync as existsSync3, readFileSync as
|
|
3566
|
+
import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
|
|
3469
3567
|
import { homedir as homedir4 } from "os";
|
|
3470
|
-
import { join as
|
|
3568
|
+
import { join as join5 } from "path";
|
|
3569
|
+
import { confirm, isCancel } from "@clack/prompts";
|
|
3471
3570
|
import { Command as Command11 } from "commander";
|
|
3472
3571
|
import open2 from "open";
|
|
3473
3572
|
import { isAddress } from "viem";
|
|
3474
3573
|
import { generatePrivateKey as generatePrivateKey2, privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
3475
3574
|
|
|
3575
|
+
// src/util/migrate-config.ts
|
|
3576
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
3577
|
+
import { join as join4 } from "path";
|
|
3578
|
+
var backupAndStripPrivateKey = (zeroDir) => {
|
|
3579
|
+
const configPath = join4(zeroDir, "config.json");
|
|
3580
|
+
const backupPath = join4(zeroDir, "config.backup.json");
|
|
3581
|
+
const raw = readFileSync7(configPath, "utf8");
|
|
3582
|
+
ensureSecureDir(zeroDir);
|
|
3583
|
+
writeSecureFile(backupPath, raw);
|
|
3584
|
+
const parsed = JSON.parse(raw);
|
|
3585
|
+
delete parsed.privateKey;
|
|
3586
|
+
writeSecureFile(configPath, JSON.stringify(parsed, null, 2));
|
|
3587
|
+
return { backupPath, configPath };
|
|
3588
|
+
};
|
|
3589
|
+
|
|
3476
3590
|
// src/util/stdin.ts
|
|
3477
3591
|
var readStdin = async () => {
|
|
3478
3592
|
const chunks = [];
|
|
@@ -3656,11 +3770,11 @@ var walletSetCommand = (appContext) => new Command11("set").description("Set wal
|
|
|
3656
3770
|
process.exitCode = 1;
|
|
3657
3771
|
return;
|
|
3658
3772
|
}
|
|
3659
|
-
const zeroDir =
|
|
3660
|
-
const configPath =
|
|
3773
|
+
const zeroDir = join5(homedir4(), ".zero");
|
|
3774
|
+
const configPath = join5(zeroDir, "config.json");
|
|
3661
3775
|
if (!options.force && existsSync3(configPath)) {
|
|
3662
3776
|
try {
|
|
3663
|
-
const existing2 = JSON.parse(
|
|
3777
|
+
const existing2 = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
3664
3778
|
if (existing2.privateKey) {
|
|
3665
3779
|
console.error(
|
|
3666
3780
|
"Wallet already configured. Use --force to overwrite."
|
|
@@ -3672,7 +3786,7 @@ var walletSetCommand = (appContext) => new Command11("set").description("Set wal
|
|
|
3672
3786
|
}
|
|
3673
3787
|
}
|
|
3674
3788
|
ensureSecureDir(zeroDir);
|
|
3675
|
-
const existing = existsSync3(configPath) ? JSON.parse(
|
|
3789
|
+
const existing = existsSync3(configPath) ? JSON.parse(readFileSync8(configPath, "utf8")) : {};
|
|
3676
3790
|
writeSecureFile(
|
|
3677
3791
|
configPath,
|
|
3678
3792
|
JSON.stringify(
|
|
@@ -3768,6 +3882,142 @@ var walletGenerateCommand = (appContext) => new Command11("generate").descriptio
|
|
|
3768
3882
|
});
|
|
3769
3883
|
}
|
|
3770
3884
|
);
|
|
3885
|
+
var resolveZeroWalletAddress = async (apiService) => {
|
|
3886
|
+
try {
|
|
3887
|
+
const wallets = await apiService.getWallets();
|
|
3888
|
+
const primary = wallets.find(
|
|
3889
|
+
(w) => w.source === "privy_embedded" && w.isPrimary
|
|
3890
|
+
);
|
|
3891
|
+
if (primary) return primary.walletAddress;
|
|
3892
|
+
const provisioned = await apiService.provisionWallet();
|
|
3893
|
+
return provisioned.walletAddress ?? null;
|
|
3894
|
+
} catch {
|
|
3895
|
+
return null;
|
|
3896
|
+
}
|
|
3897
|
+
};
|
|
3898
|
+
var walletMigrateCommand = (appContext) => new Command11("migrate").description(
|
|
3899
|
+
"Sweep all USDC from your private-key wallet into your Zero wallet"
|
|
3900
|
+
).option("--json", "Emit the migration result as JSON").option("-y, --yes", "Skip the confirmation prompt").action(async (options) => {
|
|
3901
|
+
const { apiService, paymentService } = appContext.services;
|
|
3902
|
+
const zeroDir = join5(homedir4(), ".zero");
|
|
3903
|
+
const configPath = join5(zeroDir, "config.json");
|
|
3904
|
+
const config = existsSync3(configPath) ? readConfig(configPath) : {};
|
|
3905
|
+
const privateKey = appContext.env.ZERO_PRIVATE_KEY ?? config.privateKey;
|
|
3906
|
+
if (!privateKey) {
|
|
3907
|
+
console.error(
|
|
3908
|
+
"No private-key wallet found. Run `zero wallet set <key>` first."
|
|
3909
|
+
);
|
|
3910
|
+
process.exitCode = 1;
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
const hasSession = Boolean(
|
|
3914
|
+
appContext.env.ZERO_SESSION_TOKEN || config.session
|
|
3915
|
+
);
|
|
3916
|
+
if (!hasSession) {
|
|
3917
|
+
console.error("Not logged in. Run `zero auth login` first.");
|
|
3918
|
+
process.exitCode = 1;
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
let source;
|
|
3922
|
+
try {
|
|
3923
|
+
source = privateKeyToAccount2(privateKey);
|
|
3924
|
+
} catch {
|
|
3925
|
+
console.error("Invalid private key in config.");
|
|
3926
|
+
process.exitCode = 1;
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3929
|
+
const to = await resolveZeroWalletAddress(apiService);
|
|
3930
|
+
if (!to) {
|
|
3931
|
+
console.error(
|
|
3932
|
+
"Could not resolve your Zero wallet. Run `zero auth login` and try again."
|
|
3933
|
+
);
|
|
3934
|
+
process.exitCode = 1;
|
|
3935
|
+
return;
|
|
3936
|
+
}
|
|
3937
|
+
if (to.toLowerCase() === source.address.toLowerCase()) {
|
|
3938
|
+
console.error(
|
|
3939
|
+
"Source and destination are the same wallet \u2014 nothing to migrate."
|
|
3940
|
+
);
|
|
3941
|
+
process.exitCode = 1;
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3944
|
+
const balance = await paymentService.getSweepableBalance();
|
|
3945
|
+
if (balance.raw <= 0n) {
|
|
3946
|
+
if (options.json) {
|
|
3947
|
+
console.log(
|
|
3948
|
+
JSON.stringify({ destination: to, legs: [], keyRemoved: false })
|
|
3949
|
+
);
|
|
3950
|
+
} else {
|
|
3951
|
+
console.log("Nothing to migrate (no USDC found).");
|
|
3952
|
+
}
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
if (!options.yes) {
|
|
3956
|
+
if (options.json || !process.stdin.isTTY) {
|
|
3957
|
+
console.error(
|
|
3958
|
+
"Refusing to migrate without confirmation. Re-run with --yes to proceed."
|
|
3959
|
+
);
|
|
3960
|
+
process.exitCode = 1;
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
const proceed = await confirm({
|
|
3964
|
+
message: `Please confirm you would like to transfer $${balance.usdc} from your private wallet to your Zero managed wallet`
|
|
3965
|
+
});
|
|
3966
|
+
if (isCancel(proceed) || !proceed) {
|
|
3967
|
+
console.log("Migration cancelled.");
|
|
3968
|
+
return;
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
const results = await paymentService.migrateAllUsdc(to, apiService);
|
|
3972
|
+
const anyFailed = results.some((r) => r.status === "failed");
|
|
3973
|
+
const anySwept = results.some((r) => r.status === "swept");
|
|
3974
|
+
let backupPath = null;
|
|
3975
|
+
if (anySwept && !anyFailed && config.privateKey) {
|
|
3976
|
+
try {
|
|
3977
|
+
backupPath = backupAndStripPrivateKey(zeroDir).backupPath;
|
|
3978
|
+
} catch {
|
|
3979
|
+
backupPath = null;
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
if (options.json) {
|
|
3983
|
+
console.log(
|
|
3984
|
+
JSON.stringify({
|
|
3985
|
+
destination: to,
|
|
3986
|
+
legs: results,
|
|
3987
|
+
keyRemoved: backupPath !== null,
|
|
3988
|
+
backupPath
|
|
3989
|
+
})
|
|
3990
|
+
);
|
|
3991
|
+
} else {
|
|
3992
|
+
console.log(`Migrating USDC \u2192 ${to}`);
|
|
3993
|
+
for (const r of results) {
|
|
3994
|
+
if (r.status === "swept") {
|
|
3995
|
+
console.log(
|
|
3996
|
+
` ${r.chain}: swept ${r.amount} USDC (tx ${r.txHash})`
|
|
3997
|
+
);
|
|
3998
|
+
} else if (r.status === "skipped") {
|
|
3999
|
+
console.log(` ${r.chain}: skipped (no balance)`);
|
|
4000
|
+
} else {
|
|
4001
|
+
console.log(` ${r.chain}: FAILED \u2014 ${r.error}`);
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
if (backupPath) {
|
|
4005
|
+
console.log("");
|
|
4006
|
+
console.log(
|
|
4007
|
+
`Private key removed from config (backed up to ${backupPath}). Your Zero wallet is now primary.`
|
|
4008
|
+
);
|
|
4009
|
+
} else if (anyFailed) {
|
|
4010
|
+
console.log("");
|
|
4011
|
+
console.log(
|
|
4012
|
+
"A transfer failed \u2014 your private key was kept. Re-run `zero wallet migrate` to retry."
|
|
4013
|
+
);
|
|
4014
|
+
} else if (!anySwept) {
|
|
4015
|
+
console.log("");
|
|
4016
|
+
console.log("Nothing to migrate (no USDC found).");
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
if (anyFailed) process.exitCode = 1;
|
|
4020
|
+
});
|
|
3771
4021
|
var walletCommand = (appContext) => {
|
|
3772
4022
|
const cmd = new Command11("wallet").description("Manage your wallet");
|
|
3773
4023
|
cmd.addCommand(walletBalanceCommand(appContext));
|
|
@@ -3775,22 +4025,23 @@ var walletCommand = (appContext) => {
|
|
|
3775
4025
|
cmd.addCommand(walletAddressCommand(appContext));
|
|
3776
4026
|
cmd.addCommand(walletSetCommand(appContext));
|
|
3777
4027
|
cmd.addCommand(walletGenerateCommand(appContext));
|
|
4028
|
+
cmd.addCommand(walletMigrateCommand(appContext), { hidden: true });
|
|
3778
4029
|
return cmd;
|
|
3779
4030
|
};
|
|
3780
4031
|
|
|
3781
4032
|
// src/commands/welcome-command.ts
|
|
3782
|
-
import { existsSync as existsSync4, readFileSync as
|
|
4033
|
+
import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
|
|
3783
4034
|
import { homedir as homedir5 } from "os";
|
|
3784
|
-
import { join as
|
|
4035
|
+
import { join as join6 } from "path";
|
|
3785
4036
|
import { Command as Command12 } from "commander";
|
|
3786
4037
|
import open3 from "open";
|
|
3787
4038
|
import { getAddress } from "viem";
|
|
3788
4039
|
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
3789
4040
|
var readPrivateKey = () => {
|
|
3790
|
-
const configPath =
|
|
4041
|
+
const configPath = join6(homedir5(), ".zero", "config.json");
|
|
3791
4042
|
if (!existsSync4(configPath)) return null;
|
|
3792
4043
|
try {
|
|
3793
|
-
const config = JSON.parse(
|
|
4044
|
+
const config = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
3794
4045
|
if (typeof config.privateKey === "string") {
|
|
3795
4046
|
return config.privateKey;
|
|
3796
4047
|
}
|
|
@@ -3936,12 +4187,12 @@ var getEnv = () => {
|
|
|
3936
4187
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3937
4188
|
import { existsSync as existsSync7 } from "fs";
|
|
3938
4189
|
import { homedir as homedir6 } from "os";
|
|
3939
|
-
import { join as
|
|
4190
|
+
import { join as join8 } from "path";
|
|
3940
4191
|
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
3941
4192
|
|
|
3942
4193
|
// src/services/analytics-service.ts
|
|
3943
4194
|
import { randomUUID } from "crypto";
|
|
3944
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as
|
|
4195
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
3945
4196
|
import { dirname as dirname2 } from "path";
|
|
3946
4197
|
import { PostHog } from "posthog-node";
|
|
3947
4198
|
var POSTHOG_API_KEY = "phc_B2vLyNxAf2mnqvdPQajf4d4b2iXc35dep2ZrvebMJLuX";
|
|
@@ -3964,7 +4215,7 @@ var AnalyticsService = class {
|
|
|
3964
4215
|
let persistedAnonId;
|
|
3965
4216
|
try {
|
|
3966
4217
|
if (existsSync5(opts.configPath)) {
|
|
3967
|
-
const config = JSON.parse(
|
|
4218
|
+
const config = JSON.parse(readFileSync10(opts.configPath, "utf8"));
|
|
3968
4219
|
if (config.telemetry === false) {
|
|
3969
4220
|
telemetryEnabled = false;
|
|
3970
4221
|
}
|
|
@@ -3990,7 +4241,7 @@ var AnalyticsService = class {
|
|
|
3990
4241
|
try {
|
|
3991
4242
|
const dir = dirname2(opts.configPath);
|
|
3992
4243
|
mkdirSync4(dir, { recursive: true });
|
|
3993
|
-
const existing = existsSync5(opts.configPath) ? JSON.parse(
|
|
4244
|
+
const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync10(opts.configPath, "utf8")) : {};
|
|
3994
4245
|
writeFileSync4(
|
|
3995
4246
|
opts.configPath,
|
|
3996
4247
|
JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
|
|
@@ -4031,7 +4282,7 @@ var AnalyticsService = class {
|
|
|
4031
4282
|
if (anonId === walletAddress) return;
|
|
4032
4283
|
let aliasedTo;
|
|
4033
4284
|
try {
|
|
4034
|
-
const config = JSON.parse(
|
|
4285
|
+
const config = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
4035
4286
|
if (typeof config.aliasedTo === "string") {
|
|
4036
4287
|
aliasedTo = config.aliasedTo;
|
|
4037
4288
|
}
|
|
@@ -4041,7 +4292,7 @@ var AnalyticsService = class {
|
|
|
4041
4292
|
this.posthog.alias({ distinctId: walletAddress, alias: anonId });
|
|
4042
4293
|
if (process.env.VITEST) return;
|
|
4043
4294
|
try {
|
|
4044
|
-
const config = existsSync5(configPath) ? JSON.parse(
|
|
4295
|
+
const config = existsSync5(configPath) ? JSON.parse(readFileSync10(configPath, "utf8")) : {};
|
|
4045
4296
|
writeFileSync4(
|
|
4046
4297
|
configPath,
|
|
4047
4298
|
JSON.stringify({ ...config, aliasedTo: walletAddress }, null, 2)
|
|
@@ -4145,14 +4396,14 @@ var createApiAccount = (walletAddress, api) => toAccount({
|
|
|
4145
4396
|
});
|
|
4146
4397
|
|
|
4147
4398
|
// src/services/state-service.ts
|
|
4148
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as
|
|
4149
|
-
import { join as
|
|
4399
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
4400
|
+
import { join as join7 } from "path";
|
|
4150
4401
|
var RECENT_SEARCH_LIMIT = 10;
|
|
4151
4402
|
var StateService = class {
|
|
4152
4403
|
constructor(zeroDir) {
|
|
4153
4404
|
this.zeroDir = zeroDir;
|
|
4154
|
-
this.lastSearchPath =
|
|
4155
|
-
this.recentSearchesPath =
|
|
4405
|
+
this.lastSearchPath = join7(zeroDir, "last_search.json");
|
|
4406
|
+
this.recentSearchesPath = join7(zeroDir, "recent_searches.json");
|
|
4156
4407
|
}
|
|
4157
4408
|
lastSearchPath;
|
|
4158
4409
|
recentSearchesPath;
|
|
@@ -4172,7 +4423,7 @@ var StateService = class {
|
|
|
4172
4423
|
loadLastSearch = () => {
|
|
4173
4424
|
try {
|
|
4174
4425
|
if (!existsSync6(this.lastSearchPath)) return null;
|
|
4175
|
-
const raw =
|
|
4426
|
+
const raw = readFileSync11(this.lastSearchPath, "utf8");
|
|
4176
4427
|
return JSON.parse(raw);
|
|
4177
4428
|
} catch {
|
|
4178
4429
|
return null;
|
|
@@ -4184,7 +4435,7 @@ var StateService = class {
|
|
|
4184
4435
|
const last = this.loadLastSearch();
|
|
4185
4436
|
return { searches: last ? [last] : [] };
|
|
4186
4437
|
}
|
|
4187
|
-
const raw =
|
|
4438
|
+
const raw = readFileSync11(this.recentSearchesPath, "utf8");
|
|
4188
4439
|
const parsed = JSON.parse(raw);
|
|
4189
4440
|
return { searches: parsed.searches ?? [] };
|
|
4190
4441
|
} catch {
|
|
@@ -4312,8 +4563,8 @@ var buildOnSessionRefreshed = (configPath) => async (tokens) => {
|
|
|
4312
4563
|
writeSecureFile(configPath, JSON.stringify(next, null, 2));
|
|
4313
4564
|
};
|
|
4314
4565
|
var getServices = async (env) => {
|
|
4315
|
-
const zeroDir =
|
|
4316
|
-
const configPath =
|
|
4566
|
+
const zeroDir = join8(homedir6(), ".zero");
|
|
4567
|
+
const configPath = join8(zeroDir, "config.json");
|
|
4317
4568
|
const config = existsSync7(configPath) ? readConfig(configPath) : {};
|
|
4318
4569
|
const { credentials, privateKey } = resolveCredentials(env, config);
|
|
4319
4570
|
const lowBalanceWarning = typeof config.lowBalanceWarning === "number" ? config.lowBalanceWarning : 1;
|
|
@@ -4324,20 +4575,17 @@ var getServices = async (env) => {
|
|
|
4324
4575
|
buildOnSessionRefreshed(configPath)
|
|
4325
4576
|
);
|
|
4326
4577
|
let account = privateKey ? privateKeyToAccount4(privateKey) : null;
|
|
4327
|
-
let managed = false;
|
|
4328
4578
|
if (!account && credentials.kind === "session") {
|
|
4329
4579
|
const address = await resolveManagedWalletAddress(apiService);
|
|
4330
4580
|
if (address) {
|
|
4331
4581
|
account = createApiAccount(address, apiService);
|
|
4332
|
-
managed = true;
|
|
4333
4582
|
}
|
|
4334
4583
|
}
|
|
4335
4584
|
if (account && !apiService.walletAddress) {
|
|
4336
4585
|
apiService.setWalletAddress(account.address);
|
|
4337
4586
|
}
|
|
4338
4587
|
const paymentService = new PaymentService(account, {
|
|
4339
|
-
lowBalanceWarning
|
|
4340
|
-
managed
|
|
4588
|
+
lowBalanceWarning
|
|
4341
4589
|
});
|
|
4342
4590
|
const stateService = new StateService(zeroDir);
|
|
4343
4591
|
const walletService = new WalletService(
|
|
@@ -4393,12 +4641,12 @@ import {
|
|
|
4393
4641
|
existsSync as existsSync8,
|
|
4394
4642
|
lstatSync,
|
|
4395
4643
|
mkdirSync as mkdirSync6,
|
|
4396
|
-
readFileSync as
|
|
4644
|
+
readFileSync as readFileSync12,
|
|
4397
4645
|
readlinkSync,
|
|
4398
4646
|
writeFileSync as writeFileSync6
|
|
4399
4647
|
} from "fs";
|
|
4400
4648
|
import { homedir as homedir7 } from "os";
|
|
4401
|
-
import { dirname as dirname3, join as
|
|
4649
|
+
import { dirname as dirname3, join as join9, resolve } from "path";
|
|
4402
4650
|
var CACHE_FILENAME = "update_check.json";
|
|
4403
4651
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@zeroxyz/cli/latest";
|
|
4404
4652
|
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
@@ -4425,7 +4673,7 @@ var detectInstallMethod = (opts = {}) => {
|
|
|
4425
4673
|
const home = opts.home ?? homedir7();
|
|
4426
4674
|
if (pkg) return "binary";
|
|
4427
4675
|
const resolved = resolveExecPath(execPath);
|
|
4428
|
-
const zeroBin =
|
|
4676
|
+
const zeroBin = join9(home, ".zero", "bin");
|
|
4429
4677
|
if (resolved.startsWith(zeroBin)) return "binary";
|
|
4430
4678
|
return "npm";
|
|
4431
4679
|
};
|
|
@@ -4450,12 +4698,12 @@ var compareVersions = (a, b) => {
|
|
|
4450
4698
|
if (pb.pre === null) return -1;
|
|
4451
4699
|
return pa.pre < pb.pre ? -1 : 1;
|
|
4452
4700
|
};
|
|
4453
|
-
var cachePath = (zeroDir) =>
|
|
4701
|
+
var cachePath = (zeroDir) => join9(zeroDir, CACHE_FILENAME);
|
|
4454
4702
|
var readCache = (zeroDir) => {
|
|
4455
4703
|
try {
|
|
4456
4704
|
const path = cachePath(zeroDir);
|
|
4457
4705
|
if (!existsSync8(path)) return emptyCache;
|
|
4458
|
-
const raw =
|
|
4706
|
+
const raw = readFileSync12(path, "utf8");
|
|
4459
4707
|
const parsed = JSON.parse(raw);
|
|
4460
4708
|
return {
|
|
4461
4709
|
lastCheckedMs: typeof parsed.lastCheckedMs === "number" ? parsed.lastCheckedMs : 0,
|
|
@@ -4544,7 +4792,7 @@ var main = async () => {
|
|
|
4544
4792
|
console.error("Failed to create app context");
|
|
4545
4793
|
process.exit(1);
|
|
4546
4794
|
}
|
|
4547
|
-
const zeroDir =
|
|
4795
|
+
const zeroDir = join10(homedir8(), ".zero");
|
|
4548
4796
|
maybePrintUpdateBanner(zeroDir, package_default.version);
|
|
4549
4797
|
const app = createApp(appContext);
|
|
4550
4798
|
let caughtError = null;
|