@continuumdao/ctm-mpc-defi 0.2.56 → 0.2.58
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/agent/catalog.cjs +749 -171
- package/dist/agent/catalog.cjs.map +1 -1
- package/dist/agent/catalog.d.ts +373 -44
- package/dist/agent/catalog.js +738 -173
- package/dist/agent/catalog.js.map +1 -1
- package/dist/agent/skills/aave-v4/SKILL.md +26 -3
- package/dist/agent/skills/aerodrome/SKILL.md +3 -1
- package/dist/agent/skills/circle-cctp/SKILL.md +1 -1
- package/dist/agent/skills/curve-dao/SKILL.md +2 -2
- package/dist/agent/skills/railgun/SKILL.md +39 -0
- package/dist/agent/skills/uniswap-v4/SKILL.md +2 -2
- package/dist/index.cjs +165 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +166 -20
- package/dist/index.js.map +1 -1
- package/dist/protocols/evm/aave-v4/index.cjs +198 -15
- package/dist/protocols/evm/aave-v4/index.cjs.map +1 -1
- package/dist/protocols/evm/aave-v4/index.d.ts +62 -7
- package/dist/protocols/evm/aave-v4/index.js +189 -16
- package/dist/protocols/evm/aave-v4/index.js.map +1 -1
- package/dist/protocols/evm/aerodrome/index.cjs +152 -63
- package/dist/protocols/evm/aerodrome/index.cjs.map +1 -1
- package/dist/protocols/evm/aerodrome/index.d.ts +30 -58
- package/dist/protocols/evm/aerodrome/index.js +151 -64
- package/dist/protocols/evm/aerodrome/index.js.map +1 -1
- package/dist/protocols/evm/circle-cctp/index.cjs +5 -3
- package/dist/protocols/evm/circle-cctp/index.cjs.map +1 -1
- package/dist/protocols/evm/circle-cctp/index.d.ts +3 -1
- package/dist/protocols/evm/circle-cctp/index.js +5 -4
- package/dist/protocols/evm/circle-cctp/index.js.map +1 -1
- package/dist/protocols/evm/compound-v3/index.cjs +115 -2
- package/dist/protocols/evm/compound-v3/index.cjs.map +1 -1
- package/dist/protocols/evm/compound-v3/index.js +116 -3
- package/dist/protocols/evm/compound-v3/index.js.map +1 -1
- package/dist/protocols/evm/continuum-dao/index.cjs +1 -1
- package/dist/protocols/evm/continuum-dao/index.cjs.map +1 -1
- package/dist/protocols/evm/continuum-dao/index.d.ts +1 -1
- package/dist/protocols/evm/continuum-dao/index.js +1 -1
- package/dist/protocols/evm/continuum-dao/index.js.map +1 -1
- package/dist/protocols/evm/curve-dao/index.cjs +120 -5
- package/dist/protocols/evm/curve-dao/index.cjs.map +1 -1
- package/dist/protocols/evm/curve-dao/index.js +120 -5
- package/dist/protocols/evm/curve-dao/index.js.map +1 -1
- package/dist/protocols/evm/euler-v2/index.cjs +121 -6
- package/dist/protocols/evm/euler-v2/index.cjs.map +1 -1
- package/dist/protocols/evm/euler-v2/index.js +121 -6
- package/dist/protocols/evm/euler-v2/index.js.map +1 -1
- package/dist/protocols/evm/morpho/index.cjs +129 -57
- package/dist/protocols/evm/morpho/index.cjs.map +1 -1
- package/dist/protocols/evm/morpho/index.d.ts +2 -1
- package/dist/protocols/evm/morpho/index.js +129 -58
- package/dist/protocols/evm/morpho/index.js.map +1 -1
- package/dist/protocols/evm/pendle/index.cjs +129 -10
- package/dist/protocols/evm/pendle/index.cjs.map +1 -1
- package/dist/protocols/evm/pendle/index.js +129 -10
- package/dist/protocols/evm/pendle/index.js.map +1 -1
- package/dist/protocols/evm/railgun/index.cjs +1261 -0
- package/dist/protocols/evm/railgun/index.cjs.map +1 -0
- package/dist/protocols/evm/railgun/index.d.ts +294 -0
- package/dist/protocols/evm/railgun/index.js +1218 -0
- package/dist/protocols/evm/railgun/index.js.map +1 -0
- package/dist/protocols/evm/uniswap-v4/index.cjs +164 -17
- package/dist/protocols/evm/uniswap-v4/index.cjs.map +1 -1
- package/dist/protocols/evm/uniswap-v4/index.js +165 -18
- package/dist/protocols/evm/uniswap-v4/index.js.map +1 -1
- package/package.json +17 -1
|
@@ -0,0 +1,1218 @@
|
|
|
1
|
+
import { getAddress, parseAbi, zeroAddress, encodeFunctionData, defineChain, createPublicClient, parseGwei, serializeTransaction, keccak256, hashMessage, stringToHex, http } from 'viem';
|
|
2
|
+
import { fetchChainFeeParams, gasLimitFromEstimateAndChainConfig, gweiToDecimalString, proposalTxParamsToFeeSnapshot, alignEip1559FeesWithLatestBase, getClientIdFromKeyGenResult } from '@continuumdao/continuum-node-sdk';
|
|
3
|
+
import { TXIDVersion, calculateGasPrice, ChainType, EVMGasType, RailgunWalletBalanceBucket } from '@railgun-community/shared-models';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
import { join, dirname } from 'path';
|
|
6
|
+
|
|
7
|
+
// src/core/registry.ts
|
|
8
|
+
var modules = [];
|
|
9
|
+
function registerProtocolModule(mod) {
|
|
10
|
+
const existing = modules.findIndex((m) => m.id === mod.id);
|
|
11
|
+
if (existing >= 0) {
|
|
12
|
+
modules[existing] = mod;
|
|
13
|
+
} else {
|
|
14
|
+
modules.push(mod);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/protocols/evm/railgun/networks.ts
|
|
19
|
+
function selectRailgunNetworks(config) {
|
|
20
|
+
const rows = [];
|
|
21
|
+
for (const entry of Object.values(config)) {
|
|
22
|
+
if (!entry || entry.deprecated || entry.isDevOnlyNetwork) continue;
|
|
23
|
+
const chainId = Number(entry.chain?.id);
|
|
24
|
+
const proxyContract = String(entry.proxyContract ?? "").trim();
|
|
25
|
+
const wrappedAddress = String(entry.baseToken?.wrappedAddress ?? "").trim();
|
|
26
|
+
if (!Number.isFinite(chainId) || chainId <= 0 || !proxyContract || !wrappedAddress) continue;
|
|
27
|
+
rows.push({
|
|
28
|
+
chainId,
|
|
29
|
+
networkName: String(entry.name ?? ""),
|
|
30
|
+
publicName: String(entry.publicName ?? entry.name ?? ""),
|
|
31
|
+
proxyContract,
|
|
32
|
+
deploymentBlock: Number(entry.deploymentBlock ?? 0),
|
|
33
|
+
wrappedAddress,
|
|
34
|
+
wrappedSymbol: String(entry.baseToken?.wrappedSymbol ?? entry.baseToken?.symbol ?? ""),
|
|
35
|
+
wrappedDecimals: Number(entry.baseToken?.decimals ?? 18),
|
|
36
|
+
isTestnet: Boolean(entry.isTestnet)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
rows.sort((a, b) => a.chainId - b.chainId);
|
|
40
|
+
return rows;
|
|
41
|
+
}
|
|
42
|
+
var cached = null;
|
|
43
|
+
async function loadRailgunNetworks() {
|
|
44
|
+
if (cached) return cached;
|
|
45
|
+
const mod = await import('@railgun-community/shared-models');
|
|
46
|
+
cached = selectRailgunNetworks(mod.NETWORK_CONFIG);
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
async function loadRailgunSupportedChainIds() {
|
|
50
|
+
const rows = await loadRailgunNetworks();
|
|
51
|
+
return new Set(rows.map((row) => row.chainId));
|
|
52
|
+
}
|
|
53
|
+
async function railgunNetworkByChainId(chainId) {
|
|
54
|
+
const rows = await loadRailgunNetworks();
|
|
55
|
+
return rows.find((row) => row.chainId === chainId);
|
|
56
|
+
}
|
|
57
|
+
function clearRailgunNetworkCache() {
|
|
58
|
+
cached = null;
|
|
59
|
+
}
|
|
60
|
+
var CONTINUUM_DAO_ETHEREUM_CHAIN_ID = 1;
|
|
61
|
+
var CONTINUUM_DAO_LINEA_CHAIN_ID = 59144;
|
|
62
|
+
var CONTINUUM_DAO_LINEA_SEPOLIA_CHAIN_ID = 59141;
|
|
63
|
+
var UNSET = {
|
|
64
|
+
ctm: zeroAddress,
|
|
65
|
+
votingEscrow: zeroAddress,
|
|
66
|
+
nodeProperties: zeroAddress,
|
|
67
|
+
rewards: zeroAddress,
|
|
68
|
+
governor: zeroAddress
|
|
69
|
+
};
|
|
70
|
+
var CTM_TOKEN_ADDRESS = getAddress("0x7581696b0ED142f6534E5797baaABBb9a1b27086");
|
|
71
|
+
var VOTING_ESCROW_LINEA_ADDRESS = getAddress("0x221EC90B3B083A8501A37bdeb7035CeaedF3C31f");
|
|
72
|
+
getAddress("0x4ebd7cCBe32a51d1638cFE90E8eD1c4BdF42f729");
|
|
73
|
+
getAddress("0x92557d38beAe4658d3c03A02F349FFB60c3bfFA5");
|
|
74
|
+
getAddress("0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f");
|
|
75
|
+
getAddress("0x176211869cA2b568f2A7D4EE941E073a821EE1ff");
|
|
76
|
+
var CONTINUUM_DAO_LINEA_GOVERNOR_ADDRESS = getAddress("0x76FF2CB03175900F1D83328C82D27EA9aeaF2355");
|
|
77
|
+
getAddress("0x4800F9f1dC1b6daCA841B71E0531F547D374168E");
|
|
78
|
+
var CONTINUUM_DAO_DEPLOYMENTS = {
|
|
79
|
+
[CONTINUUM_DAO_LINEA_CHAIN_ID]: {
|
|
80
|
+
...UNSET,
|
|
81
|
+
ctm: CTM_TOKEN_ADDRESS,
|
|
82
|
+
votingEscrow: VOTING_ESCROW_LINEA_ADDRESS,
|
|
83
|
+
governor: CONTINUUM_DAO_LINEA_GOVERNOR_ADDRESS
|
|
84
|
+
},
|
|
85
|
+
[CONTINUUM_DAO_LINEA_SEPOLIA_CHAIN_ID]: { ...UNSET },
|
|
86
|
+
[CONTINUUM_DAO_ETHEREUM_CHAIN_ID]: {
|
|
87
|
+
...UNSET,
|
|
88
|
+
ctm: CTM_TOKEN_ADDRESS
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
function isConfiguredAddress(addr) {
|
|
92
|
+
if (!addr?.trim()) return false;
|
|
93
|
+
try {
|
|
94
|
+
return getAddress(addr) !== zeroAddress;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function continuumDaoDeployment(chainId) {
|
|
100
|
+
return CONTINUUM_DAO_DEPLOYMENTS[chainId] ?? null;
|
|
101
|
+
}
|
|
102
|
+
function ctmTokenAddressOnEvmChain(chainId) {
|
|
103
|
+
const ctm = continuumDaoDeployment(chainId)?.ctm;
|
|
104
|
+
return ctm && isConfiguredAddress(ctm) ? getAddress(ctm) : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/protocols/evm/railgun/popular-assets.ts
|
|
108
|
+
var RAILGUN_POPULAR_ASSET_LIMIT = 5;
|
|
109
|
+
var RAILGUN_CTM_ENABLED = false;
|
|
110
|
+
var RAILGUN_CTM_DISABLED_MESSAGE = "Railgun support for CTM is off until a broadcaster quotes CTM.";
|
|
111
|
+
var POPULAR_ASSET_CACHE_MS = 10 * 60 * 1e3;
|
|
112
|
+
var RECENT_SHIELD_LIMIT = 200;
|
|
113
|
+
var SHIELD_INDEX_BY_NETWORK_NAME = {
|
|
114
|
+
Ethereum: "https://rail-squid.squids.live/squid-railgun-ethereum-v2/graphql",
|
|
115
|
+
BNB_Chain: "https://rail-squid.squids.live/squid-railgun-bsc-v2/graphql",
|
|
116
|
+
Polygon: "https://rail-squid.squids.live/squid-railgun-polygon-v2/graphql",
|
|
117
|
+
Arbitrum: "https://rail-squid.squids.live/squid-railgun-arbitrum-v2/graphql"
|
|
118
|
+
};
|
|
119
|
+
var RECENT_SHIELDS_QUERY = `query RecentShields($limit: Int!) {
|
|
120
|
+
shieldCommitments(orderBy: [blockNumber_DESC], limit: $limit) {
|
|
121
|
+
preimage { token { tokenType tokenAddress } }
|
|
122
|
+
}
|
|
123
|
+
}`;
|
|
124
|
+
var cache = /* @__PURE__ */ new Map();
|
|
125
|
+
function rankPopularShieldTokens(rows, limit = RAILGUN_POPULAR_ASSET_LIMIT) {
|
|
126
|
+
const counts = /* @__PURE__ */ new Map();
|
|
127
|
+
for (const row of rows) {
|
|
128
|
+
if (Number(row.tokenType) !== 0) continue;
|
|
129
|
+
let address;
|
|
130
|
+
try {
|
|
131
|
+
address = getAddress(row.tokenAddress);
|
|
132
|
+
} catch {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (address === zeroAddress) continue;
|
|
136
|
+
const key = address.toLowerCase();
|
|
137
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
138
|
+
}
|
|
139
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)).slice(0, limit).map(([address]) => getAddress(address));
|
|
140
|
+
}
|
|
141
|
+
function shieldIndexEndpoint(networkName) {
|
|
142
|
+
return SHIELD_INDEX_BY_NETWORK_NAME[networkName] ?? null;
|
|
143
|
+
}
|
|
144
|
+
function commitmentsFromShieldIndex(payload) {
|
|
145
|
+
const rows = payload?.data?.shieldCommitments;
|
|
146
|
+
if (!Array.isArray(rows)) return [];
|
|
147
|
+
return rows.map((row) => {
|
|
148
|
+
const token = row?.preimage?.token;
|
|
149
|
+
const tokenType = token?.tokenType === "ERC20" || token?.tokenType === 0 ? 0 : Number(token?.tokenType ?? -1);
|
|
150
|
+
return {
|
|
151
|
+
tokenAddress: String(token?.tokenAddress ?? ""),
|
|
152
|
+
tokenType: Number.isFinite(tokenType) ? tokenType : -1
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
var shieldLogReaderOverride = null;
|
|
157
|
+
function setRailgunShieldLogReaderForTests(fn) {
|
|
158
|
+
shieldLogReaderOverride = fn;
|
|
159
|
+
}
|
|
160
|
+
async function readShieldCommitments(args) {
|
|
161
|
+
const endpoint = shieldIndexEndpoint(args.networkName);
|
|
162
|
+
if (!endpoint) return [];
|
|
163
|
+
const response = await fetch(endpoint, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: { "content-type": "application/json" },
|
|
166
|
+
body: JSON.stringify({
|
|
167
|
+
query: RECENT_SHIELDS_QUERY,
|
|
168
|
+
variables: { limit: RECENT_SHIELD_LIMIT }
|
|
169
|
+
})
|
|
170
|
+
});
|
|
171
|
+
if (!response.ok) throw new Error(`Railgun shield index returned ${response.status}`);
|
|
172
|
+
return commitmentsFromShieldIndex(await response.json());
|
|
173
|
+
}
|
|
174
|
+
function assertRailgunCtmAllowed(chainId, tokenAddress) {
|
|
175
|
+
const ctm = ctmTokenAddressOnEvmChain(chainId);
|
|
176
|
+
if (!ctm) return;
|
|
177
|
+
let normalized;
|
|
178
|
+
try {
|
|
179
|
+
normalized = getAddress(tokenAddress);
|
|
180
|
+
} catch {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (normalized.toLowerCase() === ctm.toLowerCase()) throw new Error(RAILGUN_CTM_DISABLED_MESSAGE);
|
|
184
|
+
}
|
|
185
|
+
function applyContinuumToken(chainId, result) {
|
|
186
|
+
const ctm = ctmTokenAddressOnEvmChain(chainId);
|
|
187
|
+
if (!ctm) return result;
|
|
188
|
+
const present = result.tokens.some((token) => token.address.toLowerCase() === ctm.toLowerCase());
|
|
189
|
+
{
|
|
190
|
+
if (!present) return result;
|
|
191
|
+
return {
|
|
192
|
+
...result,
|
|
193
|
+
tokens: result.tokens.filter((token) => token.address.toLowerCase() !== ctm.toLowerCase())
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function loadRailgunPopularAssets(args) {
|
|
198
|
+
const network = await railgunNetworkByChainId(args.chainId);
|
|
199
|
+
if (!network) {
|
|
200
|
+
return { tokens: [], notes: "Railgun is not deployed on this chain." };
|
|
201
|
+
}
|
|
202
|
+
const cached2 = cache.get(args.chainId);
|
|
203
|
+
if (cached2 && Date.now() - cached2.at < POPULAR_ASSET_CACHE_MS) {
|
|
204
|
+
return applyContinuumToken(args.chainId, {
|
|
205
|
+
tokens: cached2.tokens,
|
|
206
|
+
nativeWrapped: cached2.nativeWrapped,
|
|
207
|
+
notes: cached2.notes
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const rpcUrl = args.rpcUrl?.trim() ?? "";
|
|
211
|
+
if (!shieldIndexEndpoint(network.networkName) && !shieldLogReaderOverride) {
|
|
212
|
+
return applyContinuumToken(args.chainId, {
|
|
213
|
+
tokens: [],
|
|
214
|
+
nativeWrapped: network.wrappedAddress,
|
|
215
|
+
notes: "This chain has no indexed Railgun shield feed."
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const commitments = await (shieldLogReaderOverride ?? readShieldCommitments)({
|
|
220
|
+
rpcUrl,
|
|
221
|
+
chainId: args.chainId,
|
|
222
|
+
proxy: getAddress(network.proxyContract),
|
|
223
|
+
deploymentBlock: network.deploymentBlock,
|
|
224
|
+
networkName: network.networkName
|
|
225
|
+
});
|
|
226
|
+
const tokens = rankPopularShieldTokens(commitments).map((address) => ({ address }));
|
|
227
|
+
const notes = tokens.length === 0 ? "No recent ERC-20 shields were returned for this chain." : `Top ${tokens.length} ERC-20s by recent indexed shield count.`;
|
|
228
|
+
const entry = {
|
|
229
|
+
at: Date.now(),
|
|
230
|
+
tokens,
|
|
231
|
+
nativeWrapped: network.wrappedAddress,
|
|
232
|
+
notes
|
|
233
|
+
};
|
|
234
|
+
cache.set(args.chainId, entry);
|
|
235
|
+
return applyContinuumToken(args.chainId, { tokens, nativeWrapped: network.wrappedAddress, notes });
|
|
236
|
+
} catch {
|
|
237
|
+
return applyContinuumToken(args.chainId, {
|
|
238
|
+
tokens: [],
|
|
239
|
+
nativeWrapped: network.wrappedAddress,
|
|
240
|
+
notes: "The Railgun shield index could not be read."
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function clearRailgunPopularAssetCache() {
|
|
245
|
+
cache.clear();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/protocols/evm/railgun/snapshot.ts
|
|
249
|
+
var byChain = /* @__PURE__ */ new Map();
|
|
250
|
+
function publishRailgunPrivateBalanceSnapshot(snapshot) {
|
|
251
|
+
byChain.set(snapshot.chainId, snapshot);
|
|
252
|
+
}
|
|
253
|
+
function readRailgunPrivateBalanceSnapshot(chainId) {
|
|
254
|
+
return byChain.get(chainId);
|
|
255
|
+
}
|
|
256
|
+
function clearRailgunPrivateBalanceSnapshots() {
|
|
257
|
+
byChain.clear();
|
|
258
|
+
}
|
|
259
|
+
function railgunFetchPrivateBalances(args) {
|
|
260
|
+
const row = readRailgunPrivateBalanceSnapshot(args.chainId);
|
|
261
|
+
if (!row || row.scanStatus === "scanning") {
|
|
262
|
+
return {
|
|
263
|
+
chainId: args.chainId,
|
|
264
|
+
scanned: false,
|
|
265
|
+
scanStatus: row?.scanStatus ?? "unscanned",
|
|
266
|
+
balances: [],
|
|
267
|
+
notes: "No Railgun scan is stored for this chain yet."
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
chainId: args.chainId,
|
|
272
|
+
scanned: true,
|
|
273
|
+
scanStatus: row.scanStatus,
|
|
274
|
+
balances: row.balances,
|
|
275
|
+
notes: row.notes ?? (row.balances.length === 0 ? "No private balance on this chain yet." : "Private balances from the latest Railgun wallet scan.")
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/protocols/evm/railgun/session.ts
|
|
280
|
+
var RAILGUN_WALLET_LOCKED_MESSAGE = "Railgun wallet is not stored on this KeyGen.";
|
|
281
|
+
var session = null;
|
|
282
|
+
function setRailgunWalletSession(next) {
|
|
283
|
+
session = next;
|
|
284
|
+
}
|
|
285
|
+
function getRailgunWalletSession() {
|
|
286
|
+
return session;
|
|
287
|
+
}
|
|
288
|
+
function requireRailgunWalletSession() {
|
|
289
|
+
if (!session) throw new Error(RAILGUN_WALLET_LOCKED_MESSAGE);
|
|
290
|
+
return session;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/protocols/evm/railgun/reads.ts
|
|
294
|
+
async function railgunFetchPopularAssets(args) {
|
|
295
|
+
const result = await loadRailgunPopularAssets(args);
|
|
296
|
+
return {
|
|
297
|
+
chainId: args.chainId,
|
|
298
|
+
tokens: result.tokens,
|
|
299
|
+
nativeWrapped: result.nativeWrapped,
|
|
300
|
+
notes: result.notes ?? ""
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function railgunFetchPrivateBalancesSummary(args) {
|
|
304
|
+
return railgunFetchPrivateBalances(args);
|
|
305
|
+
}
|
|
306
|
+
function defiEvmTransport(rpcUrl, opts) {
|
|
307
|
+
const url = rpcUrl.trim();
|
|
308
|
+
return http(url, opts);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/core/purpose.ts
|
|
312
|
+
function mergePurposeText(purposeText, purposeSuffix) {
|
|
313
|
+
const t = (purposeText ?? "").trim();
|
|
314
|
+
const suffix = (purposeSuffix ?? "").trim();
|
|
315
|
+
if (!suffix) return t;
|
|
316
|
+
return t ? `${t}
|
|
317
|
+
|
|
318
|
+
${suffix}` : suffix;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/core/envelope.ts
|
|
322
|
+
function finalizeMultisign(input) {
|
|
323
|
+
const { keyGen, destinationChainID, legs } = input;
|
|
324
|
+
if (legs.length === 0) {
|
|
325
|
+
throw new Error("finalizeMultisign requires at least one leg");
|
|
326
|
+
}
|
|
327
|
+
const ph = (keyGen.pubkeyhex ?? "").trim();
|
|
328
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
329
|
+
const keyList = keyGen.keylist ?? [];
|
|
330
|
+
const clientId = getClientIdFromKeyGenResult(keyGen);
|
|
331
|
+
const first = legs[0];
|
|
332
|
+
const messageHashes = legs.map((l) => l.msgHash);
|
|
333
|
+
const messageRawBatch = legs.map((l) => l.msgRaw);
|
|
334
|
+
const batchMeta = legs.map((l) => ({
|
|
335
|
+
destinationAddress: l.destinationAddress,
|
|
336
|
+
signatureText: l.signatureText,
|
|
337
|
+
...l.audit
|
|
338
|
+
}));
|
|
339
|
+
const proposalTxParams = legs.map((l) => l.proposalTxParams).filter((p) => p != null && typeof p === "object");
|
|
340
|
+
const extraPayload = {
|
|
341
|
+
batchMeta,
|
|
342
|
+
...input.extraJSON ?? {}
|
|
343
|
+
};
|
|
344
|
+
const extraJSON = JSON.stringify(extraPayload, (_, v) => typeof v === "bigint" ? v.toString() : v);
|
|
345
|
+
const bodyForSign = {
|
|
346
|
+
keyList,
|
|
347
|
+
pubKey: ph,
|
|
348
|
+
msgHash: messageHashes[0],
|
|
349
|
+
msgRaw: first.msgRaw,
|
|
350
|
+
destinationChainID,
|
|
351
|
+
destinationAddress: input.destinationAddress ?? first.destinationAddress,
|
|
352
|
+
extraJSON,
|
|
353
|
+
signatureText: first.signatureText,
|
|
354
|
+
purpose: mergePurposeText(input.purposeText, input.purposeSuffix),
|
|
355
|
+
...first.feeSnapshot
|
|
356
|
+
};
|
|
357
|
+
if (legs.length > 1) {
|
|
358
|
+
bodyForSign.messageHashes = messageHashes;
|
|
359
|
+
bodyForSign.messageRawBatch = messageRawBatch;
|
|
360
|
+
}
|
|
361
|
+
if (proposalTxParams.length > 0) {
|
|
362
|
+
bodyForSign.proposalTxParams = proposalTxParams;
|
|
363
|
+
}
|
|
364
|
+
const valueWei = first.valueWei;
|
|
365
|
+
if (valueWei != null && valueWei > 0n) {
|
|
366
|
+
bodyForSign.value = valueWei.toString();
|
|
367
|
+
}
|
|
368
|
+
if (clientId) bodyForSign.clientId = clientId;
|
|
369
|
+
if (input.expiryDate != null && input.expiryDate > 0) {
|
|
370
|
+
bodyForSign.expiryDate = Math.floor(input.expiryDate);
|
|
371
|
+
}
|
|
372
|
+
return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
|
|
373
|
+
}
|
|
374
|
+
function routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimit) {
|
|
375
|
+
if (chainGasLimit != null && Number.isFinite(chainGasLimit) && chainGasLimit > 0) {
|
|
376
|
+
return gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit);
|
|
377
|
+
}
|
|
378
|
+
return (estimatedGas * 12n + 9n) / 10n;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/chains/evm/buildBatch.ts
|
|
382
|
+
async function buildEvmMultisignBatch(args) {
|
|
383
|
+
const { context, steps } = args;
|
|
384
|
+
const {
|
|
385
|
+
chainId,
|
|
386
|
+
rpcUrl,
|
|
387
|
+
executorAddress,
|
|
388
|
+
chainDetail,
|
|
389
|
+
useCustomGas,
|
|
390
|
+
customGasChainDetails,
|
|
391
|
+
keyGen,
|
|
392
|
+
purposeText
|
|
393
|
+
} = context;
|
|
394
|
+
if (steps.length === 0) throw new Error("buildEvmMultisignBatch requires at least one step");
|
|
395
|
+
const ch = defineChain({
|
|
396
|
+
id: chainId,
|
|
397
|
+
name: "Destination",
|
|
398
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
399
|
+
rpcUrls: { default: { http: [rpcUrl] } }
|
|
400
|
+
});
|
|
401
|
+
const publicClient = createPublicClient({ chain: ch, transport: defiEvmTransport(rpcUrl) });
|
|
402
|
+
const feeParams = await fetchChainFeeParams(rpcUrl, chainId);
|
|
403
|
+
const legacy = Boolean(chainDetail?.legacy) || !feeParams.isEip1559;
|
|
404
|
+
const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
|
|
405
|
+
const gasLimitConfig = useCustomGas && chainDetail?.gasLimit != null ? Number(chainDetail.gasLimit) : void 0;
|
|
406
|
+
const chainGasLimitRouter = chainDetail?.gasLimit != null && Number.isFinite(Number(chainDetail.gasLimit)) && Number(chainDetail.gasLimit) > 0 ? Number(chainDetail.gasLimit) : void 0;
|
|
407
|
+
const gasFeeMultiplier = useCustomGas && chainDetail?.gasMultiplier != null ? Number(chainDetail.gasMultiplier) : void 0;
|
|
408
|
+
const executor = getAddress(executorAddress);
|
|
409
|
+
const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
|
|
410
|
+
const legs = [];
|
|
411
|
+
for (let i = 0; i < steps.length; i++) {
|
|
412
|
+
const step = steps[i];
|
|
413
|
+
const currentNonce = baseNonce + i;
|
|
414
|
+
let estimatedGas;
|
|
415
|
+
if (args.estimateGasForStep) {
|
|
416
|
+
estimatedGas = await args.estimateGasForStep({ step, index: i, publicClient, executor });
|
|
417
|
+
} else {
|
|
418
|
+
try {
|
|
419
|
+
estimatedGas = await publicClient.estimateGas({
|
|
420
|
+
to: step.to,
|
|
421
|
+
data: step.data,
|
|
422
|
+
value: step.value,
|
|
423
|
+
account: executor
|
|
424
|
+
});
|
|
425
|
+
} catch {
|
|
426
|
+
estimatedGas = step.fallbackGas ?? 100000n;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
let gasLimitI;
|
|
430
|
+
if (args.resolveGasLimit) {
|
|
431
|
+
gasLimitI = await args.resolveGasLimit({ step, index: i, estimatedGas, publicClient });
|
|
432
|
+
} else if (step.routerSwap) {
|
|
433
|
+
gasLimitI = routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimitRouter);
|
|
434
|
+
} else {
|
|
435
|
+
gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
|
|
436
|
+
}
|
|
437
|
+
let proposalTxParams;
|
|
438
|
+
let feeSnapshot;
|
|
439
|
+
let serialized;
|
|
440
|
+
if (legacy) {
|
|
441
|
+
let gasPriceWei = await publicClient.getGasPrice();
|
|
442
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
443
|
+
gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
444
|
+
}
|
|
445
|
+
if (useCustomGas && chainDetail?.gasPrice != null && chainDetail.gasPrice > 0) {
|
|
446
|
+
const configured = parseGwei(gweiToDecimalString(Number(chainDetail.gasPrice)));
|
|
447
|
+
if (configured > gasPriceWei) gasPriceWei = configured;
|
|
448
|
+
}
|
|
449
|
+
serialized = serializeTransaction({
|
|
450
|
+
type: "legacy",
|
|
451
|
+
to: step.to,
|
|
452
|
+
data: step.data,
|
|
453
|
+
value: step.value,
|
|
454
|
+
gas: gasLimitI,
|
|
455
|
+
gasPrice: gasPriceWei,
|
|
456
|
+
nonce: currentNonce,
|
|
457
|
+
chainId
|
|
458
|
+
});
|
|
459
|
+
proposalTxParams = {
|
|
460
|
+
nonce: currentNonce,
|
|
461
|
+
gasLimit: gasLimitI.toString(),
|
|
462
|
+
txType: "legacy",
|
|
463
|
+
gasPrice: gasPriceWei.toString()
|
|
464
|
+
};
|
|
465
|
+
feeSnapshot = proposalTxParamsToFeeSnapshot(proposalTxParams);
|
|
466
|
+
} else {
|
|
467
|
+
const fetchedBase = feeParams.baseFeeGwei ?? 0;
|
|
468
|
+
const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
|
|
469
|
+
const configuredBase = useCustomGas && chainDetail?.baseFee != null ? Number(chainDetail.baseFee) : 0;
|
|
470
|
+
const configuredPriority = useCustomGas && chainDetail?.priorityFee != null ? Number(chainDetail.priorityFee) : 0;
|
|
471
|
+
const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
|
|
472
|
+
const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
|
|
473
|
+
const baseFeeMultiplierPct = useCustomGas && chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(chainDetail.baseFeeMultiplier)) : 100;
|
|
474
|
+
const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
|
|
475
|
+
const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
|
|
476
|
+
let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : parseGwei("1");
|
|
477
|
+
let maxFeePerGas = parseGwei(gweiToDecimalString(maxFeePerGasGwei));
|
|
478
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
479
|
+
maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
480
|
+
maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
481
|
+
}
|
|
482
|
+
({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
|
|
483
|
+
maxFeePerGas,
|
|
484
|
+
maxPriorityFeePerGas,
|
|
485
|
+
latestBaseFeeWei
|
|
486
|
+
));
|
|
487
|
+
serialized = serializeTransaction({
|
|
488
|
+
type: "eip1559",
|
|
489
|
+
to: step.to,
|
|
490
|
+
data: step.data,
|
|
491
|
+
value: step.value,
|
|
492
|
+
gas: gasLimitI,
|
|
493
|
+
maxFeePerGas,
|
|
494
|
+
maxPriorityFeePerGas,
|
|
495
|
+
nonce: currentNonce,
|
|
496
|
+
chainId
|
|
497
|
+
});
|
|
498
|
+
proposalTxParams = {
|
|
499
|
+
nonce: currentNonce,
|
|
500
|
+
gasLimit: gasLimitI.toString(),
|
|
501
|
+
txType: "eip1559",
|
|
502
|
+
maxFeePerGas: maxFeePerGas.toString(),
|
|
503
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
504
|
+
};
|
|
505
|
+
feeSnapshot = i === 0 ? proposalTxParamsToFeeSnapshot(proposalTxParams) : {};
|
|
506
|
+
}
|
|
507
|
+
const h = keccak256(serialized);
|
|
508
|
+
const msgHash = h.startsWith("0x") ? h.slice(2) : h;
|
|
509
|
+
const batchMetaExtra = args.buildBatchMeta({ step, index: i, gasLimit: gasLimitI });
|
|
510
|
+
legs.push({
|
|
511
|
+
msgHash,
|
|
512
|
+
msgRaw: i === 0 && args.firstMsgRawNo0x != null ? args.firstMsgRawNo0x : serialized,
|
|
513
|
+
destinationAddress: step.to,
|
|
514
|
+
signatureText: typeof batchMetaExtra.signatureText === "string" ? batchMetaExtra.signatureText : JSON.stringify(batchMetaExtra.signatureText ?? {}),
|
|
515
|
+
audit: batchMetaExtra,
|
|
516
|
+
feeSnapshot: i === 0 ? feeSnapshot : {},
|
|
517
|
+
proposalTxParams,
|
|
518
|
+
valueWei: i === 0 ? step.value : void 0
|
|
519
|
+
});
|
|
520
|
+
if (i === 0 && args.firstMsgRawNo0x != null) {
|
|
521
|
+
legs[0].msgRaw = args.firstMsgRawNo0x;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const extraJSON = {};
|
|
525
|
+
if (useCustomGas && customGasChainDetails && Object.keys(customGasChainDetails).length > 0) {
|
|
526
|
+
extraJSON.customGasChainDetails = customGasChainDetails;
|
|
527
|
+
}
|
|
528
|
+
const result = finalizeMultisign({
|
|
529
|
+
keyGen,
|
|
530
|
+
purposeText,
|
|
531
|
+
purposeSuffix: args.purposeSuffix,
|
|
532
|
+
destinationChainID: String(chainId),
|
|
533
|
+
destinationAddress: args.destinationAddress ?? steps[0].to,
|
|
534
|
+
legs,
|
|
535
|
+
extraJSON: Object.keys(extraJSON).length > 0 ? extraJSON : void 0,
|
|
536
|
+
expiryDate: context.expiryDate
|
|
537
|
+
});
|
|
538
|
+
const pv = args.payableValueWei;
|
|
539
|
+
if (pv != null && pv > 0n) {
|
|
540
|
+
result.bodyForSign.value = pv.toString();
|
|
541
|
+
}
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
var require2 = createRequire(import.meta.url);
|
|
545
|
+
var artifacts = /* @__PURE__ */ new Map();
|
|
546
|
+
var started = false;
|
|
547
|
+
var loadedProviders = /* @__PURE__ */ new Set();
|
|
548
|
+
var lastEngineError = "";
|
|
549
|
+
function errorChain(err) {
|
|
550
|
+
const parts = [];
|
|
551
|
+
let current = err;
|
|
552
|
+
for (let depth = 0; current && depth < 4; depth += 1) {
|
|
553
|
+
if (current instanceof Error) {
|
|
554
|
+
parts.push(current.message);
|
|
555
|
+
current = current.cause;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
parts.push(String(current));
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
return parts.join(": ");
|
|
562
|
+
}
|
|
563
|
+
function latestRailgunEngineError() {
|
|
564
|
+
return lastEngineError;
|
|
565
|
+
}
|
|
566
|
+
var POI_NODE_URLS = ["https://ppoi.fdi.network"];
|
|
567
|
+
async function walletModule() {
|
|
568
|
+
return await import('@railgun-community/wallet');
|
|
569
|
+
}
|
|
570
|
+
var artifactStore = {
|
|
571
|
+
async get(path) {
|
|
572
|
+
return artifacts.get(path) ?? null;
|
|
573
|
+
},
|
|
574
|
+
async store(_dir, path, item) {
|
|
575
|
+
artifacts.set(path, item);
|
|
576
|
+
},
|
|
577
|
+
async exists(path) {
|
|
578
|
+
return artifacts.has(path);
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
async function ensureRailgunEngine() {
|
|
582
|
+
if (started) return;
|
|
583
|
+
const wallet = await walletModule();
|
|
584
|
+
const walletEntry = require2.resolve("@railgun-community/wallet");
|
|
585
|
+
const { setLoggers } = require2(join(dirname(walletEntry), "utils/logger.js"));
|
|
586
|
+
setLoggers(
|
|
587
|
+
() => void 0,
|
|
588
|
+
(err) => {
|
|
589
|
+
lastEngineError = errorChain(err);
|
|
590
|
+
}
|
|
591
|
+
);
|
|
592
|
+
const memdown = (await import('memdown')).default;
|
|
593
|
+
const store = new wallet.ArtifactStore(
|
|
594
|
+
(path) => artifactStore.get(path),
|
|
595
|
+
(dir, path, item) => artifactStore.store(dir, path, item),
|
|
596
|
+
(path) => artifactStore.exists(path)
|
|
597
|
+
);
|
|
598
|
+
await wallet.startRailgunEngine("continuum", new memdown(), true, store, false, false, POI_NODE_URLS, [], false);
|
|
599
|
+
started = true;
|
|
600
|
+
}
|
|
601
|
+
async function ensureRailgunProvider(chainId, rpcUrl) {
|
|
602
|
+
await ensureRailgunEngine();
|
|
603
|
+
const network = await railgunNetworkByChainId(chainId);
|
|
604
|
+
if (!network) throw new Error("Railgun is not deployed on this chain.");
|
|
605
|
+
const networkName = network.networkName;
|
|
606
|
+
if (!loadedProviders.has(networkName)) {
|
|
607
|
+
const wallet = await walletModule();
|
|
608
|
+
await wallet.loadProvider(
|
|
609
|
+
{
|
|
610
|
+
chainId,
|
|
611
|
+
providers: [{ provider: rpcUrl, priority: 1, weight: 2, stallTimeout: 2500 }]
|
|
612
|
+
},
|
|
613
|
+
networkName,
|
|
614
|
+
1e3 * 60 * 5
|
|
615
|
+
);
|
|
616
|
+
loadedProviders.add(networkName);
|
|
617
|
+
}
|
|
618
|
+
return networkName;
|
|
619
|
+
}
|
|
620
|
+
function railgunGasDetails(chainDetail, gasEstimate) {
|
|
621
|
+
if (chainDetail.legacy) {
|
|
622
|
+
return {
|
|
623
|
+
evmGasType: EVMGasType.Type0,
|
|
624
|
+
gasEstimate,
|
|
625
|
+
gasPrice: BigInt(Math.round(Number(chainDetail.gasPrice ?? 1) * 1e9))
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
evmGasType: EVMGasType.Type2,
|
|
630
|
+
gasEstimate,
|
|
631
|
+
maxFeePerGas: BigInt(Math.round(Number(chainDetail.baseFee ?? 1) * 1e9)),
|
|
632
|
+
maxPriorityFeePerGas: BigInt(Math.round(Number(chainDetail.priorityFee ?? 1) * 1e9))
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/protocols/evm/railgun/shield.ts
|
|
637
|
+
var erc20Abi = parseAbi([
|
|
638
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
639
|
+
"function approve(address spender, uint256 amount) returns (bool)"
|
|
640
|
+
]);
|
|
641
|
+
var wethAbi = parseAbi(["function deposit() payable", "function withdraw(uint256 wad)"]);
|
|
642
|
+
var populateShieldOverride = null;
|
|
643
|
+
var allowanceReaderOverride = null;
|
|
644
|
+
function setRailgunPopulateShieldForTests(fn) {
|
|
645
|
+
populateShieldOverride = fn;
|
|
646
|
+
}
|
|
647
|
+
function setRailgunAllowanceReaderForTests(fn) {
|
|
648
|
+
allowanceReaderOverride = fn;
|
|
649
|
+
}
|
|
650
|
+
async function shieldSignatureMessage() {
|
|
651
|
+
const wallet = await import('@railgun-community/wallet');
|
|
652
|
+
return wallet.getShieldPrivateKeySignatureMessage();
|
|
653
|
+
}
|
|
654
|
+
function personalSignBody(args, message) {
|
|
655
|
+
const executor = getAddress(args.executorAddress);
|
|
656
|
+
const digest = hashMessage(message);
|
|
657
|
+
return finalizeMultisign({
|
|
658
|
+
keyGen: args.keyGen,
|
|
659
|
+
purposeText: args.purposeText,
|
|
660
|
+
destinationChainID: String(args.chainId),
|
|
661
|
+
destinationAddress: executor,
|
|
662
|
+
expiryDate: args.expiryDate,
|
|
663
|
+
extraJSON: {
|
|
664
|
+
signRequestKind: "personal_sign",
|
|
665
|
+
delivery: {
|
|
666
|
+
kind: "railgun_shield_signature",
|
|
667
|
+
chainId: args.chainId,
|
|
668
|
+
message
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
legs: [
|
|
672
|
+
{
|
|
673
|
+
msgHash: digest.startsWith("0x") ? digest.slice(2) : digest,
|
|
674
|
+
msgRaw: stringToHex(message),
|
|
675
|
+
destinationAddress: executor,
|
|
676
|
+
signatureText: "Railgun shield key",
|
|
677
|
+
audit: {
|
|
678
|
+
kind: "PERSONAL_SIGN",
|
|
679
|
+
protocol: "railgun",
|
|
680
|
+
action: "shield-signature"
|
|
681
|
+
},
|
|
682
|
+
feeSnapshot: {}
|
|
683
|
+
}
|
|
684
|
+
]
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
async function readAllowance(args) {
|
|
688
|
+
if (allowanceReaderOverride) return allowanceReaderOverride(args);
|
|
689
|
+
const client = createPublicClient({ transport: defiEvmTransport(args.rpcUrl) });
|
|
690
|
+
return client.readContract({
|
|
691
|
+
address: args.tokenAddress,
|
|
692
|
+
abi: erc20Abi,
|
|
693
|
+
functionName: "allowance",
|
|
694
|
+
args: [args.owner, args.spender]
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
async function buildEvmMultisignBodyWethUnwrap(args) {
|
|
698
|
+
const network = await railgunNetworkByChainId(args.chainId);
|
|
699
|
+
if (!network) throw new Error("Railgun is not deployed on this chain.");
|
|
700
|
+
const tokenAddress = getAddress(network.wrappedAddress);
|
|
701
|
+
const amount = BigInt(args.amountWei);
|
|
702
|
+
if (amount <= 0n) throw new Error("Amount must be greater than zero.");
|
|
703
|
+
const rpcUrl = args.rpcUrl.trim();
|
|
704
|
+
if (!rpcUrl) throw new Error("rpcUrl is required.");
|
|
705
|
+
return buildEvmMultisignBatch({
|
|
706
|
+
context: {
|
|
707
|
+
chainCategory: "evm",
|
|
708
|
+
chainId: args.chainId,
|
|
709
|
+
rpcUrl,
|
|
710
|
+
executorAddress: getAddress(args.executorAddress),
|
|
711
|
+
chainDetail: args.chainDetail,
|
|
712
|
+
useCustomGas: Boolean(args.useCustomGas),
|
|
713
|
+
customGasChainDetails: args.customGasChainDetails,
|
|
714
|
+
keyGen: args.keyGen,
|
|
715
|
+
purposeText: args.purposeText
|
|
716
|
+
},
|
|
717
|
+
steps: [
|
|
718
|
+
{
|
|
719
|
+
to: tokenAddress,
|
|
720
|
+
data: encodeFunctionData({ abi: wethAbi, functionName: "withdraw", args: [amount] }),
|
|
721
|
+
value: 0n,
|
|
722
|
+
fallbackGas: 80000n
|
|
723
|
+
}
|
|
724
|
+
],
|
|
725
|
+
buildBatchMeta: () => ({ evm: { type: "railgun_unwrap" } })
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
async function buildEvmMultisignBodyRailgunShield(args) {
|
|
729
|
+
assertRailgunCtmAllowed(args.chainId, args.tokenAddress);
|
|
730
|
+
const signature = args.shieldSignature?.trim();
|
|
731
|
+
if (!signature) {
|
|
732
|
+
return personalSignBody(args, await shieldSignatureMessage());
|
|
733
|
+
}
|
|
734
|
+
const network = await railgunNetworkByChainId(args.chainId);
|
|
735
|
+
if (!network) throw new Error("Railgun is not deployed on this chain.");
|
|
736
|
+
const tokenAddress = getAddress(args.tokenAddress);
|
|
737
|
+
if (tokenAddress === zeroAddress) {
|
|
738
|
+
throw new Error("Shield the wrapped gas token. Railgun does not shield the native gas token directly.");
|
|
739
|
+
}
|
|
740
|
+
const amount = BigInt(args.amountWei);
|
|
741
|
+
if (amount <= 0n) throw new Error("Amount must be greater than zero.");
|
|
742
|
+
const executor = getAddress(args.executorAddress);
|
|
743
|
+
const spender = getAddress(network.proxyContract);
|
|
744
|
+
const rpcUrl = args.rpcUrl.trim();
|
|
745
|
+
if (!rpcUrl) throw new Error("rpcUrl is required.");
|
|
746
|
+
const allowance = await readAllowance({
|
|
747
|
+
rpcUrl,
|
|
748
|
+
tokenAddress,
|
|
749
|
+
owner: executor,
|
|
750
|
+
spender
|
|
751
|
+
});
|
|
752
|
+
const steps = [];
|
|
753
|
+
if (args.wrapNative) {
|
|
754
|
+
if (getAddress(network.wrappedAddress) !== tokenAddress) {
|
|
755
|
+
throw new Error("Native wrap only applies to this chain\u2019s wrapped gas token.");
|
|
756
|
+
}
|
|
757
|
+
steps.push({
|
|
758
|
+
to: tokenAddress,
|
|
759
|
+
data: encodeFunctionData({ abi: wethAbi, functionName: "deposit" }),
|
|
760
|
+
value: amount,
|
|
761
|
+
fallbackGas: 80000n
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
if (allowance > 0n && allowance < amount) {
|
|
765
|
+
steps.push({
|
|
766
|
+
to: tokenAddress,
|
|
767
|
+
data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [spender, 0n] }),
|
|
768
|
+
value: 0n,
|
|
769
|
+
fallbackGas: 80000n
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
if (allowance < amount) {
|
|
773
|
+
steps.push({
|
|
774
|
+
to: tokenAddress,
|
|
775
|
+
data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [spender, amount] }),
|
|
776
|
+
value: 0n,
|
|
777
|
+
fallbackGas: 80000n
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
const networkName = populateShieldOverride ? network.networkName : await ensureRailgunProvider(args.chainId, rpcUrl);
|
|
781
|
+
const populate = populateShieldOverride ?? (await import('@railgun-community/wallet')).populateShield;
|
|
782
|
+
const populated = await populate(
|
|
783
|
+
TXIDVersion.V2_PoseidonMerkle,
|
|
784
|
+
networkName,
|
|
785
|
+
signature,
|
|
786
|
+
[{ tokenAddress, amount, recipientAddress: args.railgunAddress.trim() }],
|
|
787
|
+
[],
|
|
788
|
+
railgunGasDetails(args.chainDetail, 1200000n)
|
|
789
|
+
);
|
|
790
|
+
const tx = populated.transaction;
|
|
791
|
+
const to = tx?.to ? getAddress(tx.to) : spender;
|
|
792
|
+
const data = tx?.data;
|
|
793
|
+
if (!data || !data.startsWith("0x")) throw new Error("Railgun shield transaction was empty.");
|
|
794
|
+
steps.push({
|
|
795
|
+
to,
|
|
796
|
+
data,
|
|
797
|
+
value: BigInt(tx?.value ?? 0),
|
|
798
|
+
fallbackGas: 1200000n
|
|
799
|
+
});
|
|
800
|
+
return buildEvmMultisignBatch({
|
|
801
|
+
context: {
|
|
802
|
+
chainCategory: "evm",
|
|
803
|
+
chainId: args.chainId,
|
|
804
|
+
rpcUrl,
|
|
805
|
+
executorAddress: executor,
|
|
806
|
+
chainDetail: args.chainDetail,
|
|
807
|
+
useCustomGas: Boolean(args.useCustomGas),
|
|
808
|
+
customGasChainDetails: args.customGasChainDetails,
|
|
809
|
+
keyGen: args.keyGen,
|
|
810
|
+
purposeText: args.purposeText
|
|
811
|
+
},
|
|
812
|
+
steps,
|
|
813
|
+
buildBatchMeta: ({ step }) => ({
|
|
814
|
+
evm: {
|
|
815
|
+
type: step.value > 0n ? "railgun_wrap" : step.to === tokenAddress ? "railgun_approve" : "railgun_shield"
|
|
816
|
+
}
|
|
817
|
+
})
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
function chainFor(chainId) {
|
|
821
|
+
return { type: ChainType.EVM, id: chainId };
|
|
822
|
+
}
|
|
823
|
+
async function assertDeployed(chainId) {
|
|
824
|
+
const network = await railgunNetworkByChainId(chainId);
|
|
825
|
+
if (!network) throw new Error("Railgun is not deployed on this chain.");
|
|
826
|
+
return network;
|
|
827
|
+
}
|
|
828
|
+
function parsedAmount(amountWei) {
|
|
829
|
+
const amount = BigInt(amountWei);
|
|
830
|
+
if (amount <= 0n) throw new Error("Amount must be greater than zero.");
|
|
831
|
+
return amount;
|
|
832
|
+
}
|
|
833
|
+
async function broadcasterClient() {
|
|
834
|
+
try {
|
|
835
|
+
return await import('@railgun-community/waku-broadcaster-client-web');
|
|
836
|
+
} catch {
|
|
837
|
+
throw new Error("Railgun broadcaster client is not available.");
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
async function selectedBroadcaster(chain, tokenAddress) {
|
|
841
|
+
const { WakuBroadcasterClient } = await broadcasterClient();
|
|
842
|
+
if (!WakuBroadcasterClient.isStarted()) {
|
|
843
|
+
await WakuBroadcasterClient.start(chain, { useDNSDiscovery: true }, () => void 0);
|
|
844
|
+
} else {
|
|
845
|
+
await WakuBroadcasterClient.setChain(chain);
|
|
846
|
+
}
|
|
847
|
+
const deadline = Date.now() + 2e4;
|
|
848
|
+
let selected;
|
|
849
|
+
while (Date.now() < deadline) {
|
|
850
|
+
selected = await WakuBroadcasterClient.findBestBroadcaster(chain, tokenAddress, false);
|
|
851
|
+
if (selected) return selected;
|
|
852
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
853
|
+
}
|
|
854
|
+
throw new Error("No Railgun broadcaster is available for this token.");
|
|
855
|
+
}
|
|
856
|
+
async function submitProved(args) {
|
|
857
|
+
const to = args.populated.transaction.to;
|
|
858
|
+
const data = args.populated.transaction.data;
|
|
859
|
+
if (!to || !data) throw new Error("Railgun proof did not produce a transaction.");
|
|
860
|
+
const { BroadcasterTransaction } = await broadcasterClient();
|
|
861
|
+
const tx = await BroadcasterTransaction.create(
|
|
862
|
+
TXIDVersion.V2_PoseidonMerkle,
|
|
863
|
+
to,
|
|
864
|
+
data,
|
|
865
|
+
args.broadcaster.railgunAddress,
|
|
866
|
+
args.broadcaster.tokenFee.feesID,
|
|
867
|
+
args.chain,
|
|
868
|
+
args.populated.nullifiers ?? [],
|
|
869
|
+
args.overallBatchMinGasPrice,
|
|
870
|
+
false,
|
|
871
|
+
args.populated.preTransactionPOIsPerTxidLeafPerList
|
|
872
|
+
);
|
|
873
|
+
return tx.send();
|
|
874
|
+
}
|
|
875
|
+
function feeRecipient(wallet, broadcaster, gasDetails) {
|
|
876
|
+
const fee = wallet.calculateBroadcasterFeeERC20Amount(
|
|
877
|
+
{
|
|
878
|
+
tokenAddress: broadcaster.tokenAddress,
|
|
879
|
+
feePerUnitGas: BigInt(broadcaster.tokenFee.feePerUnitGas)
|
|
880
|
+
},
|
|
881
|
+
gasDetails
|
|
882
|
+
);
|
|
883
|
+
return {
|
|
884
|
+
tokenAddress: fee.tokenAddress,
|
|
885
|
+
amount: fee.amount,
|
|
886
|
+
recipientAddress: broadcaster.railgunAddress
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
async function railgunPrivateTransfer(args) {
|
|
890
|
+
assertRailgunCtmAllowed(args.chainId, args.tokenAddress);
|
|
891
|
+
await assertDeployed(args.chainId);
|
|
892
|
+
const tokenAddress = getAddress(args.tokenAddress);
|
|
893
|
+
const amount = parsedAmount(args.amountWei);
|
|
894
|
+
const recipientAddress = args.recipientAddress.trim();
|
|
895
|
+
if (!recipientAddress.startsWith("0zk")) throw new Error("Private transfer recipient must be a 0zk address.");
|
|
896
|
+
const session2 = requireRailgunWalletSession();
|
|
897
|
+
const rpcUrl = args.rpcUrl?.trim() ?? "";
|
|
898
|
+
if (!rpcUrl || !args.chainDetail) throw new Error("rpcUrl and chain details are required.");
|
|
899
|
+
const networkName = await ensureRailgunProvider(args.chainId, rpcUrl);
|
|
900
|
+
const wallet = await import('@railgun-community/wallet');
|
|
901
|
+
const chain = chainFor(args.chainId);
|
|
902
|
+
const broadcaster = await selectedBroadcaster(chain, tokenAddress);
|
|
903
|
+
const recipients = [{ tokenAddress, amount, recipientAddress }];
|
|
904
|
+
const feeToken = {
|
|
905
|
+
tokenAddress: broadcaster.tokenAddress,
|
|
906
|
+
feePerUnitGas: BigInt(broadcaster.tokenFee.feePerUnitGas)
|
|
907
|
+
};
|
|
908
|
+
const original = railgunGasDetails(args.chainDetail, 0n);
|
|
909
|
+
const { gasEstimate } = await wallet.gasEstimateForUnprovenTransfer(
|
|
910
|
+
TXIDVersion.V2_PoseidonMerkle,
|
|
911
|
+
networkName,
|
|
912
|
+
session2.walletId,
|
|
913
|
+
session2.encryptionKey,
|
|
914
|
+
args.memoText ?? null,
|
|
915
|
+
recipients,
|
|
916
|
+
[],
|
|
917
|
+
original,
|
|
918
|
+
feeToken,
|
|
919
|
+
false
|
|
920
|
+
);
|
|
921
|
+
const gasDetails = railgunGasDetails(args.chainDetail, gasEstimate);
|
|
922
|
+
const broadcasterFee = feeRecipient(wallet, broadcaster, gasDetails);
|
|
923
|
+
const overallBatchMinGasPrice = calculateGasPrice(gasDetails);
|
|
924
|
+
await wallet.generateTransferProof(
|
|
925
|
+
TXIDVersion.V2_PoseidonMerkle,
|
|
926
|
+
networkName,
|
|
927
|
+
session2.walletId,
|
|
928
|
+
session2.encryptionKey,
|
|
929
|
+
false,
|
|
930
|
+
args.memoText ?? null,
|
|
931
|
+
recipients,
|
|
932
|
+
[],
|
|
933
|
+
broadcasterFee,
|
|
934
|
+
false,
|
|
935
|
+
overallBatchMinGasPrice,
|
|
936
|
+
(progress) => args.onProofProgress?.(progress)
|
|
937
|
+
);
|
|
938
|
+
const populated = await wallet.populateProvedTransfer(
|
|
939
|
+
TXIDVersion.V2_PoseidonMerkle,
|
|
940
|
+
networkName,
|
|
941
|
+
session2.walletId,
|
|
942
|
+
false,
|
|
943
|
+
args.memoText ?? null,
|
|
944
|
+
recipients,
|
|
945
|
+
[],
|
|
946
|
+
broadcasterFee,
|
|
947
|
+
false,
|
|
948
|
+
overallBatchMinGasPrice,
|
|
949
|
+
gasDetails
|
|
950
|
+
);
|
|
951
|
+
return { txHash: await submitProved({ chain, populated, broadcaster, overallBatchMinGasPrice }) };
|
|
952
|
+
}
|
|
953
|
+
async function railgunUnshield(args) {
|
|
954
|
+
assertRailgunCtmAllowed(args.chainId, args.tokenAddress);
|
|
955
|
+
await assertDeployed(args.chainId);
|
|
956
|
+
const tokenAddress = getAddress(args.tokenAddress);
|
|
957
|
+
const amount = parsedAmount(args.amountWei);
|
|
958
|
+
const recipientAddress = getAddress(args.recipientAddress);
|
|
959
|
+
const session2 = requireRailgunWalletSession();
|
|
960
|
+
const rpcUrl = args.rpcUrl?.trim() ?? "";
|
|
961
|
+
if (!rpcUrl || !args.chainDetail) throw new Error("rpcUrl and chain details are required.");
|
|
962
|
+
const networkName = await ensureRailgunProvider(args.chainId, rpcUrl);
|
|
963
|
+
const wallet = await import('@railgun-community/wallet');
|
|
964
|
+
const chain = chainFor(args.chainId);
|
|
965
|
+
const broadcaster = await selectedBroadcaster(chain, tokenAddress);
|
|
966
|
+
const recipients = [{ tokenAddress, amount, recipientAddress }];
|
|
967
|
+
const feeToken = {
|
|
968
|
+
tokenAddress: broadcaster.tokenAddress,
|
|
969
|
+
feePerUnitGas: BigInt(broadcaster.tokenFee.feePerUnitGas)
|
|
970
|
+
};
|
|
971
|
+
const original = railgunGasDetails(args.chainDetail, 0n);
|
|
972
|
+
const { gasEstimate } = await wallet.gasEstimateForUnprovenUnshield(
|
|
973
|
+
TXIDVersion.V2_PoseidonMerkle,
|
|
974
|
+
networkName,
|
|
975
|
+
session2.walletId,
|
|
976
|
+
session2.encryptionKey,
|
|
977
|
+
recipients,
|
|
978
|
+
[],
|
|
979
|
+
original,
|
|
980
|
+
feeToken,
|
|
981
|
+
false
|
|
982
|
+
);
|
|
983
|
+
const gasDetails = railgunGasDetails(args.chainDetail, gasEstimate);
|
|
984
|
+
const broadcasterFee = feeRecipient(wallet, broadcaster, gasDetails);
|
|
985
|
+
const overallBatchMinGasPrice = calculateGasPrice(gasDetails);
|
|
986
|
+
await wallet.generateUnshieldProof(
|
|
987
|
+
TXIDVersion.V2_PoseidonMerkle,
|
|
988
|
+
networkName,
|
|
989
|
+
session2.walletId,
|
|
990
|
+
session2.encryptionKey,
|
|
991
|
+
recipients,
|
|
992
|
+
[],
|
|
993
|
+
broadcasterFee,
|
|
994
|
+
false,
|
|
995
|
+
overallBatchMinGasPrice,
|
|
996
|
+
(progress) => args.onProofProgress?.(progress)
|
|
997
|
+
);
|
|
998
|
+
const populated = await wallet.populateProvedUnshield(
|
|
999
|
+
TXIDVersion.V2_PoseidonMerkle,
|
|
1000
|
+
networkName,
|
|
1001
|
+
session2.walletId,
|
|
1002
|
+
recipients,
|
|
1003
|
+
[],
|
|
1004
|
+
broadcasterFee,
|
|
1005
|
+
false,
|
|
1006
|
+
overallBatchMinGasPrice,
|
|
1007
|
+
gasDetails
|
|
1008
|
+
);
|
|
1009
|
+
return { txHash: await submitProved({ chain, populated, broadcaster, overallBatchMinGasPrice }) };
|
|
1010
|
+
}
|
|
1011
|
+
async function walletModule2() {
|
|
1012
|
+
return await import('@railgun-community/wallet');
|
|
1013
|
+
}
|
|
1014
|
+
async function createRailgunBrowserWallet(args) {
|
|
1015
|
+
await ensureRailgunEngine();
|
|
1016
|
+
const network = await railgunNetworkByChainId(args.chainId);
|
|
1017
|
+
const wallet = await walletModule2();
|
|
1018
|
+
const created = await wallet.createRailgunWallet(
|
|
1019
|
+
args.encryptionKey,
|
|
1020
|
+
args.mnemonic,
|
|
1021
|
+
network ? { [network.networkName]: network.deploymentBlock } : void 0
|
|
1022
|
+
);
|
|
1023
|
+
const session2 = {
|
|
1024
|
+
walletId: created.id,
|
|
1025
|
+
encryptionKey: args.encryptionKey,
|
|
1026
|
+
railgunAddress: created.railgunAddress
|
|
1027
|
+
};
|
|
1028
|
+
setRailgunWalletSession(session2);
|
|
1029
|
+
return session2;
|
|
1030
|
+
}
|
|
1031
|
+
async function unlockRailgunBrowserWallet(args) {
|
|
1032
|
+
const wallet = await walletModule2();
|
|
1033
|
+
const loaded = await wallet.loadWalletByID(args.encryptionKey, args.walletId, false);
|
|
1034
|
+
const session2 = {
|
|
1035
|
+
walletId: loaded.id,
|
|
1036
|
+
encryptionKey: args.encryptionKey,
|
|
1037
|
+
railgunAddress: loaded.railgunAddress
|
|
1038
|
+
};
|
|
1039
|
+
setRailgunWalletSession(session2);
|
|
1040
|
+
return session2;
|
|
1041
|
+
}
|
|
1042
|
+
async function railgunShareableViewingKey(walletId) {
|
|
1043
|
+
const wallet = await walletModule2();
|
|
1044
|
+
const viewingKey = await wallet.getWalletShareableViewingKey(walletId);
|
|
1045
|
+
if (!viewingKey) throw new Error("Railgun viewing key is not available.");
|
|
1046
|
+
return viewingKey;
|
|
1047
|
+
}
|
|
1048
|
+
async function openRailgunViewOnlyWallet(args) {
|
|
1049
|
+
await ensureRailgunEngine();
|
|
1050
|
+
const network = await railgunNetworkByChainId(args.chainId);
|
|
1051
|
+
const wallet = await walletModule2();
|
|
1052
|
+
const created = await wallet.createViewOnlyRailgunWallet(
|
|
1053
|
+
args.encryptionKey,
|
|
1054
|
+
args.shareableViewingKey,
|
|
1055
|
+
network ? { [network.networkName]: network.deploymentBlock } : void 0
|
|
1056
|
+
);
|
|
1057
|
+
return { walletId: created.id, railgunAddress: created.railgunAddress };
|
|
1058
|
+
}
|
|
1059
|
+
async function discardRailgunWallet(walletId) {
|
|
1060
|
+
setRailgunWalletSession(null);
|
|
1061
|
+
const wallet = await walletModule2();
|
|
1062
|
+
wallet.unloadWalletByID(walletId);
|
|
1063
|
+
await wallet.deleteWalletByID(walletId);
|
|
1064
|
+
}
|
|
1065
|
+
function railgunAddressIsValid(address) {
|
|
1066
|
+
return walletModule2().then((wallet) => wallet.validateRailgunAddress(address));
|
|
1067
|
+
}
|
|
1068
|
+
var buckets = /* @__PURE__ */ new Map();
|
|
1069
|
+
function rowMap(chainId) {
|
|
1070
|
+
let rows = buckets.get(chainId);
|
|
1071
|
+
if (!rows) {
|
|
1072
|
+
rows = /* @__PURE__ */ new Map();
|
|
1073
|
+
buckets.set(chainId, rows);
|
|
1074
|
+
}
|
|
1075
|
+
return rows;
|
|
1076
|
+
}
|
|
1077
|
+
function cellFor(rows, tokenAddress) {
|
|
1078
|
+
const key = tokenAddress.toLowerCase();
|
|
1079
|
+
let cell = rows.get(key);
|
|
1080
|
+
if (!cell) {
|
|
1081
|
+
cell = { spendable: 0n, pending: /* @__PURE__ */ new Map() };
|
|
1082
|
+
rows.set(key, cell);
|
|
1083
|
+
}
|
|
1084
|
+
return cell;
|
|
1085
|
+
}
|
|
1086
|
+
function applyBalanceEvent(event) {
|
|
1087
|
+
if (event.txidVersion !== TXIDVersion.V2_PoseidonMerkle) return;
|
|
1088
|
+
const rows = rowMap(event.chain.id);
|
|
1089
|
+
const spendable = event.balanceBucket === RailgunWalletBalanceBucket.Spendable;
|
|
1090
|
+
const pending = event.balanceBucket === RailgunWalletBalanceBucket.ShieldPending || event.balanceBucket === RailgunWalletBalanceBucket.ProofSubmitted || event.balanceBucket === RailgunWalletBalanceBucket.MissingExternalPOI || event.balanceBucket === RailgunWalletBalanceBucket.MissingInternalPOI;
|
|
1091
|
+
if (!spendable && !pending) return;
|
|
1092
|
+
if (spendable) {
|
|
1093
|
+
for (const cell of rows.values()) cell.spendable = 0n;
|
|
1094
|
+
}
|
|
1095
|
+
if (pending) {
|
|
1096
|
+
for (const cell of rows.values()) cell.pending.delete(event.balanceBucket);
|
|
1097
|
+
}
|
|
1098
|
+
for (const amount of event.erc20Amounts) {
|
|
1099
|
+
const cell = cellFor(rows, amount.tokenAddress);
|
|
1100
|
+
if (spendable) cell.spendable = amount.amount;
|
|
1101
|
+
if (pending) cell.pending.set(event.balanceBucket, amount.amount);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
function snapshotRows(chainId) {
|
|
1105
|
+
const rows = buckets.get(chainId);
|
|
1106
|
+
if (!rows) return [];
|
|
1107
|
+
return [...rows.entries()].filter(([, cell]) => cell.spendable > 0n || [...cell.pending.values()].some((amount) => amount > 0n)).map(([tokenAddress, cell]) => ({
|
|
1108
|
+
tokenAddress,
|
|
1109
|
+
spendable: cell.spendable.toString(),
|
|
1110
|
+
pending: [...cell.pending.values()].reduce((sum, amount) => sum + amount, 0n).toString()
|
|
1111
|
+
}));
|
|
1112
|
+
}
|
|
1113
|
+
function utxoScanTerminalStatus(scanStatus) {
|
|
1114
|
+
if (scanStatus === "Complete") return "done";
|
|
1115
|
+
if (scanStatus === "Incomplete") return "failed";
|
|
1116
|
+
return "pending";
|
|
1117
|
+
}
|
|
1118
|
+
async function scanRailgunPrivateBalances(args) {
|
|
1119
|
+
const rpcUrl = args.rpcUrl.trim();
|
|
1120
|
+
if (!rpcUrl) throw new Error("rpcUrl is required.");
|
|
1121
|
+
buckets.set(args.chainId, /* @__PURE__ */ new Map());
|
|
1122
|
+
publishRailgunPrivateBalanceSnapshot({
|
|
1123
|
+
chainId: args.chainId,
|
|
1124
|
+
scanStatus: "scanning",
|
|
1125
|
+
balances: [],
|
|
1126
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1127
|
+
});
|
|
1128
|
+
const wallet = await walletModule2();
|
|
1129
|
+
let resolveScan = () => {
|
|
1130
|
+
};
|
|
1131
|
+
const scanSettled = new Promise((resolve) => {
|
|
1132
|
+
resolveScan = resolve;
|
|
1133
|
+
});
|
|
1134
|
+
wallet.setOnBalanceUpdateCallback((event) => {
|
|
1135
|
+
if (event.railgunWalletID !== args.walletId) return;
|
|
1136
|
+
applyBalanceEvent(event);
|
|
1137
|
+
});
|
|
1138
|
+
wallet.setOnUTXOMerkletreeScanCallback((event) => {
|
|
1139
|
+
if (event.chain.id !== args.chainId) return;
|
|
1140
|
+
args.onProgress?.(event.progress);
|
|
1141
|
+
const terminal = utxoScanTerminalStatus(event.scanStatus);
|
|
1142
|
+
if (terminal !== "pending") resolveScan(terminal);
|
|
1143
|
+
});
|
|
1144
|
+
await ensureRailgunProvider(args.chainId, rpcUrl);
|
|
1145
|
+
const chain = { type: ChainType.EVM, id: args.chainId };
|
|
1146
|
+
await wallet.refreshBalances(chain, [args.walletId]);
|
|
1147
|
+
if (await scanSettled === "failed") {
|
|
1148
|
+
const cause = latestRailgunEngineError();
|
|
1149
|
+
throw new Error(cause ? `Railgun note scan stopped before it finished. ${cause}` : "Railgun note scan stopped before it finished.");
|
|
1150
|
+
}
|
|
1151
|
+
const balances = snapshotRows(args.chainId);
|
|
1152
|
+
const scanStatus = balances.length === 0 ? "empty" : "ready";
|
|
1153
|
+
publishRailgunPrivateBalanceSnapshot({
|
|
1154
|
+
chainId: args.chainId,
|
|
1155
|
+
scanStatus,
|
|
1156
|
+
balances,
|
|
1157
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1158
|
+
notes: balances.length === 0 ? "No private balance on this chain yet." : void 0
|
|
1159
|
+
});
|
|
1160
|
+
return { scanStatus, balances };
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/protocols/evm/railgun/index.ts
|
|
1164
|
+
var RAILGUN_PROTOCOL_ID = "railgun";
|
|
1165
|
+
var railgunProtocolModule = {
|
|
1166
|
+
id: RAILGUN_PROTOCOL_ID,
|
|
1167
|
+
chainCategory: "evm",
|
|
1168
|
+
isChainSupported(ctx) {
|
|
1169
|
+
if (ctx.chainCategory !== "evm") return false;
|
|
1170
|
+
return loadRailgunSupportedChainIds().then((ids) => ids.has(Number(ctx.chainId)));
|
|
1171
|
+
},
|
|
1172
|
+
isTokenSupported(token) {
|
|
1173
|
+
return token.category === "evm" && (token.kind === "native" || token.kind === "erc20");
|
|
1174
|
+
},
|
|
1175
|
+
actions: [
|
|
1176
|
+
{
|
|
1177
|
+
id: "railgun.shield",
|
|
1178
|
+
protocolId: RAILGUN_PROTOCOL_ID,
|
|
1179
|
+
chainCategory: "evm",
|
|
1180
|
+
description: "Shield an ERC-20 into a Railgun 0zk address",
|
|
1181
|
+
commonParams: ["keyGen", "purposeText", "useCustomGas"],
|
|
1182
|
+
params: {
|
|
1183
|
+
tokenAddress: { type: "string", required: true, description: "ERC-20 to shield" },
|
|
1184
|
+
amountWei: { type: "string", required: true, description: "Amount in wei" },
|
|
1185
|
+
railgunAddress: { type: "string", required: true, description: "Recipient 0zk address" }
|
|
1186
|
+
}
|
|
1187
|
+
},
|
|
1188
|
+
{
|
|
1189
|
+
id: "railgun.private-transfer",
|
|
1190
|
+
protocolId: RAILGUN_PROTOCOL_ID,
|
|
1191
|
+
chainCategory: "evm",
|
|
1192
|
+
description: "Private transfer from the unlocked Railgun wallet",
|
|
1193
|
+
commonParams: ["purposeText"],
|
|
1194
|
+
params: {
|
|
1195
|
+
tokenAddress: { type: "string", required: true, description: "Shielded ERC-20" },
|
|
1196
|
+
amountWei: { type: "string", required: true, description: "Amount in wei" },
|
|
1197
|
+
recipientAddress: { type: "string", required: true, description: "Recipient 0zk address" }
|
|
1198
|
+
}
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
id: "railgun.unshield",
|
|
1202
|
+
protocolId: RAILGUN_PROTOCOL_ID,
|
|
1203
|
+
chainCategory: "evm",
|
|
1204
|
+
description: "Unshield from the unlocked Railgun wallet to a public address",
|
|
1205
|
+
commonParams: ["purposeText"],
|
|
1206
|
+
params: {
|
|
1207
|
+
tokenAddress: { type: "string", required: true, description: "Shielded ERC-20" },
|
|
1208
|
+
amountWei: { type: "string", required: true, description: "Amount in wei" },
|
|
1209
|
+
recipientAddress: { type: "string", required: true, description: "Public recipient" }
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
]
|
|
1213
|
+
};
|
|
1214
|
+
registerProtocolModule(railgunProtocolModule);
|
|
1215
|
+
|
|
1216
|
+
export { RAILGUN_CTM_DISABLED_MESSAGE, RAILGUN_CTM_ENABLED, RAILGUN_POPULAR_ASSET_LIMIT, RAILGUN_PROTOCOL_ID, RAILGUN_WALLET_LOCKED_MESSAGE, assertRailgunCtmAllowed, buildEvmMultisignBodyRailgunShield, buildEvmMultisignBodyWethUnwrap, clearRailgunNetworkCache, clearRailgunPopularAssetCache, clearRailgunPrivateBalanceSnapshots, commitmentsFromShieldIndex, createRailgunBrowserWallet, discardRailgunWallet, getRailgunWalletSession, loadRailgunNetworks, loadRailgunPopularAssets, loadRailgunSupportedChainIds, openRailgunViewOnlyWallet, publishRailgunPrivateBalanceSnapshot, railgunAddressIsValid, railgunFetchPopularAssets, railgunFetchPrivateBalances, railgunFetchPrivateBalancesSummary, railgunNetworkByChainId, railgunPrivateTransfer, railgunProtocolModule, railgunShareableViewingKey, railgunUnshield, rankPopularShieldTokens, readRailgunPrivateBalanceSnapshot, requireRailgunWalletSession, scanRailgunPrivateBalances, selectRailgunNetworks, setRailgunAllowanceReaderForTests, setRailgunPopulateShieldForTests, setRailgunShieldLogReaderForTests, setRailgunWalletSession, shieldIndexEndpoint, unlockRailgunBrowserWallet, utxoScanTerminalStatus };
|
|
1217
|
+
//# sourceMappingURL=index.js.map
|
|
1218
|
+
//# sourceMappingURL=index.js.map
|