@reserve-protocol/dtf-cli 0.1.0 → 0.1.1
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 +27807 -143
- package/package.json +3 -3
- package/dist/commands/basket.d.ts +0 -3
- package/dist/commands/basket.d.ts.map +0 -1
- package/dist/commands/basket.js +0 -131
- package/dist/commands/basket.js.map +0 -1
- package/dist/commands/deploy.d.ts +0 -3
- package/dist/commands/deploy.d.ts.map +0 -1
- package/dist/commands/deploy.js +0 -495
- package/dist/commands/deploy.js.map +0 -1
- package/dist/commands/discover.d.ts +0 -3
- package/dist/commands/discover.d.ts.map +0 -1
- package/dist/commands/discover.js +0 -141
- package/dist/commands/discover.js.map +0 -1
- package/dist/commands/earn.d.ts +0 -3
- package/dist/commands/earn.d.ts.map +0 -1
- package/dist/commands/earn.js +0 -42
- package/dist/commands/earn.js.map +0 -1
- package/dist/commands/fees.d.ts +0 -3
- package/dist/commands/fees.d.ts.map +0 -1
- package/dist/commands/fees.js +0 -82
- package/dist/commands/fees.js.map +0 -1
- package/dist/commands/forum.d.ts +0 -3
- package/dist/commands/forum.d.ts.map +0 -1
- package/dist/commands/forum.js +0 -304
- package/dist/commands/forum.js.map +0 -1
- package/dist/commands/governance.d.ts +0 -3
- package/dist/commands/governance.d.ts.map +0 -1
- package/dist/commands/governance.js +0 -97
- package/dist/commands/governance.js.map +0 -1
- package/dist/commands/info.d.ts +0 -3
- package/dist/commands/info.d.ts.map +0 -1
- package/dist/commands/info.js +0 -114
- package/dist/commands/info.js.map +0 -1
- package/dist/commands/prices.d.ts +0 -3
- package/dist/commands/prices.d.ts.map +0 -1
- package/dist/commands/prices.js +0 -71
- package/dist/commands/prices.js.map +0 -1
- package/dist/commands/proposals.d.ts +0 -3
- package/dist/commands/proposals.d.ts.map +0 -1
- package/dist/commands/proposals.js +0 -261
- package/dist/commands/proposals.js.map +0 -1
- package/dist/commands/quote.d.ts +0 -3
- package/dist/commands/quote.d.ts.map +0 -1
- package/dist/commands/quote.js +0 -100
- package/dist/commands/quote.js.map +0 -1
- package/dist/commands/rebalance-history.d.ts +0 -24
- package/dist/commands/rebalance-history.d.ts.map +0 -1
- package/dist/commands/rebalance-history.js +0 -229
- package/dist/commands/rebalance-history.js.map +0 -1
- package/dist/commands/rebalance.d.ts +0 -3
- package/dist/commands/rebalance.d.ts.map +0 -1
- package/dist/commands/rebalance.js +0 -162
- package/dist/commands/rebalance.js.map +0 -1
- package/dist/commands/revenue.d.ts +0 -3
- package/dist/commands/revenue.d.ts.map +0 -1
- package/dist/commands/revenue.js +0 -135
- package/dist/commands/revenue.js.map +0 -1
- package/dist/commands/roles.d.ts +0 -3
- package/dist/commands/roles.d.ts.map +0 -1
- package/dist/commands/roles.js +0 -53
- package/dist/commands/roles.js.map +0 -1
- package/dist/commands/rsr-burns.d.ts +0 -3
- package/dist/commands/rsr-burns.d.ts.map +0 -1
- package/dist/commands/rsr-burns.js +0 -134
- package/dist/commands/rsr-burns.js.map +0 -1
- package/dist/commands/staking.d.ts +0 -3
- package/dist/commands/staking.d.ts.map +0 -1
- package/dist/commands/staking.js +0 -102
- package/dist/commands/staking.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/cache.d.ts +0 -4
- package/dist/lib/cache.d.ts.map +0 -1
- package/dist/lib/cache.js +0 -58
- package/dist/lib/cache.js.map +0 -1
- package/dist/lib/cached-config.d.ts +0 -12
- package/dist/lib/cached-config.d.ts.map +0 -1
- package/dist/lib/cached-config.js +0 -18
- package/dist/lib/cached-config.js.map +0 -1
- package/dist/lib/config.d.ts +0 -22
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js +0 -94
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/dtf-registry.d.ts +0 -19
- package/dist/lib/dtf-registry.d.ts.map +0 -1
- package/dist/lib/dtf-registry.js +0 -54
- package/dist/lib/dtf-registry.js.map +0 -1
- package/dist/lib/output.d.ts +0 -16
- package/dist/lib/output.d.ts.map +0 -1
- package/dist/lib/output.js +0 -73
- package/dist/lib/output.js.map +0 -1
- package/dist/lib/token-symbols.d.ts +0 -7
- package/dist/lib/token-symbols.d.ts.map +0 -1
- package/dist/lib/token-symbols.js +0 -44
- package/dist/lib/token-symbols.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reserve-protocol/dtf-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": ["dist"],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "
|
|
11
|
+
"build": "bun build src/index.ts --outfile dist/index.js --target node",
|
|
12
12
|
"prepublishOnly": "bun run build"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@reserve-protocol/dtf-sdk": "^0.1.
|
|
15
|
+
"@reserve-protocol/dtf-sdk": "^0.1.1",
|
|
16
16
|
"viem": "^2.45.2"
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"basket.d.ts","sourceRoot":"","sources":["../../src/commands/basket.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAY9C,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,iBAUrE"}
|
package/dist/commands/basket.js
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { formatUnits } from 'viem';
|
|
2
|
-
import { createDtfClients, readBasket, fetchDtfBasket, fetchDtfConfig, fetchTokenPrices, } from '@reserve-protocol/dtf-sdk';
|
|
3
|
-
import { fetchDtfConfigCached } from '../lib/cached-config';
|
|
4
|
-
import { printHeader, printJson, printTable, formatAddress, formatUsd, formatPct, chainLabel, } from '../lib/output';
|
|
5
|
-
export async function basketCommand(address, config) {
|
|
6
|
-
// WHY: Use the API path by default — one call for basket + prices + weights.
|
|
7
|
-
// Falls back to RPC path if API fails (e.g., new DTF not yet indexed).
|
|
8
|
-
try {
|
|
9
|
-
await basketFromApi(address, config);
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
// NOTE: API may fail for newly deployed DTFs not yet indexed.
|
|
13
|
-
console.warn('API basket fetch failed, falling back to RPC...');
|
|
14
|
-
await basketFromRpc(address, config);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
async function basketFromApi(address, config) {
|
|
18
|
-
const { publicClient } = createDtfClients({
|
|
19
|
-
chainId: config.chainId,
|
|
20
|
-
rpc: config.rpc,
|
|
21
|
-
});
|
|
22
|
-
const [data, dtfConfig] = await Promise.all([
|
|
23
|
-
fetchDtfBasket(config.chainId, address),
|
|
24
|
-
fetchDtfConfigCached(fetchDtfConfig, publicClient, config.chainId, address),
|
|
25
|
-
]);
|
|
26
|
-
// WHY: API doesn't always return token symbols (e.g. vault tokens like mooMorpho).
|
|
27
|
-
// Subgraph config always has them, so use it as fallback.
|
|
28
|
-
const symbolLookup = {};
|
|
29
|
-
for (const t of dtfConfig.tokens) {
|
|
30
|
-
symbolLookup[t.address.toLowerCase()] = t.symbol;
|
|
31
|
-
}
|
|
32
|
-
if (config.json) {
|
|
33
|
-
printJson({
|
|
34
|
-
dtf: address,
|
|
35
|
-
chain: config.chainId,
|
|
36
|
-
chainLabel: chainLabel(config.chainId),
|
|
37
|
-
tokens: data.basket.map((t) => ({
|
|
38
|
-
address: t.address,
|
|
39
|
-
symbol: t.symbol ?? symbolLookup[t.address.toLowerCase()] ?? null,
|
|
40
|
-
decimals: t.decimals,
|
|
41
|
-
price: t.price,
|
|
42
|
-
weight: Number(t.weight) / 100,
|
|
43
|
-
amount: t.amount,
|
|
44
|
-
amountRaw: t.amountRaw,
|
|
45
|
-
value: t.price * t.amount,
|
|
46
|
-
})),
|
|
47
|
-
totalSupply: data.totalSupply,
|
|
48
|
-
tvl: Math.round(data.price * data.totalSupply),
|
|
49
|
-
sharePrice: data.price,
|
|
50
|
-
marketCap: data.marketCap,
|
|
51
|
-
});
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
printHeader(`DTF Basket — ${formatAddress(address)} (Chain ${config.chainId})`);
|
|
55
|
-
console.log();
|
|
56
|
-
printTable([
|
|
57
|
-
{ header: 'Address', key: 'address' },
|
|
58
|
-
{ header: 'Weight', key: 'weight', align: 'right' },
|
|
59
|
-
{ header: 'Price', key: 'price', align: 'right' },
|
|
60
|
-
{ header: 'Amount/Share', key: 'amount', align: 'right' },
|
|
61
|
-
], data.basket.map((t) => ({
|
|
62
|
-
address: formatAddress(t.address),
|
|
63
|
-
weight: `${t.weight}%`,
|
|
64
|
-
price: formatUsd(t.price),
|
|
65
|
-
amount: t.amount.toLocaleString('en-US', { maximumFractionDigits: 6 }),
|
|
66
|
-
})));
|
|
67
|
-
console.log();
|
|
68
|
-
console.log(`Share Price: ${formatUsd(data.price)}`);
|
|
69
|
-
console.log(`Market Cap: ${formatUsd(data.marketCap)}`);
|
|
70
|
-
}
|
|
71
|
-
async function basketFromRpc(address, config) {
|
|
72
|
-
const { publicClient } = createDtfClients({
|
|
73
|
-
chainId: config.chainId,
|
|
74
|
-
rpc: config.rpc,
|
|
75
|
-
});
|
|
76
|
-
const basket = await readBasket(publicClient, address);
|
|
77
|
-
const prices = await fetchTokenPrices(config.chainId, basket.tokens.map((t) => t.address));
|
|
78
|
-
const tokenValues = basket.tokens.map((token, i) => {
|
|
79
|
-
const balance = basket.balances[i];
|
|
80
|
-
const balanceFormatted = Number(formatUnits(balance, token.decimals));
|
|
81
|
-
const price = prices[token.address.toLowerCase()] ?? 0;
|
|
82
|
-
const usdValue = balanceFormatted * price;
|
|
83
|
-
return { token, balance, balanceFormatted, price, usdValue };
|
|
84
|
-
});
|
|
85
|
-
const tvl = tokenValues.reduce((sum, t) => sum + t.usdValue, 0);
|
|
86
|
-
const totalSupplyFormatted = Number(formatUnits(basket.totalSupply, 18));
|
|
87
|
-
if (config.json) {
|
|
88
|
-
printJson({
|
|
89
|
-
dtf: address,
|
|
90
|
-
chain: config.chainId,
|
|
91
|
-
chainLabel: chainLabel(config.chainId),
|
|
92
|
-
tokens: tokenValues.map((t) => ({
|
|
93
|
-
symbol: t.token.symbol,
|
|
94
|
-
address: t.token.address,
|
|
95
|
-
balance: t.balance.toString(),
|
|
96
|
-
decimals: t.token.decimals,
|
|
97
|
-
price: t.price,
|
|
98
|
-
weight: tvl > 0 ? t.usdValue / tvl : 0,
|
|
99
|
-
usdValue: Math.round(t.usdValue),
|
|
100
|
-
})),
|
|
101
|
-
totalSupply: basket.totalSupply.toString(),
|
|
102
|
-
tvl: Math.round(tvl),
|
|
103
|
-
sharePrice: totalSupplyFormatted > 0 ? tvl / totalSupplyFormatted : 0,
|
|
104
|
-
});
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
printHeader(`DTF Basket — ${formatAddress(address)} (Chain ${config.chainId})`);
|
|
108
|
-
console.log();
|
|
109
|
-
printTable([
|
|
110
|
-
{ header: 'Token', key: 'symbol' },
|
|
111
|
-
{ header: 'Address', key: 'address' },
|
|
112
|
-
{ header: 'Balance', key: 'balance', align: 'right' },
|
|
113
|
-
{ header: 'Weight', key: 'weight', align: 'right' },
|
|
114
|
-
{ header: 'Value (USD)', key: 'value', align: 'right' },
|
|
115
|
-
], tokenValues.map((t) => ({
|
|
116
|
-
symbol: t.token.symbol,
|
|
117
|
-
address: formatAddress(t.token.address),
|
|
118
|
-
balance: t.balanceFormatted.toLocaleString('en-US', {
|
|
119
|
-
maximumFractionDigits: 4,
|
|
120
|
-
}),
|
|
121
|
-
weight: tvl > 0 ? formatPct(t.usdValue / tvl) : '—',
|
|
122
|
-
value: formatUsd(t.usdValue),
|
|
123
|
-
})));
|
|
124
|
-
console.log();
|
|
125
|
-
console.log(`TVL: ${formatUsd(tvl)}`);
|
|
126
|
-
console.log(`Total Supply: ${totalSupplyFormatted.toLocaleString('en-US', { maximumFractionDigits: 2 })}`);
|
|
127
|
-
if (totalSupplyFormatted > 0) {
|
|
128
|
-
console.log(`Share Price: ${formatUsd(tvl / totalSupplyFormatted)}`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
//# sourceMappingURL=basket.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"basket.js","sourceRoot":"","sources":["../../src/commands/basket.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AA8D9C,wBAAsB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,iBAsMpE"}
|
package/dist/commands/deploy.js
DELETED
|
@@ -1,495 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from 'fs';
|
|
2
|
-
import { isAddress } from 'viem';
|
|
3
|
-
import { createDtfClients, submitZapDeploy, resolveToken, getTokenRegistry, fetchTokenPrices, fetchTokenMetadata, extractDeployedAddress, buildDefaultFeeRecipients, VOTE_LOCK_ADDRESS, DEFAULT_TVL_FEE, DEFAULT_MINT_FEE, DEFAULT_AUCTION_LENGTH, DEFAULT_FLAGS, DEFAULT_TRADING_GOV_PARAMS, DEFAULT_OWNER_GOV_PARAMS, DEFAULT_GUARDIANS, DEFAULT_AUCTION_LAUNCHERS, DEFAULT_DEPLOY_SLIPPAGE, } from '@reserve-protocol/dtf-sdk';
|
|
4
|
-
import { printJson, printHeader, printTable, chainLabel } from '../lib/output';
|
|
5
|
-
// ETH sentinel address used by Zapper API for native ETH
|
|
6
|
-
const ETH_SENTINEL = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
|
7
|
-
const CHAIN_SLUGS = {
|
|
8
|
-
1: 'ethereum',
|
|
9
|
-
56: 'bsc',
|
|
10
|
-
8453: 'base',
|
|
11
|
-
};
|
|
12
|
-
const DEPLOY_HELP = `
|
|
13
|
-
dtf deploy — Deploy a new DTF
|
|
14
|
-
|
|
15
|
-
Usage:
|
|
16
|
-
dtf deploy --name "My Index" --symbol MIDX --basket "50% WETH, 30% USDC, 20% cbBTC" --amount 0.1 --wallet-key $KEY
|
|
17
|
-
dtf deploy --config deploy.json --wallet-key $KEY
|
|
18
|
-
dtf deploy --list-tokens --chain 8453
|
|
19
|
-
dtf deploy --name "My DTF" --symbol MDTF --basket basket.csv --amount 0.1 --wallet-key $KEY
|
|
20
|
-
dtf deploy --name "Test" --symbol TST --basket "50% WETH, 50% USDC" --wallet-key $KEY --dry-run
|
|
21
|
-
|
|
22
|
-
Options:
|
|
23
|
-
--name <name> DTF name
|
|
24
|
-
--symbol <symbol> DTF ticker symbol
|
|
25
|
-
--basket <spec> Basket: inline "50% WETH, 30% USDC" or path to .csv file
|
|
26
|
-
--amount <eth> Amount of ETH to deposit
|
|
27
|
-
--mandate <text> DTF mandate/description (optional)
|
|
28
|
-
--config <file> JSON config file (alternative to --name/--symbol/--basket)
|
|
29
|
-
--ungoverned Deploy without DAO governance (wallet-owned)
|
|
30
|
-
--dry-run Preview without sending transaction
|
|
31
|
-
--list-tokens Show available token symbols for --chain
|
|
32
|
-
--wallet-key <key> Private key (or WALLET_KEY env var)
|
|
33
|
-
--json JSON output
|
|
34
|
-
|
|
35
|
-
Tip: Use WALLET_KEY env var instead of --wallet-key to keep your key out of shell history.
|
|
36
|
-
`;
|
|
37
|
-
// -- Main --
|
|
38
|
-
export async function deployCommand(config, args) {
|
|
39
|
-
if (args.includes('--help')) {
|
|
40
|
-
console.log(DEPLOY_HELP);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (args.includes('--list-tokens')) {
|
|
44
|
-
return listTokens(config);
|
|
45
|
-
}
|
|
46
|
-
// -- Parse & validate --
|
|
47
|
-
const deployConfig = parseDeployConfig(args);
|
|
48
|
-
const chainId = config.chainId;
|
|
49
|
-
const ungoverned = deployConfig.ungoverned;
|
|
50
|
-
const dryRun = args.includes('--dry-run');
|
|
51
|
-
if (!config.walletKey) {
|
|
52
|
-
throw new Error('Deploy requires --wallet-key or WALLET_KEY env var');
|
|
53
|
-
}
|
|
54
|
-
if (!ungoverned) {
|
|
55
|
-
const voteLock = VOTE_LOCK_ADDRESS[chainId];
|
|
56
|
-
if (!voteLock || voteLock === '0x0000000000000000000000000000000000000000') {
|
|
57
|
-
throw new Error(`Governed deploy not available on ${chainLabel(chainId)} (no vote-lock DAO). Use --ungoverned or deploy on Base.`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
validateBasket(deployConfig.basket);
|
|
61
|
-
// -- Resolve addresses & fetch prices --
|
|
62
|
-
const resolvedBasket = resolveBasketAddresses(deployConfig.basket, chainId);
|
|
63
|
-
const { publicClient, walletClient, account } = createDtfClients({
|
|
64
|
-
chainId,
|
|
65
|
-
rpc: config.rpc,
|
|
66
|
-
walletKey: config.walletKey,
|
|
67
|
-
});
|
|
68
|
-
if (!walletClient || !account) {
|
|
69
|
-
throw new Error('Failed to create wallet client from provided key');
|
|
70
|
-
}
|
|
71
|
-
const addresses = resolvedBasket.map((t) => t.address);
|
|
72
|
-
const needDecimals = resolvedBasket.filter((t) => t.decimals === undefined);
|
|
73
|
-
// WHY: Price and metadata fetches are independent — run in parallel.
|
|
74
|
-
const [prices, metaEntries] = await Promise.all([
|
|
75
|
-
fetchTokenPrices(chainId, addresses),
|
|
76
|
-
needDecimals.length > 0
|
|
77
|
-
// WHY: createDtfClients returns a narrower type than fetchTokenMetadata expects.
|
|
78
|
-
// The PublicClient from viem satisfies the multicall interface at runtime.
|
|
79
|
-
? fetchTokenMetadata(publicClient, needDecimals.map((t) => t.address))
|
|
80
|
-
: Promise.resolve([]),
|
|
81
|
-
]);
|
|
82
|
-
const decimalMap = {};
|
|
83
|
-
for (const m of metaEntries) {
|
|
84
|
-
decimalMap[m.address.toLowerCase()] = m.decimals;
|
|
85
|
-
}
|
|
86
|
-
// -- Calculate basket amounts --
|
|
87
|
-
const { basketAssets, basketAmounts } = calculateBasketAmounts(resolvedBasket, prices, decimalMap, chainId);
|
|
88
|
-
// -- Build Zap params --
|
|
89
|
-
const stToken = ungoverned ? undefined : VOTE_LOCK_ADDRESS[chainId];
|
|
90
|
-
const amountInWei = parseEthToWei(deployConfig.amount);
|
|
91
|
-
const feeRecipients = stToken
|
|
92
|
-
? buildDefaultFeeRecipients(chainId, stToken)
|
|
93
|
-
: [{ recipient: account.address, portion: (10n ** 18n).toString() }];
|
|
94
|
-
const zapParams = {
|
|
95
|
-
chainId,
|
|
96
|
-
tokenIn: ETH_SENTINEL,
|
|
97
|
-
amountIn: amountInWei.toString(),
|
|
98
|
-
signer: account.address,
|
|
99
|
-
slippage: DEFAULT_DEPLOY_SLIPPAGE,
|
|
100
|
-
basicDetails: {
|
|
101
|
-
assets: basketAssets,
|
|
102
|
-
amounts: basketAmounts,
|
|
103
|
-
name: deployConfig.name,
|
|
104
|
-
symbol: deployConfig.symbol,
|
|
105
|
-
},
|
|
106
|
-
additionalDetails: {
|
|
107
|
-
auctionLength: DEFAULT_AUCTION_LENGTH.toString(),
|
|
108
|
-
feeRecipients,
|
|
109
|
-
tvlFee: DEFAULT_TVL_FEE.toString(),
|
|
110
|
-
mintFee: DEFAULT_MINT_FEE.toString(),
|
|
111
|
-
mandate: deployConfig.mandate,
|
|
112
|
-
},
|
|
113
|
-
folioFlags: { ...DEFAULT_FLAGS },
|
|
114
|
-
stToken,
|
|
115
|
-
ownerGovParams: stToken
|
|
116
|
-
? { ...DEFAULT_OWNER_GOV_PARAMS, guardians: DEFAULT_GUARDIANS[chainId] ?? [] }
|
|
117
|
-
: undefined,
|
|
118
|
-
tradingGovParams: stToken
|
|
119
|
-
? { ...DEFAULT_TRADING_GOV_PARAMS, guardians: DEFAULT_GUARDIANS[chainId] ?? [] }
|
|
120
|
-
: undefined,
|
|
121
|
-
auctionLaunchers: DEFAULT_AUCTION_LAUNCHERS[chainId] ?? [],
|
|
122
|
-
basketManagers: [],
|
|
123
|
-
brandManagers: [],
|
|
124
|
-
};
|
|
125
|
-
// -- Dry run --
|
|
126
|
-
if (dryRun) {
|
|
127
|
-
return outputDryRun(config, deployConfig, chainId, ungoverned, resolvedBasket, basketAmounts, prices, zapParams);
|
|
128
|
-
}
|
|
129
|
-
// -- Deploy --
|
|
130
|
-
if (!config.json) {
|
|
131
|
-
process.stdout.write(`Deploying ${deployConfig.name} (${deployConfig.symbol}) on ${chainLabel(chainId)} with ${deployConfig.amount} ETH...\n\n`);
|
|
132
|
-
}
|
|
133
|
-
const zapResult = await submitZapDeploy(zapParams);
|
|
134
|
-
if (zapResult.insufficientFunds) {
|
|
135
|
-
throw new Error(`Insufficient ETH balance. Need ${deployConfig.amount} ETH.`);
|
|
136
|
-
}
|
|
137
|
-
if (!zapResult.tx) {
|
|
138
|
-
throw new Error('Zapper API returned no transaction data. Check basket tokens and amounts.');
|
|
139
|
-
}
|
|
140
|
-
if (zapResult.approvalNeeded) {
|
|
141
|
-
// WHY: Should never happen with native ETH. If it does, something is wrong.
|
|
142
|
-
throw new Error('Unexpected: Zapper requires token approval for native ETH.');
|
|
143
|
-
}
|
|
144
|
-
if (!config.json) {
|
|
145
|
-
process.stdout.write(' Sending deploy tx... ');
|
|
146
|
-
}
|
|
147
|
-
const deployHash = await walletClient.sendTransaction({
|
|
148
|
-
to: zapResult.tx.to,
|
|
149
|
-
data: zapResult.tx.data,
|
|
150
|
-
value: BigInt(zapResult.tx.value),
|
|
151
|
-
chain: null,
|
|
152
|
-
});
|
|
153
|
-
// WHY: Print hash immediately so user has it even if they Ctrl+C during confirmation wait.
|
|
154
|
-
// In JSON mode, print to stderr so it doesn't corrupt JSON output on stdout.
|
|
155
|
-
if (config.json) {
|
|
156
|
-
console.error(`tx: ${deployHash}`);
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
process.stdout.write(`done (${deployHash.slice(0, 10)}...${deployHash.slice(-4)})\n`);
|
|
160
|
-
process.stdout.write(' Waiting for confirmation... ');
|
|
161
|
-
}
|
|
162
|
-
const receipt = await publicClient.waitForTransactionReceipt({
|
|
163
|
-
hash: deployHash,
|
|
164
|
-
timeout: 300_000, // 5 min
|
|
165
|
-
});
|
|
166
|
-
if (receipt.status === 'reverted') {
|
|
167
|
-
const explorer = chainId === 1 ? 'etherscan.io' : chainId === 8453 ? 'basescan.org' : 'bscscan.com';
|
|
168
|
-
throw new Error(`Deploy transaction reverted. Hash: ${deployHash}\nCheck: https://${explorer}/tx/${deployHash}`);
|
|
169
|
-
}
|
|
170
|
-
if (!config.json) {
|
|
171
|
-
process.stdout.write('done\n');
|
|
172
|
-
}
|
|
173
|
-
// WHY: viem's receipt.logs type is slightly wider than Log[].
|
|
174
|
-
// extractDeployedAddress only needs topics + data which are always present.
|
|
175
|
-
const deployedAddress = extractDeployedAddress(receipt.logs);
|
|
176
|
-
const registerUrl = `https://app.reserve.org/${CHAIN_SLUGS[chainId]}/index-dtf/${deployedAddress}/overview`;
|
|
177
|
-
if (config.json) {
|
|
178
|
-
printJson({
|
|
179
|
-
deployed: true,
|
|
180
|
-
dtf: deployedAddress,
|
|
181
|
-
name: deployConfig.name,
|
|
182
|
-
symbol: deployConfig.symbol,
|
|
183
|
-
chain: chainId,
|
|
184
|
-
chainLabel: chainLabel(chainId),
|
|
185
|
-
mode: ungoverned ? 'ungoverned' : 'governed',
|
|
186
|
-
amountEth: deployConfig.amount.toString(),
|
|
187
|
-
sharesOut: zapResult.amountOut,
|
|
188
|
-
priceImpact: zapResult.priceImpact,
|
|
189
|
-
gas: zapResult.gas,
|
|
190
|
-
txHash: deployHash,
|
|
191
|
-
registerUrl,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
console.log(`\n${'═'.repeat(20)} DTF Deployed ${'═'.repeat(20)}`);
|
|
196
|
-
console.log(`\n Name ${deployConfig.name}`);
|
|
197
|
-
console.log(` Symbol ${deployConfig.symbol}`);
|
|
198
|
-
console.log(` Address ${deployedAddress}`);
|
|
199
|
-
console.log(` Chain ${chainLabel(chainId)}`);
|
|
200
|
-
console.log(` Mode ${ungoverned ? 'Ungoverned (wallet-owned)' : 'Governed (Reserve DAO)'}`);
|
|
201
|
-
console.log(` Tx ${deployHash}`);
|
|
202
|
-
console.log(`\n Register ${registerUrl}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
// -- Basket validation --
|
|
206
|
-
function validateBasket(basket) {
|
|
207
|
-
if (basket.length === 0) {
|
|
208
|
-
throw new Error('Basket must have at least one token.');
|
|
209
|
-
}
|
|
210
|
-
const totalWeight = basket.reduce((sum, item) => sum + item.weight, 0);
|
|
211
|
-
if (Math.abs(totalWeight - 100) > 0.01) {
|
|
212
|
-
throw new Error(`Basket weights must sum to 100%, got ${totalWeight}%.`);
|
|
213
|
-
}
|
|
214
|
-
// Detect duplicate symbols (catches "50% WETH, 50% WETH")
|
|
215
|
-
const seen = new Set();
|
|
216
|
-
for (const item of basket) {
|
|
217
|
-
const key = item.address?.toLowerCase() ?? item.symbol.toUpperCase();
|
|
218
|
-
if (seen.has(key)) {
|
|
219
|
-
throw new Error(`Duplicate token in basket: ${item.symbol}`);
|
|
220
|
-
}
|
|
221
|
-
seen.add(key);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// -- Basket amount calculation --
|
|
225
|
-
function calculateBasketAmounts(resolvedBasket, prices, decimalMap, chainId) {
|
|
226
|
-
// WHY: Collect all missing prices at once so user sees all failures, not just the first.
|
|
227
|
-
const missing = [];
|
|
228
|
-
for (const token of resolvedBasket) {
|
|
229
|
-
const price = prices[token.address.toLowerCase()];
|
|
230
|
-
if (!price || price <= 0) {
|
|
231
|
-
missing.push(`${token.symbol} (${token.address})`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (missing.length > 0) {
|
|
235
|
-
throw new Error(`No price found for ${missing.join(', ')} on ${chainLabel(chainId)}. Cannot calculate basket amounts.`);
|
|
236
|
-
}
|
|
237
|
-
const basketAssets = [];
|
|
238
|
-
const basketAmounts = [];
|
|
239
|
-
for (const token of resolvedBasket) {
|
|
240
|
-
const price = prices[token.address.toLowerCase()];
|
|
241
|
-
const decimals = token.decimals ?? decimalMap[token.address.toLowerCase()] ?? 18;
|
|
242
|
-
// amount_i = (weight_i / 100) / price_i, scaled to token decimals
|
|
243
|
-
const amount = scaleToDecimals(token.weight / 100 / price, decimals);
|
|
244
|
-
basketAssets.push(token.address);
|
|
245
|
-
basketAmounts.push(amount.toString());
|
|
246
|
-
}
|
|
247
|
-
return { basketAssets, basketAmounts };
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Convert a floating-point value to a bigint scaled to the given decimals.
|
|
251
|
-
* Uses string manipulation to avoid IEEE 754 precision loss for amounts
|
|
252
|
-
* that would exceed Number.MAX_SAFE_INTEGER when multiplied.
|
|
253
|
-
*/
|
|
254
|
-
function scaleToDecimals(value, decimals) {
|
|
255
|
-
if (value <= 0)
|
|
256
|
-
return 0n;
|
|
257
|
-
// WHY: For small decimals (≤15), direct multiplication is safe within Number precision.
|
|
258
|
-
// For 18 decimals, the result can exceed MAX_SAFE_INTEGER. Use string-based scaling.
|
|
259
|
-
if (decimals <= 15) {
|
|
260
|
-
return BigInt(Math.floor(value * 10 ** decimals));
|
|
261
|
-
}
|
|
262
|
-
// Split into integer and fractional parts via string to preserve precision
|
|
263
|
-
const str = value.toFixed(20); // enough digits for 18 decimals
|
|
264
|
-
const [intPart, fracPart = ''] = str.split('.');
|
|
265
|
-
const padded = (fracPart + '0'.repeat(decimals)).slice(0, decimals);
|
|
266
|
-
const combined = intPart + padded;
|
|
267
|
-
// WHY: Remove leading zeros so BigInt doesn't interpret as octal, but keep at least '0'
|
|
268
|
-
return BigInt(combined.replace(/^0+/, '') || '0');
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Parse ETH amount string to wei without floating-point precision loss.
|
|
272
|
-
* Handles up to 18 decimal places.
|
|
273
|
-
*/
|
|
274
|
-
function parseEthToWei(amount) {
|
|
275
|
-
const str = amount.toFixed(18);
|
|
276
|
-
const [intPart, fracPart = ''] = str.split('.');
|
|
277
|
-
const padded = (fracPart + '0'.repeat(18)).slice(0, 18);
|
|
278
|
-
return BigInt(intPart + padded);
|
|
279
|
-
}
|
|
280
|
-
// -- Basket parsing (3 formats) --
|
|
281
|
-
function parseDeployConfig(args) {
|
|
282
|
-
const configFile = getFlag(args, '--config');
|
|
283
|
-
if (configFile) {
|
|
284
|
-
return parseJsonConfig(configFile, args);
|
|
285
|
-
}
|
|
286
|
-
const name = getFlag(args, '--name');
|
|
287
|
-
const symbol = getFlag(args, '--symbol');
|
|
288
|
-
const basketRaw = getFlag(args, '--basket');
|
|
289
|
-
const amountStr = getFlag(args, '--amount');
|
|
290
|
-
const mandate = getFlag(args, '--mandate') ?? '';
|
|
291
|
-
const ungoverned = args.includes('--ungoverned');
|
|
292
|
-
if (!name || !symbol || !basketRaw || !amountStr) {
|
|
293
|
-
console.log(DEPLOY_HELP);
|
|
294
|
-
throw new Error('Missing required flags: --name, --symbol, --basket, --amount');
|
|
295
|
-
}
|
|
296
|
-
const amount = Number(amountStr);
|
|
297
|
-
if (isNaN(amount) || amount <= 0) {
|
|
298
|
-
throw new Error(`Invalid --amount: "${amountStr}". Must be a positive number (ETH).`);
|
|
299
|
-
}
|
|
300
|
-
// Auto-detect CSV by extension
|
|
301
|
-
const basket = basketRaw.endsWith('.csv')
|
|
302
|
-
? parseBasketCsv(basketRaw)
|
|
303
|
-
: parseBasketString(basketRaw);
|
|
304
|
-
return { name, symbol, mandate, basket, amount, ungoverned };
|
|
305
|
-
}
|
|
306
|
-
function parseJsonConfig(filePath, args) {
|
|
307
|
-
if (!existsSync(filePath)) {
|
|
308
|
-
throw new Error(`Config file not found: ${filePath}`);
|
|
309
|
-
}
|
|
310
|
-
const raw = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
311
|
-
const ungoverned = args.includes('--ungoverned');
|
|
312
|
-
if (!raw.name || typeof raw.name !== 'string') {
|
|
313
|
-
throw new Error('JSON config: "name" must be a non-empty string');
|
|
314
|
-
}
|
|
315
|
-
if (!raw.symbol || typeof raw.symbol !== 'string') {
|
|
316
|
-
throw new Error('JSON config: "symbol" must be a non-empty string');
|
|
317
|
-
}
|
|
318
|
-
if (!Array.isArray(raw.basket) || raw.basket.length === 0) {
|
|
319
|
-
throw new Error('JSON config: "basket" must be a non-empty array');
|
|
320
|
-
}
|
|
321
|
-
if (typeof raw.amount !== 'number' || raw.amount <= 0) {
|
|
322
|
-
throw new Error('JSON config: "amount" must be a positive number');
|
|
323
|
-
}
|
|
324
|
-
const basket = raw.basket.map((item, i) => {
|
|
325
|
-
if (!item || typeof item !== 'object') {
|
|
326
|
-
throw new Error(`JSON config: basket[${i}] must be an object`);
|
|
327
|
-
}
|
|
328
|
-
const obj = item;
|
|
329
|
-
if (typeof obj.symbol !== 'string') {
|
|
330
|
-
throw new Error(`JSON config: basket[${i}].symbol must be a string`);
|
|
331
|
-
}
|
|
332
|
-
if (typeof obj.weight !== 'number' || obj.weight <= 0) {
|
|
333
|
-
throw new Error(`JSON config: basket[${i}].weight must be a positive number`);
|
|
334
|
-
}
|
|
335
|
-
let address;
|
|
336
|
-
if (typeof obj.address === 'string') {
|
|
337
|
-
if (!isAddress(obj.address)) {
|
|
338
|
-
throw new Error(`JSON config: basket[${i}].address "${obj.address}" is not a valid Ethereum address`);
|
|
339
|
-
}
|
|
340
|
-
address = obj.address;
|
|
341
|
-
}
|
|
342
|
-
return {
|
|
343
|
-
symbol: obj.symbol,
|
|
344
|
-
address,
|
|
345
|
-
weight: obj.weight,
|
|
346
|
-
};
|
|
347
|
-
});
|
|
348
|
-
return {
|
|
349
|
-
name: raw.name,
|
|
350
|
-
symbol: raw.symbol,
|
|
351
|
-
mandate: typeof raw.mandate === 'string' ? raw.mandate : '',
|
|
352
|
-
basket,
|
|
353
|
-
amount: raw.amount,
|
|
354
|
-
ungoverned,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
function parseBasketString(input) {
|
|
358
|
-
// Format: "50% WETH, 30% USDC, 20% cbBTC"
|
|
359
|
-
const items = input.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
360
|
-
const basket = [];
|
|
361
|
-
for (const item of items) {
|
|
362
|
-
const match = item.match(/^(\d+(?:\.\d+)?)\s*%\s+(\S+)$/);
|
|
363
|
-
if (!match) {
|
|
364
|
-
throw new Error(`Invalid basket item: "${item}". Expected format: "50% WETH" (percentage followed by symbol or 0x address)`);
|
|
365
|
-
}
|
|
366
|
-
const weight = Number(match[1]);
|
|
367
|
-
const symbolOrAddr = match[2];
|
|
368
|
-
// WHY: A token like "0xWETH" starts with 0x but isn't a valid address.
|
|
369
|
-
// Only treat as address if it passes full validation.
|
|
370
|
-
const isAddr = symbolOrAddr.startsWith('0x') && isAddress(symbolOrAddr);
|
|
371
|
-
basket.push({
|
|
372
|
-
symbol: isAddr ? symbolOrAddr : symbolOrAddr,
|
|
373
|
-
address: isAddr ? symbolOrAddr : undefined,
|
|
374
|
-
weight,
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
return basket;
|
|
378
|
-
}
|
|
379
|
-
function parseBasketCsv(filePath) {
|
|
380
|
-
if (!existsSync(filePath)) {
|
|
381
|
-
throw new Error(`CSV file not found: ${filePath}`);
|
|
382
|
-
}
|
|
383
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
384
|
-
const lines = content
|
|
385
|
-
.split('\n')
|
|
386
|
-
.map((l) => l.trim())
|
|
387
|
-
.filter((l) => l.length > 0);
|
|
388
|
-
if (lines.length < 2) {
|
|
389
|
-
throw new Error('CSV file must have a header row and at least one data row');
|
|
390
|
-
}
|
|
391
|
-
// Skip header row
|
|
392
|
-
const basket = [];
|
|
393
|
-
for (let i = 1; i < lines.length; i++) {
|
|
394
|
-
const parts = lines[i].split(',').map((p) => p.trim());
|
|
395
|
-
if (parts.length < 3) {
|
|
396
|
-
throw new Error(`Invalid CSV row ${i + 1}: expected 'symbol,address,weight' format`);
|
|
397
|
-
}
|
|
398
|
-
const symbol = parts[0];
|
|
399
|
-
const address = parts[1];
|
|
400
|
-
const weight = Number(parts[2]);
|
|
401
|
-
if (!isAddress(address)) {
|
|
402
|
-
throw new Error(`Invalid CSV row ${i + 1}: "${address}" is not a valid Ethereum address`);
|
|
403
|
-
}
|
|
404
|
-
if (address === '0x0000000000000000000000000000000000000000') {
|
|
405
|
-
throw new Error(`Invalid CSV row ${i + 1}: zero address is not a valid token`);
|
|
406
|
-
}
|
|
407
|
-
if (isNaN(weight) || weight <= 0) {
|
|
408
|
-
throw new Error(`Invalid CSV row ${i + 1}: weight "${parts[2]}" must be a positive number`);
|
|
409
|
-
}
|
|
410
|
-
basket.push({ symbol, address: address, weight });
|
|
411
|
-
}
|
|
412
|
-
return basket;
|
|
413
|
-
}
|
|
414
|
-
// -- Token resolution --
|
|
415
|
-
function resolveBasketAddresses(basket, chainId) {
|
|
416
|
-
return basket.map((item) => {
|
|
417
|
-
if (item.address) {
|
|
418
|
-
return { ...item, address: item.address };
|
|
419
|
-
}
|
|
420
|
-
const entry = resolveToken(chainId, item.symbol);
|
|
421
|
-
if (!entry) {
|
|
422
|
-
throw new Error(`Unknown token '${item.symbol}' on ${chainLabel(chainId)}. ` +
|
|
423
|
-
`Use 'dtf deploy --list-tokens' to see available symbols, or use a raw 0x address.`);
|
|
424
|
-
}
|
|
425
|
-
return { ...item, address: entry.address, decimals: entry.decimals };
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
// -- Output helpers --
|
|
429
|
-
function outputDryRun(config, deployConfig, chainId, ungoverned, resolvedBasket, basketAmounts, prices, zapParams) {
|
|
430
|
-
if (config.json) {
|
|
431
|
-
printJson({
|
|
432
|
-
dryRun: true,
|
|
433
|
-
name: deployConfig.name,
|
|
434
|
-
symbol: deployConfig.symbol,
|
|
435
|
-
chain: chainId,
|
|
436
|
-
chainLabel: chainLabel(chainId),
|
|
437
|
-
mode: ungoverned ? 'ungoverned' : 'governed',
|
|
438
|
-
amountEth: deployConfig.amount.toString(),
|
|
439
|
-
basket: resolvedBasket.map((t, i) => ({
|
|
440
|
-
symbol: t.symbol,
|
|
441
|
-
address: t.address,
|
|
442
|
-
weight: t.weight,
|
|
443
|
-
amount: basketAmounts[i],
|
|
444
|
-
priceUsd: prices[t.address.toLowerCase()] ?? null,
|
|
445
|
-
})),
|
|
446
|
-
zapParams,
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
printHeader('Dry Run — Deploy Preview');
|
|
451
|
-
console.log(`\n Name ${deployConfig.name}`);
|
|
452
|
-
console.log(` Symbol ${deployConfig.symbol}`);
|
|
453
|
-
console.log(` Chain ${chainLabel(chainId)}`);
|
|
454
|
-
console.log(` Mode ${ungoverned ? 'Ungoverned (wallet-owned)' : 'Governed (Reserve DAO)'}`);
|
|
455
|
-
console.log(` Amount ${deployConfig.amount} ETH`);
|
|
456
|
-
console.log(` Basket:`);
|
|
457
|
-
for (let i = 0; i < resolvedBasket.length; i++) {
|
|
458
|
-
const t = resolvedBasket[i];
|
|
459
|
-
const price = prices[t.address.toLowerCase()];
|
|
460
|
-
console.log(` ${t.weight}% ${t.symbol} (${t.address})` +
|
|
461
|
-
(price ? ` @ $${price}` : ''));
|
|
462
|
-
}
|
|
463
|
-
console.log('\n No transaction sent (--dry-run)');
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
function listTokens(config) {
|
|
467
|
-
const registry = getTokenRegistry(config.chainId);
|
|
468
|
-
const entries = Object.entries(registry);
|
|
469
|
-
if (config.json) {
|
|
470
|
-
printJson(entries.map(([symbol, entry]) => ({
|
|
471
|
-
symbol,
|
|
472
|
-
address: entry.address,
|
|
473
|
-
decimals: entry.decimals,
|
|
474
|
-
})));
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
printHeader(`Available Tokens — ${chainLabel(config.chainId)}`);
|
|
478
|
-
console.log();
|
|
479
|
-
printTable([
|
|
480
|
-
{ header: 'Symbol', key: 'symbol' },
|
|
481
|
-
{ header: 'Address', key: 'address' },
|
|
482
|
-
{ header: 'Decimals', key: 'decimals', align: 'right' },
|
|
483
|
-
], entries.map(([symbol, entry]) => ({
|
|
484
|
-
symbol,
|
|
485
|
-
address: entry.address,
|
|
486
|
-
decimals: String(entry.decimals),
|
|
487
|
-
})));
|
|
488
|
-
}
|
|
489
|
-
function getFlag(args, flag) {
|
|
490
|
-
const idx = args.indexOf(flag);
|
|
491
|
-
if (idx === -1 || idx + 1 >= args.length)
|
|
492
|
-
return undefined;
|
|
493
|
-
return args[idx + 1];
|
|
494
|
-
}
|
|
495
|
-
//# sourceMappingURL=deploy.js.map
|