@sage-protocol/sdk 0.0.4 → 0.0.5
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.cjs +2205 -23
- package/dist/index.mjs +2205 -23
- package/package.json +1 -1
- package/types/index.d.ts +138 -0
package/dist/index.mjs
CHANGED
|
@@ -19,8 +19,8 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
19
19
|
var require_package = __commonJS({
|
|
20
20
|
"package.json"(exports, module) {
|
|
21
21
|
module.exports = {
|
|
22
|
-
name: "sage-protocol
|
|
23
|
-
version: "0.
|
|
22
|
+
name: "@sage-protocol/sdk",
|
|
23
|
+
version: "0.0.5",
|
|
24
24
|
description: "Backend-agnostic SDK for interacting with the Sage Protocol (governance, SubDAOs, tokens).",
|
|
25
25
|
main: "dist/index.cjs",
|
|
26
26
|
module: "dist/index.mjs",
|
|
@@ -43,11 +43,19 @@ var require_package = __commonJS({
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
sideEffects: false,
|
|
46
|
+
repository: {
|
|
47
|
+
type: "git",
|
|
48
|
+
url: "git+https://github.com/sage-protocol/sdk.git"
|
|
49
|
+
},
|
|
50
|
+
bugs: {
|
|
51
|
+
url: "https://github.com/sage-protocol/sdk/issues"
|
|
52
|
+
},
|
|
53
|
+
homepage: "https://github.com/sage-protocol/sdk#readme",
|
|
46
54
|
scripts: {
|
|
47
55
|
build: "tsup --config tsup.config.ts",
|
|
48
56
|
clean: "rm -rf dist",
|
|
49
57
|
test: 'yarn build && node --test "tests/**/*.test.js"',
|
|
50
|
-
release: "yarn build && npm publish --workspace sage-protocol
|
|
58
|
+
release: "yarn build && npm publish --workspace @sage-protocol/sdk"
|
|
51
59
|
},
|
|
52
60
|
dependencies: {
|
|
53
61
|
"@merit-systems/echo-typescript-sdk": "^1.0.17",
|
|
@@ -229,8 +237,11 @@ var require_abi = __commonJS({
|
|
|
229
237
|
"function routerOrVault() view returns (address)",
|
|
230
238
|
"function maxWithdrawalRate() view returns (uint256)",
|
|
231
239
|
"function emergencyWithdrawalLimit() view returns (uint256)",
|
|
240
|
+
"function getReserveTokens() view returns (address[])",
|
|
241
|
+
"function getReserve(address token) view returns (address tokenAddress,uint256 amount,uint256 value,bool isLP,bool isActive)",
|
|
232
242
|
"function pendingWithdrawals(uint256) view returns (address token,address recipient,uint256 amount,uint256 value,address requester,uint256 balanceBefore,uint256 recipientBalanceBefore,uint256 depositSnapshot,bool isLP,bool isEmergency,bool exists)",
|
|
233
243
|
"function nextWithdrawalId() view returns (uint256)",
|
|
244
|
+
"function manualPrices(address token) view returns (uint256 price,uint256 expiresAt,bool active)",
|
|
234
245
|
"function lpContributions(address,address) view returns (uint256)"
|
|
235
246
|
];
|
|
236
247
|
var GovernanceBoostMerkle = [
|
|
@@ -2265,13 +2276,26 @@ var require_ipfs = __commonJS({
|
|
|
2265
2276
|
jwt: options.pinataJwt || env.PINATA_JWT || null
|
|
2266
2277
|
},
|
|
2267
2278
|
worker: {
|
|
2268
|
-
|
|
2269
|
-
|
|
2279
|
+
baseUrl: removeTrailingSlash(options.workerBaseUrl || env.SAGE_IPFS_WORKER_URL || ""),
|
|
2280
|
+
uploadUrl: removeTrailingSlash(options.workerUploadUrl || env.SAGE_IPFS_UPLOAD_URL || ""),
|
|
2281
|
+
token: options.workerUploadToken || env.SAGE_IPFS_UPLOAD_TOKEN || "",
|
|
2282
|
+
challengePath: options.workerChallengePath || env.SAGE_IPFS_WORKER_CHALLENGE_PATH || "/auth/challenge",
|
|
2283
|
+
uploadPath: options.workerUploadPath || env.SAGE_IPFS_WORKER_UPLOAD_PATH || "/ipfs/upload",
|
|
2284
|
+
pinPath: options.workerPinPath || env.SAGE_IPFS_WORKER_PIN_PATH || "/ipfs/pin",
|
|
2285
|
+
warmPath: options.workerWarmPath || env.SAGE_IPFS_WORKER_WARM_PATH || "/ipfs/warm",
|
|
2286
|
+
pinUrl: removeTrailingSlash(options.workerPinUrl || env.SAGE_IPFS_WORKER_PIN_URL || ""),
|
|
2287
|
+
warmUrl: removeTrailingSlash(options.workerWarmUrl || env.SAGE_IPFS_WORKER_WARM_URL || ""),
|
|
2288
|
+
signer: options.workerSigner || options.signer || null,
|
|
2289
|
+
getAuth: options.workerGetAuth || null
|
|
2270
2290
|
},
|
|
2271
2291
|
web3: {
|
|
2272
2292
|
token: options.web3Token || env.W3S_TOKEN || env.WEB3_STORAGE_TOKEN || ""
|
|
2273
2293
|
}
|
|
2274
2294
|
};
|
|
2295
|
+
if (!config.worker.baseUrl && config.worker.uploadUrl) {
|
|
2296
|
+
const maybeBase = config.worker.uploadUrl.replace(/\/ipfs\/upload$/i, "");
|
|
2297
|
+
config.worker.baseUrl = removeTrailingSlash(maybeBase);
|
|
2298
|
+
}
|
|
2275
2299
|
function hasPinataCreds() {
|
|
2276
2300
|
if (config.pinata.jwt && config.pinata.jwt.length > 10) return true;
|
|
2277
2301
|
if (config.pinata.apiKey && config.pinata.secretKey) {
|
|
@@ -2279,9 +2303,79 @@ var require_ipfs = __commonJS({
|
|
|
2279
2303
|
}
|
|
2280
2304
|
return false;
|
|
2281
2305
|
}
|
|
2306
|
+
function workerHasAuth() {
|
|
2307
|
+
if (config.worker.token) return true;
|
|
2308
|
+
if (typeof config.worker.getAuth === "function") return true;
|
|
2309
|
+
if (config.worker.signer) return true;
|
|
2310
|
+
return false;
|
|
2311
|
+
}
|
|
2312
|
+
function workerBaseUrl() {
|
|
2313
|
+
return config.worker.baseUrl || "";
|
|
2314
|
+
}
|
|
2315
|
+
function workerUrl(kind, cid) {
|
|
2316
|
+
const base = workerBaseUrl();
|
|
2317
|
+
const ensure = (path) => path.startsWith("/") ? path : `/${path}`;
|
|
2318
|
+
if (base) {
|
|
2319
|
+
if (kind === "challenge") return `${base}${ensure(config.worker.challengePath || "/auth/challenge")}`;
|
|
2320
|
+
if (kind === "upload") return `${base}${ensure(config.worker.uploadPath || "/ipfs/upload")}`;
|
|
2321
|
+
if (kind === "pin") return `${base}${ensure(config.worker.pinPath || "/ipfs/pin")}`;
|
|
2322
|
+
if (kind === "warm") return `${base}${ensure(config.worker.warmPath || "/ipfs/warm")}`;
|
|
2323
|
+
if (kind === "status") return `${base}${ensure(config.worker.pinPath || "/ipfs/pin")}/${cid}`;
|
|
2324
|
+
if (kind === "delete") return `${base}${ensure(config.worker.pinPath || "/ipfs/pin")}/${cid}`;
|
|
2325
|
+
}
|
|
2326
|
+
if (kind === "upload" && config.worker.uploadUrl) return ensureEndsWith(config.worker.uploadUrl, "/upload");
|
|
2327
|
+
if (kind === "pin" && config.worker.pinUrl) return ensureEndsWith(config.worker.pinUrl, "/pin");
|
|
2328
|
+
if (kind === "warm" && config.worker.warmUrl) return ensureEndsWith(config.worker.warmUrl, "/warm");
|
|
2329
|
+
if (kind === "status" && config.worker.pinUrl) return `${ensureEndsWith(config.worker.pinUrl, "/pin")}/${cid}`;
|
|
2330
|
+
if (kind === "delete" && config.worker.pinUrl) return `${ensureEndsWith(config.worker.pinUrl, "/pin")}/${cid}`;
|
|
2331
|
+
throw new Error(`Worker endpoint for ${kind} not configured`);
|
|
2332
|
+
}
|
|
2333
|
+
async function buildWorkerAuthHeaders(extraHeaders = {}) {
|
|
2334
|
+
if (config.worker.token) {
|
|
2335
|
+
return { headers: { ...extraHeaders, Authorization: `Bearer ${config.worker.token}` }, auth: null };
|
|
2336
|
+
}
|
|
2337
|
+
if (typeof config.worker.getAuth === "function") {
|
|
2338
|
+
const result = await config.worker.getAuth();
|
|
2339
|
+
const auth = result?.auth || result;
|
|
2340
|
+
const headers = { ...extraHeaders, ...result?.headers || {} };
|
|
2341
|
+
if (auth && !headers["X-Wallet-Address"]) {
|
|
2342
|
+
headers["X-Wallet-Address"] = auth.address;
|
|
2343
|
+
headers["X-Wallet-Signature"] = auth.signature;
|
|
2344
|
+
headers["X-Wallet-Nonce"] = auth.nonce;
|
|
2345
|
+
headers["X-Wallet-Message"] = auth.message;
|
|
2346
|
+
}
|
|
2347
|
+
return { headers, auth };
|
|
2348
|
+
}
|
|
2349
|
+
if (config.worker.signer) {
|
|
2350
|
+
const signer = typeof config.worker.signer === "function" ? await config.worker.signer() : config.worker.signer;
|
|
2351
|
+
if (!signer) throw new Error("worker_signer_unavailable");
|
|
2352
|
+
const address = await signer.getAddress();
|
|
2353
|
+
const challengeResponse = await axiosInstance.post(
|
|
2354
|
+
workerUrl("challenge"),
|
|
2355
|
+
{ address },
|
|
2356
|
+
{ timeout: config.timeoutMs }
|
|
2357
|
+
);
|
|
2358
|
+
const { message, nonce } = challengeResponse?.data || {};
|
|
2359
|
+
if (!message || !nonce) throw new Error("Worker challenge response missing message/nonce");
|
|
2360
|
+
const signature = await signer.signMessage(message);
|
|
2361
|
+
const auth = { address, signature, nonce, message };
|
|
2362
|
+
const headers = {
|
|
2363
|
+
...extraHeaders,
|
|
2364
|
+
"X-Wallet-Address": auth.address,
|
|
2365
|
+
"X-Wallet-Signature": auth.signature,
|
|
2366
|
+
"X-Wallet-Nonce": auth.nonce,
|
|
2367
|
+
"X-Wallet-Message": auth.message
|
|
2368
|
+
};
|
|
2369
|
+
return { headers, auth };
|
|
2370
|
+
}
|
|
2371
|
+
throw new Error("Worker auth requires token, getAuth, or signer");
|
|
2372
|
+
}
|
|
2282
2373
|
function shouldUseWorker() {
|
|
2374
|
+
const hasEndpoint = workerBaseUrl() || config.worker.uploadUrl;
|
|
2375
|
+
if (!hasEndpoint) return false;
|
|
2376
|
+
if (!workerHasAuth()) return false;
|
|
2283
2377
|
if (config.provider === "worker") return true;
|
|
2284
|
-
if (config.provider === "auto"
|
|
2378
|
+
if (config.provider === "auto") return true;
|
|
2285
2379
|
return false;
|
|
2286
2380
|
}
|
|
2287
2381
|
function resolveProviderOrder(preference) {
|
|
@@ -2299,13 +2393,10 @@ var require_ipfs = __commonJS({
|
|
|
2299
2393
|
return order;
|
|
2300
2394
|
}
|
|
2301
2395
|
async function uploadViaWorker(payload, { warm, gateways } = {}) {
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
const
|
|
2306
|
-
const headers = { "Content-Type": "application/json" };
|
|
2307
|
-
if (config.worker.token) headers.Authorization = `Bearer ${config.worker.token}`;
|
|
2308
|
-
const response = await axiosInstance.post(url, payload, { headers, timeout: config.timeoutMs });
|
|
2396
|
+
const { headers, auth } = await buildWorkerAuthHeaders({ "Content-Type": "application/json" });
|
|
2397
|
+
const body = { ...payload };
|
|
2398
|
+
if (auth) body.auth = auth;
|
|
2399
|
+
const response = await axiosInstance.post(workerUrl("upload"), body, { headers, timeout: config.timeoutMs });
|
|
2309
2400
|
const cid = response?.data?.cid || response?.data?.IpfsHash;
|
|
2310
2401
|
if (!cid) {
|
|
2311
2402
|
throw new Error("Worker response missing cid");
|
|
@@ -2500,11 +2591,11 @@ var require_ipfs = __commonJS({
|
|
|
2500
2591
|
async function pin({ cids, warm = false, gateways } = {}) {
|
|
2501
2592
|
const list = Array.isArray(cids) ? cids : [cids];
|
|
2502
2593
|
if (!list.length) return { provider: null, ok: false, reason: "no cids provided" };
|
|
2503
|
-
if (shouldUseWorker()
|
|
2504
|
-
const
|
|
2505
|
-
const
|
|
2506
|
-
if (
|
|
2507
|
-
const response = await axiosInstance.post(
|
|
2594
|
+
if (shouldUseWorker()) {
|
|
2595
|
+
const { headers, auth } = await buildWorkerAuthHeaders({ "Content-Type": "application/json" });
|
|
2596
|
+
const body = { cids: list };
|
|
2597
|
+
if (auth) body.auth = auth;
|
|
2598
|
+
const response = await axiosInstance.post(workerUrl("pin"), body, { headers, timeout: config.timeoutMs });
|
|
2508
2599
|
if (warm || config.shouldWarm) {
|
|
2509
2600
|
await Promise.all(list.map((cid) => warmGateways(cid, { gateways }).catch(() => null)));
|
|
2510
2601
|
}
|
|
@@ -3712,6 +3803,399 @@ var require_prompt = __commonJS({
|
|
|
3712
3803
|
}
|
|
3713
3804
|
});
|
|
3714
3805
|
|
|
3806
|
+
// src/ipns/index.js
|
|
3807
|
+
var require_ipns = __commonJS({
|
|
3808
|
+
"src/ipns/index.js"(exports, module) {
|
|
3809
|
+
var axiosDefault = __require("axios");
|
|
3810
|
+
var contentHashLib = null;
|
|
3811
|
+
try {
|
|
3812
|
+
contentHashLib = __require("content-hash");
|
|
3813
|
+
} catch (_) {
|
|
3814
|
+
contentHashLib = null;
|
|
3815
|
+
}
|
|
3816
|
+
var FALLBACK_GATEWAYS = [
|
|
3817
|
+
"https://ipfs.dev.sageprotocol.io",
|
|
3818
|
+
"https://cf-ipfs.com",
|
|
3819
|
+
"https://dweb.link",
|
|
3820
|
+
"https://inbrowser.link"
|
|
3821
|
+
];
|
|
3822
|
+
function removeTrailingSlash(str) {
|
|
3823
|
+
return str ? str.replace(/\/$/, "") : str;
|
|
3824
|
+
}
|
|
3825
|
+
function ensureLeadingSlash(path) {
|
|
3826
|
+
if (!path) return "/";
|
|
3827
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
3828
|
+
}
|
|
3829
|
+
function dedupe(list = []) {
|
|
3830
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3831
|
+
const output = [];
|
|
3832
|
+
for (const value of list) {
|
|
3833
|
+
const key = typeof value === "string" ? value.trim() : "";
|
|
3834
|
+
if (!key || seen.has(key.toLowerCase())) continue;
|
|
3835
|
+
seen.add(key.toLowerCase());
|
|
3836
|
+
output.push(key.replace(/\/$/, ""));
|
|
3837
|
+
}
|
|
3838
|
+
return output;
|
|
3839
|
+
}
|
|
3840
|
+
function delay(ms) {
|
|
3841
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3842
|
+
}
|
|
3843
|
+
function jitter(base, jitterMax) {
|
|
3844
|
+
if (!jitterMax) return base;
|
|
3845
|
+
const offset = Math.floor(Math.random() * jitterMax);
|
|
3846
|
+
return base + offset;
|
|
3847
|
+
}
|
|
3848
|
+
function normalizeIpnsName(input) {
|
|
3849
|
+
if (!input) return null;
|
|
3850
|
+
const str = String(input).trim();
|
|
3851
|
+
if (!str) return null;
|
|
3852
|
+
if (str.startsWith("ipns://")) return str.slice("ipns://".length);
|
|
3853
|
+
if (str.startsWith("/ipns/")) return str.slice("/ipns/".length);
|
|
3854
|
+
if (str.startsWith("k")) return str;
|
|
3855
|
+
return str;
|
|
3856
|
+
}
|
|
3857
|
+
function normalizeCid(input) {
|
|
3858
|
+
if (!input) return null;
|
|
3859
|
+
const str = String(input).trim();
|
|
3860
|
+
if (!str) return null;
|
|
3861
|
+
const cidMatcher = /^[a-z0-9]{46,}$/i;
|
|
3862
|
+
return cidMatcher.test(str) ? str : null;
|
|
3863
|
+
}
|
|
3864
|
+
async function fetchEnsRecords(name, provider, { textKeys }) {
|
|
3865
|
+
if (!name || !provider) return { contenthash: null, text: {}, resolverAddress: null };
|
|
3866
|
+
try {
|
|
3867
|
+
const resolver = await provider.getResolver(name);
|
|
3868
|
+
if (!resolver) {
|
|
3869
|
+
return { contenthash: null, text: {}, resolverAddress: null };
|
|
3870
|
+
}
|
|
3871
|
+
const result = { contenthash: null, text: {}, resolverAddress: resolver.address };
|
|
3872
|
+
try {
|
|
3873
|
+
const hash = await resolver.getContentHash();
|
|
3874
|
+
if (hash && hash !== "0x") {
|
|
3875
|
+
result.contenthash = hash;
|
|
3876
|
+
}
|
|
3877
|
+
} catch (_) {
|
|
3878
|
+
}
|
|
3879
|
+
const entries = Array.isArray(textKeys) ? textKeys : [];
|
|
3880
|
+
for (const key of entries) {
|
|
3881
|
+
try {
|
|
3882
|
+
const value = await resolver.getText(key);
|
|
3883
|
+
if (value) {
|
|
3884
|
+
result.text[key] = value;
|
|
3885
|
+
}
|
|
3886
|
+
} catch (_) {
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
return result;
|
|
3890
|
+
} catch (error) {
|
|
3891
|
+
const wrapped = new Error(`Failed to resolve ENS records for ${name}: ${error.message}`);
|
|
3892
|
+
wrapped.cause = error;
|
|
3893
|
+
throw wrapped;
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
function decodeContenthash(hash) {
|
|
3897
|
+
if (!hash || hash === "0x") return null;
|
|
3898
|
+
if (typeof hash === "string") {
|
|
3899
|
+
const trimmed = hash.trim();
|
|
3900
|
+
if (trimmed.startsWith("ipns://") || trimmed.startsWith("/ipns/") || trimmed.startsWith("ipfs://") || trimmed.startsWith("/ipfs/")) {
|
|
3901
|
+
return trimmed;
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
if (contentHashLib) {
|
|
3905
|
+
try {
|
|
3906
|
+
return contentHashLib.decode(hash);
|
|
3907
|
+
} catch (_) {
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
try {
|
|
3911
|
+
if (hash.startsWith("0x")) {
|
|
3912
|
+
const bytes = Buffer.from(hash.slice(2), "hex");
|
|
3913
|
+
const utf = bytes.toString("utf8");
|
|
3914
|
+
if (utf.startsWith("ipns://") || utf.startsWith("/ipns/") || utf.startsWith("ipfs://") || utf.startsWith("/ipfs/")) {
|
|
3915
|
+
return utf;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
} catch (_) {
|
|
3919
|
+
}
|
|
3920
|
+
return null;
|
|
3921
|
+
}
|
|
3922
|
+
async function fetchWithGateways(path, axiosInstance, gateways, { timeout, attempts = 3, baseDelay = 500, jitterMs = 250 } = {}) {
|
|
3923
|
+
const errors = [];
|
|
3924
|
+
const unique = dedupe(gateways);
|
|
3925
|
+
const suffix = path.replace(/^\/+/, "");
|
|
3926
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
3927
|
+
const delayMs = attempt === 0 ? 0 : jitter(baseDelay * attempt, jitterMs);
|
|
3928
|
+
if (delayMs) await delay(delayMs);
|
|
3929
|
+
for (const gateway of unique) {
|
|
3930
|
+
const url = `${gateway.replace(/\/$/, "")}/${suffix}`;
|
|
3931
|
+
try {
|
|
3932
|
+
const response = await axiosInstance.get(url, {
|
|
3933
|
+
timeout,
|
|
3934
|
+
headers: { Accept: "application/json" }
|
|
3935
|
+
});
|
|
3936
|
+
return {
|
|
3937
|
+
data: response.data,
|
|
3938
|
+
cid: extractCidFromResponse(response, suffix),
|
|
3939
|
+
gateway,
|
|
3940
|
+
url
|
|
3941
|
+
};
|
|
3942
|
+
} catch (error) {
|
|
3943
|
+
errors.push({ url, error });
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
const failure = new Error(`Failed to fetch ${path} from gateways`);
|
|
3948
|
+
failure.attempts = errors;
|
|
3949
|
+
throw failure;
|
|
3950
|
+
}
|
|
3951
|
+
function extractCidFromResponse(response, fallbackPath) {
|
|
3952
|
+
if (!response) return null;
|
|
3953
|
+
const headerPath = response.headers?.["x-ipfs-path"] || response.headers?.["x-ipfs-origin-path"];
|
|
3954
|
+
if (headerPath && typeof headerPath === "string") {
|
|
3955
|
+
const match = headerPath.match(/\/ipfs\/([^\/?#]+)/i);
|
|
3956
|
+
if (match && match[1]) return match[1];
|
|
3957
|
+
}
|
|
3958
|
+
const url = response.request?.res?.responseUrl || response.config?.url;
|
|
3959
|
+
if (url) {
|
|
3960
|
+
const match = url.match(/\/ipfs\/([^\/?#]+)/i);
|
|
3961
|
+
if (match && match[1]) return match[1];
|
|
3962
|
+
}
|
|
3963
|
+
if (fallbackPath) {
|
|
3964
|
+
const match = fallbackPath.match(/\/ipfs\/([^\/?#]+)/i);
|
|
3965
|
+
if (match && match[1]) return match[1];
|
|
3966
|
+
}
|
|
3967
|
+
return null;
|
|
3968
|
+
}
|
|
3969
|
+
function createClient(options = {}) {
|
|
3970
|
+
const axiosInstance = options.axiosInstance || axiosDefault;
|
|
3971
|
+
const provider = options.provider || null;
|
|
3972
|
+
const publishImplementation = typeof options.publish === "function" ? options.publish : null;
|
|
3973
|
+
const workerConfig = options.worker || options.publishWorker || null;
|
|
3974
|
+
const gateways = dedupe([
|
|
3975
|
+
...Array.isArray(options.gateways) ? options.gateways : options.gateways ? String(options.gateways).split(",") : [],
|
|
3976
|
+
...options.projectGateway ? [options.projectGateway] : [],
|
|
3977
|
+
...FALLBACK_GATEWAYS
|
|
3978
|
+
]);
|
|
3979
|
+
const retry = {
|
|
3980
|
+
attempts: Math.max(1, Number(options.retryAttempts ?? 3)),
|
|
3981
|
+
baseDelay: Math.max(100, Number(options.retryDelayMs ?? 750)),
|
|
3982
|
+
jitterMs: Math.max(0, Number(options.retryJitterMs ?? 500))
|
|
3983
|
+
};
|
|
3984
|
+
const timeout = Math.max(1e3, Number(options.timeoutMs ?? 7500));
|
|
3985
|
+
function resolvePointerFromContenthash(decoded) {
|
|
3986
|
+
if (!decoded) return null;
|
|
3987
|
+
if (decoded.startsWith("ipns://")) return { type: "ipns", value: decoded.slice("ipns://".length) };
|
|
3988
|
+
if (decoded.startsWith("/ipns/")) return { type: "ipns", value: decoded.slice("/ipns/".length) };
|
|
3989
|
+
if (decoded.startsWith("ipfs://")) return { type: "cid", value: decoded.slice("ipfs://".length) };
|
|
3990
|
+
if (decoded.startsWith("/ipfs/")) return { type: "cid", value: decoded.slice("/ipfs/".length) };
|
|
3991
|
+
const normalizedCid = normalizeCid(decoded);
|
|
3992
|
+
if (normalizedCid) return { type: "cid", value: normalizedCid };
|
|
3993
|
+
const normalizedIpns = normalizeIpnsName(decoded);
|
|
3994
|
+
if (normalizedIpns) return { type: "ipns", value: normalizedIpns };
|
|
3995
|
+
return null;
|
|
3996
|
+
}
|
|
3997
|
+
function pointerFromTextRecords(textRecords = {}) {
|
|
3998
|
+
const priorityKeys = ["sage:ipns", "sage:latestIPNS", "ipns"];
|
|
3999
|
+
for (const key of priorityKeys) {
|
|
4000
|
+
const candidate = normalizeIpnsName(textRecords[key]);
|
|
4001
|
+
if (candidate) return { type: "ipns", value: candidate, source: `ens:text:${key}` };
|
|
4002
|
+
}
|
|
4003
|
+
const cidKeys = ["sage:cid", "sage:latestCID", "cid"];
|
|
4004
|
+
for (const key of cidKeys) {
|
|
4005
|
+
const candidate = normalizeCid(textRecords[key]);
|
|
4006
|
+
if (candidate) return { type: "cid", value: candidate, source: `ens:text:${key}` };
|
|
4007
|
+
}
|
|
4008
|
+
return null;
|
|
4009
|
+
}
|
|
4010
|
+
async function resolveLatestManifest(args = {}) {
|
|
4011
|
+
const attempts = [];
|
|
4012
|
+
const pointerCandidates = [];
|
|
4013
|
+
const ensName = args.ensName || args.name || null;
|
|
4014
|
+
const explicitIpns = normalizeIpnsName(args.ipnsName || args.pointer || null);
|
|
4015
|
+
const fallbackCid = normalizeCid(args.fallbackCid || args.cid || null);
|
|
4016
|
+
const localIpns = normalizeIpnsName(args.profileIpns || options.defaultIpnsName || null);
|
|
4017
|
+
const localCid = normalizeCid(args.profileCid || options.defaultCid || null);
|
|
4018
|
+
const ensProvider = args.provider || provider;
|
|
4019
|
+
if (ensName && ensProvider) {
|
|
4020
|
+
try {
|
|
4021
|
+
const records = await fetchEnsRecords(ensName, ensProvider, {
|
|
4022
|
+
textKeys: ["sage:ipns", "sage:latestIPNS", "sage:cid", "sage:latestCID", "ipns", "cid"]
|
|
4023
|
+
});
|
|
4024
|
+
const decoded = resolvePointerFromContenthash(decodeContenthash(records.contenthash));
|
|
4025
|
+
if (decoded) {
|
|
4026
|
+
pointerCandidates.push({ ...decoded, source: "ens:contenthash" });
|
|
4027
|
+
}
|
|
4028
|
+
const fromText = pointerFromTextRecords(records.text);
|
|
4029
|
+
if (fromText) {
|
|
4030
|
+
pointerCandidates.push(fromText);
|
|
4031
|
+
}
|
|
4032
|
+
} catch (error2) {
|
|
4033
|
+
attempts.push({ type: "ens", name: ensName, error: error2 });
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
if (explicitIpns) {
|
|
4037
|
+
pointerCandidates.push({ type: "ipns", value: explicitIpns, source: "input:ipns" });
|
|
4038
|
+
}
|
|
4039
|
+
if (fallbackCid) {
|
|
4040
|
+
pointerCandidates.push({ type: "cid", value: fallbackCid, source: "input:cid" });
|
|
4041
|
+
}
|
|
4042
|
+
if (localIpns) {
|
|
4043
|
+
pointerCandidates.push({ type: "ipns", value: localIpns, source: "profile:ipns" });
|
|
4044
|
+
}
|
|
4045
|
+
if (localCid) {
|
|
4046
|
+
pointerCandidates.push({ type: "cid", value: localCid, source: "profile:cid" });
|
|
4047
|
+
}
|
|
4048
|
+
const visited = /* @__PURE__ */ new Set();
|
|
4049
|
+
for (const pointer of pointerCandidates) {
|
|
4050
|
+
const key = `${pointer.type}:${pointer.value}`;
|
|
4051
|
+
if (!pointer.value || visited.has(key)) continue;
|
|
4052
|
+
visited.add(key);
|
|
4053
|
+
try {
|
|
4054
|
+
if (pointer.type === "ipns") {
|
|
4055
|
+
const result = await fetchWithGateways(`/ipns/${pointer.value}`, axiosInstance, gateways, {
|
|
4056
|
+
timeout,
|
|
4057
|
+
attempts: retry.attempts,
|
|
4058
|
+
baseDelay: retry.baseDelay,
|
|
4059
|
+
jitterMs: retry.jitterMs
|
|
4060
|
+
});
|
|
4061
|
+
return {
|
|
4062
|
+
manifest: result.data,
|
|
4063
|
+
cid: result.cid,
|
|
4064
|
+
pointer,
|
|
4065
|
+
gateway: result.gateway,
|
|
4066
|
+
url: result.url,
|
|
4067
|
+
attempts
|
|
4068
|
+
};
|
|
4069
|
+
}
|
|
4070
|
+
if (pointer.type === "cid") {
|
|
4071
|
+
const result = await fetchWithGateways(`/ipfs/${pointer.value}`, axiosInstance, gateways, {
|
|
4072
|
+
timeout,
|
|
4073
|
+
attempts: retry.attempts,
|
|
4074
|
+
baseDelay: retry.baseDelay,
|
|
4075
|
+
jitterMs: retry.jitterMs
|
|
4076
|
+
});
|
|
4077
|
+
return {
|
|
4078
|
+
manifest: result.data,
|
|
4079
|
+
cid: pointer.value,
|
|
4080
|
+
pointer,
|
|
4081
|
+
gateway: result.gateway,
|
|
4082
|
+
url: result.url,
|
|
4083
|
+
attempts
|
|
4084
|
+
};
|
|
4085
|
+
}
|
|
4086
|
+
} catch (error2) {
|
|
4087
|
+
attempts.push({ type: pointer.type, value: pointer.value, error: error2 });
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
const error = new Error("Unable to resolve latest manifest");
|
|
4091
|
+
error.attempts = attempts;
|
|
4092
|
+
throw error;
|
|
4093
|
+
}
|
|
4094
|
+
async function buildWorkerAuthHeaders(worker, extraHeaders = {}) {
|
|
4095
|
+
if (!worker) throw new Error("worker configuration required");
|
|
4096
|
+
if (worker.token) {
|
|
4097
|
+
return { headers: { ...extraHeaders, Authorization: `Bearer ${worker.token}` }, auth: null };
|
|
4098
|
+
}
|
|
4099
|
+
if (typeof worker.getAuth === "function") {
|
|
4100
|
+
const result = await worker.getAuth();
|
|
4101
|
+
const auth = result?.auth || result;
|
|
4102
|
+
const headers = { ...extraHeaders, ...result?.headers || {} };
|
|
4103
|
+
if (auth) {
|
|
4104
|
+
headers["X-Wallet-Address"] = auth.address;
|
|
4105
|
+
headers["X-Wallet-Signature"] = auth.signature;
|
|
4106
|
+
headers["X-Wallet-Nonce"] = auth.nonce;
|
|
4107
|
+
headers["X-Wallet-Message"] = auth.message;
|
|
4108
|
+
}
|
|
4109
|
+
return { headers, auth };
|
|
4110
|
+
}
|
|
4111
|
+
if (worker.signer) {
|
|
4112
|
+
const signer = typeof worker.signer === "function" ? await worker.signer() : worker.signer;
|
|
4113
|
+
if (!signer) throw new Error("worker_signer_unavailable");
|
|
4114
|
+
const address = await signer.getAddress();
|
|
4115
|
+
const challengeUrl = `${worker.baseUrl}${worker.challengePath}`;
|
|
4116
|
+
const challengeResponse = await axiosInstance.post(
|
|
4117
|
+
challengeUrl,
|
|
4118
|
+
{ address },
|
|
4119
|
+
{ timeout: worker.timeout }
|
|
4120
|
+
);
|
|
4121
|
+
const { message, nonce } = challengeResponse?.data || {};
|
|
4122
|
+
if (!message || !nonce) throw new Error("Worker challenge response missing message/nonce");
|
|
4123
|
+
const signature = await signer.signMessage(message);
|
|
4124
|
+
const auth = { address, signature, nonce, message };
|
|
4125
|
+
const headers = {
|
|
4126
|
+
...extraHeaders,
|
|
4127
|
+
"X-Wallet-Address": auth.address,
|
|
4128
|
+
"X-Wallet-Signature": auth.signature,
|
|
4129
|
+
"X-Wallet-Nonce": auth.nonce,
|
|
4130
|
+
"X-Wallet-Message": auth.message
|
|
4131
|
+
};
|
|
4132
|
+
return { headers, auth };
|
|
4133
|
+
}
|
|
4134
|
+
throw new Error("Worker auth requires token, getAuth, or signer");
|
|
4135
|
+
}
|
|
4136
|
+
async function publishViaWorker(args, worker) {
|
|
4137
|
+
if (!worker?.baseUrl) {
|
|
4138
|
+
throw new Error("Worker baseUrl required for IPNS publish");
|
|
4139
|
+
}
|
|
4140
|
+
const { headers } = await buildWorkerAuthHeaders(worker, { "Content-Type": "application/json" });
|
|
4141
|
+
const body = {
|
|
4142
|
+
ipnsName: args.ipnsName,
|
|
4143
|
+
manifestCid: args.manifestCid,
|
|
4144
|
+
metadata: args.metadata || null
|
|
4145
|
+
};
|
|
4146
|
+
if (args.overrides) body.overrides = args.overrides;
|
|
4147
|
+
const publishUrl = `${worker.baseUrl}${worker.publishPath}`;
|
|
4148
|
+
const response = await axiosInstance.post(publishUrl, body, { headers, timeout: worker.timeout });
|
|
4149
|
+
return response?.data || null;
|
|
4150
|
+
}
|
|
4151
|
+
async function publishManifest(args = {}) {
|
|
4152
|
+
if (publishImplementation) {
|
|
4153
|
+
return publishImplementation(args, {
|
|
4154
|
+
axiosInstance,
|
|
4155
|
+
config: { gateways, timeout, retry }
|
|
4156
|
+
});
|
|
4157
|
+
}
|
|
4158
|
+
const manifestCid = normalizeCid(args.manifestCid || args.cid);
|
|
4159
|
+
if (!manifestCid) {
|
|
4160
|
+
throw new Error("manifestCid required for IPNS publish");
|
|
4161
|
+
}
|
|
4162
|
+
const ipnsName = normalizeIpnsName(args.ipnsName || args.name || options.defaultIpnsName);
|
|
4163
|
+
if (!ipnsName) {
|
|
4164
|
+
throw new Error("ipnsName required for IPNS publish");
|
|
4165
|
+
}
|
|
4166
|
+
const worker = workerConfig ? {
|
|
4167
|
+
baseUrl: removeTrailingSlash(workerConfig.baseUrl || workerConfig.url || workerConfig.workerBaseUrl || ""),
|
|
4168
|
+
challengePath: ensureLeadingSlash(workerConfig.challengePath || workerConfig.challenge || "/auth/challenge"),
|
|
4169
|
+
publishPath: ensureLeadingSlash(workerConfig.publishPath || workerConfig.publish || "/ipns/publish"),
|
|
4170
|
+
token: workerConfig.token || null,
|
|
4171
|
+
getAuth: workerConfig.getAuth || null,
|
|
4172
|
+
signer: args.signer || workerConfig.signer || null,
|
|
4173
|
+
timeout: Math.max(1e3, Number(workerConfig.timeoutMs ?? timeout))
|
|
4174
|
+
} : null;
|
|
4175
|
+
if (worker) {
|
|
4176
|
+
return publishViaWorker({ ...args, manifestCid, ipnsName }, worker);
|
|
4177
|
+
}
|
|
4178
|
+
throw new Error("IPNS publish helpers not configured yet. Provide options.publish or integrate with worker.");
|
|
4179
|
+
}
|
|
4180
|
+
return {
|
|
4181
|
+
resolveLatestManifest,
|
|
4182
|
+
publishManifest,
|
|
4183
|
+
config: {
|
|
4184
|
+
gateways,
|
|
4185
|
+
timeout,
|
|
4186
|
+
retry
|
|
4187
|
+
}
|
|
4188
|
+
};
|
|
4189
|
+
}
|
|
4190
|
+
module.exports = {
|
|
4191
|
+
createClient,
|
|
4192
|
+
normalizeIpnsName,
|
|
4193
|
+
normalizeCid,
|
|
4194
|
+
FALLBACK_GATEWAYS
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
});
|
|
4198
|
+
|
|
3715
4199
|
// src/token/index.js
|
|
3716
4200
|
var require_token = __commonJS({
|
|
3717
4201
|
"src/token/index.js"(exports, module) {
|
|
@@ -4236,6 +4720,11 @@ var require_treasury = __commonJS({
|
|
|
4236
4720
|
"event LiquidityRemovePlanned(address indexed subdao, address indexed pool, address lpToken, uint256 lpAmount, address recipient)"
|
|
4237
4721
|
]);
|
|
4238
4722
|
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
4723
|
+
var ERC20_METADATA_ABI = [
|
|
4724
|
+
"function decimals() view returns (uint8)",
|
|
4725
|
+
"function symbol() view returns (string)",
|
|
4726
|
+
"function name() view returns (string)"
|
|
4727
|
+
];
|
|
4239
4728
|
function normalise(address, label) {
|
|
4240
4729
|
if (!address) throw new SageSDKError(CODES.INVALID_ARGS, `${label} required`);
|
|
4241
4730
|
try {
|
|
@@ -4322,6 +4811,77 @@ var require_treasury = __commonJS({
|
|
|
4322
4811
|
}
|
|
4323
4812
|
return withdrawals;
|
|
4324
4813
|
}
|
|
4814
|
+
async function getReserveTokens({ provider, treasury }) {
|
|
4815
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4816
|
+
const addr = normalise(treasury, "treasury");
|
|
4817
|
+
const contract = new Contract(addr, ABI.SageTreasury, provider);
|
|
4818
|
+
const tokens = await contract.getReserveTokens().catch(() => []);
|
|
4819
|
+
return tokens.map((token) => getAddress(token));
|
|
4820
|
+
}
|
|
4821
|
+
async function getReserves({ provider, treasury, tokens, fetchMetadata = true }) {
|
|
4822
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4823
|
+
const addr = normalise(treasury, "treasury");
|
|
4824
|
+
const contract = new Contract(addr, ABI.SageTreasury, provider);
|
|
4825
|
+
const tokenList = Array.isArray(tokens) && tokens.length ? tokens.map((token) => getAddress(token)) : await contract.getReserveTokens().catch(() => []);
|
|
4826
|
+
const reserves = [];
|
|
4827
|
+
for (const token of tokenList) {
|
|
4828
|
+
const reserve = await contract.getReserve(token).catch(() => null);
|
|
4829
|
+
if (!reserve) continue;
|
|
4830
|
+
const isActive = reserve.isActive ?? reserve[4] ?? false;
|
|
4831
|
+
if (!isActive) continue;
|
|
4832
|
+
const amountRaw = reserve.amount ?? reserve[1] ?? 0n;
|
|
4833
|
+
const valueRaw = reserve.value ?? reserve[2] ?? 0n;
|
|
4834
|
+
const isLP = reserve.isLP ?? reserve[3] ?? false;
|
|
4835
|
+
let metadata = {};
|
|
4836
|
+
if (fetchMetadata) {
|
|
4837
|
+
metadata = await readTokenMetadata(provider, token).catch(() => ({}));
|
|
4838
|
+
}
|
|
4839
|
+
reserves.push({
|
|
4840
|
+
token: getAddress(token),
|
|
4841
|
+
amount: toBigInt(amountRaw),
|
|
4842
|
+
value: toBigInt(valueRaw),
|
|
4843
|
+
isLP: Boolean(isLP),
|
|
4844
|
+
decimals: metadata.decimals ?? null,
|
|
4845
|
+
symbol: metadata.symbol ?? null,
|
|
4846
|
+
name: metadata.name ?? null
|
|
4847
|
+
});
|
|
4848
|
+
}
|
|
4849
|
+
return reserves;
|
|
4850
|
+
}
|
|
4851
|
+
async function getManualPriceOverrides({ provider, treasury, tokens }) {
|
|
4852
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4853
|
+
const addr = normalise(treasury, "treasury");
|
|
4854
|
+
const contract = new Contract(addr, ABI.SageTreasury, provider);
|
|
4855
|
+
const tokenList = Array.isArray(tokens) && tokens.length ? tokens.map((token) => getAddress(token)) : await contract.getReserveTokens().catch(() => []);
|
|
4856
|
+
const overrides = [];
|
|
4857
|
+
for (const token of tokenList) {
|
|
4858
|
+
const cfg = await contract.manualPrices(token).catch(() => null);
|
|
4859
|
+
const active = cfg?.active ?? cfg?.[2] ?? false;
|
|
4860
|
+
if (!active) continue;
|
|
4861
|
+
const priceRaw = cfg.price ?? cfg[0] ?? 0n;
|
|
4862
|
+
const expiresAtRaw = cfg.expiresAt ?? cfg[1] ?? 0n;
|
|
4863
|
+
overrides.push({
|
|
4864
|
+
token: getAddress(token),
|
|
4865
|
+
price: toBigInt(priceRaw),
|
|
4866
|
+
expiresAt: Number(expiresAtRaw),
|
|
4867
|
+
active: Boolean(active)
|
|
4868
|
+
});
|
|
4869
|
+
}
|
|
4870
|
+
return overrides;
|
|
4871
|
+
}
|
|
4872
|
+
async function readTokenMetadata(provider, token) {
|
|
4873
|
+
const contract = new Contract(token, ERC20_METADATA_ABI, provider);
|
|
4874
|
+
const [decimals, symbol, name] = await Promise.all([
|
|
4875
|
+
contract.decimals().catch(() => null),
|
|
4876
|
+
contract.symbol().catch(() => null),
|
|
4877
|
+
contract.name().catch(() => null)
|
|
4878
|
+
]);
|
|
4879
|
+
return {
|
|
4880
|
+
decimals: decimals != null ? Number(decimals) : null,
|
|
4881
|
+
symbol: symbol || null,
|
|
4882
|
+
name: name || null
|
|
4883
|
+
};
|
|
4884
|
+
}
|
|
4325
4885
|
function toBigInt(value) {
|
|
4326
4886
|
if (value == null) return 0n;
|
|
4327
4887
|
try {
|
|
@@ -4400,11 +4960,82 @@ var require_treasury = __commonJS({
|
|
|
4400
4960
|
const amount = await c.lpContributions(normalise(subdao, "subdao"), normalise(token, "token")).catch(() => 0n);
|
|
4401
4961
|
return amount;
|
|
4402
4962
|
}
|
|
4963
|
+
async function scheduleWithdrawal({ signer, treasury, token, amount, recipient, waitMs }) {
|
|
4964
|
+
const contract = createWriteContract({ signer, treasury });
|
|
4965
|
+
const tx = await contract.withdraw(normalise(token, "token"), toBigInt(amount), getAddress(recipient));
|
|
4966
|
+
const receipt = await waitForReceipt({ signer, tx, waitMs });
|
|
4967
|
+
const parsed = parseLog(contract, receipt, "WithdrawalScheduled");
|
|
4968
|
+
return {
|
|
4969
|
+
transaction: tx,
|
|
4970
|
+
receipt,
|
|
4971
|
+
id: parsed?.id != null ? Number(parsed.id) : null
|
|
4972
|
+
};
|
|
4973
|
+
}
|
|
4974
|
+
async function confirmWithdrawal({ signer, treasury, id, waitMs }) {
|
|
4975
|
+
const contract = createWriteContract({ signer, treasury });
|
|
4976
|
+
const tx = await contract.confirmWithdrawal(Number(id));
|
|
4977
|
+
const receipt = await waitForReceipt({ signer, tx, waitMs });
|
|
4978
|
+
return { transaction: tx, receipt, id: Number(id) };
|
|
4979
|
+
}
|
|
4980
|
+
async function cancelWithdrawal({ signer, treasury, id, waitMs }) {
|
|
4981
|
+
const contract = createWriteContract({ signer, treasury });
|
|
4982
|
+
const tx = await contract.cancelWithdrawal(Number(id));
|
|
4983
|
+
const receipt = await waitForReceipt({ signer, tx, waitMs });
|
|
4984
|
+
return { transaction: tx, receipt, id: Number(id) };
|
|
4985
|
+
}
|
|
4986
|
+
async function setPriceOverride({ signer, treasury, token, price, ttlSeconds, waitMs }) {
|
|
4987
|
+
const contract = createWriteContract({ signer, treasury });
|
|
4988
|
+
const tx = await contract.setPriceOverride(normalise(token, "token"), toBigInt(price), BigInt(ttlSeconds));
|
|
4989
|
+
const receipt = await waitForReceipt({ signer, tx, waitMs });
|
|
4990
|
+
return { transaction: tx, receipt };
|
|
4991
|
+
}
|
|
4992
|
+
async function clearPriceOverride({ signer, treasury, token, waitMs }) {
|
|
4993
|
+
const contract = createWriteContract({ signer, treasury });
|
|
4994
|
+
const tx = await contract.clearPriceOverride(normalise(token, "token"));
|
|
4995
|
+
const receipt = await waitForReceipt({ signer, tx, waitMs });
|
|
4996
|
+
return { transaction: tx, receipt };
|
|
4997
|
+
}
|
|
4998
|
+
function createWriteContract({ signer, treasury }) {
|
|
4999
|
+
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
5000
|
+
const addr = normalise(treasury, "treasury");
|
|
5001
|
+
return new Contract(addr, ABI.SageTreasury, signer);
|
|
5002
|
+
}
|
|
5003
|
+
async function waitForReceipt({ signer, tx, waitMs }) {
|
|
5004
|
+
const provider = signer.provider;
|
|
5005
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "signer missing provider");
|
|
5006
|
+
const timeoutMs = waitMs ?? 6e4;
|
|
5007
|
+
const receipt = await provider.waitForTransaction(tx.hash, 1, timeoutMs);
|
|
5008
|
+
if (!receipt) {
|
|
5009
|
+
throw new SageSDKError(CODES.TIMEOUT, "transaction confirmation timed out", { hash: tx.hash });
|
|
5010
|
+
}
|
|
5011
|
+
if (receipt.status === 0) {
|
|
5012
|
+
throw new SageSDKError(CODES.TX_REVERTED, "transaction reverted", { hash: receipt.hash });
|
|
5013
|
+
}
|
|
5014
|
+
return receipt;
|
|
5015
|
+
}
|
|
5016
|
+
function parseLog(contract, receipt, eventName) {
|
|
5017
|
+
try {
|
|
5018
|
+
for (const log of receipt.logs || []) {
|
|
5019
|
+
const parsed = contract.interface.parseLog(log);
|
|
5020
|
+
if (parsed?.name === eventName) return parsed.args;
|
|
5021
|
+
}
|
|
5022
|
+
} catch (_) {
|
|
5023
|
+
}
|
|
5024
|
+
return null;
|
|
5025
|
+
}
|
|
4403
5026
|
module.exports = {
|
|
4404
5027
|
getTreasuryInfo,
|
|
4405
5028
|
getPendingWithdrawals,
|
|
4406
5029
|
getCanonicalLiquidityPlans,
|
|
4407
|
-
getLPContribution
|
|
5030
|
+
getLPContribution,
|
|
5031
|
+
getReserveTokens,
|
|
5032
|
+
getReserves,
|
|
5033
|
+
getManualPriceOverrides,
|
|
5034
|
+
scheduleWithdrawal,
|
|
5035
|
+
confirmWithdrawal,
|
|
5036
|
+
cancelWithdrawal,
|
|
5037
|
+
setPriceOverride,
|
|
5038
|
+
clearPriceOverride
|
|
4408
5039
|
};
|
|
4409
5040
|
}
|
|
4410
5041
|
});
|
|
@@ -4658,7 +5289,15 @@ var require_bounty = __commonJS({
|
|
|
4658
5289
|
// src/bond/index.js
|
|
4659
5290
|
var require_bond = __commonJS({
|
|
4660
5291
|
"src/bond/index.js"(exports, module) {
|
|
4661
|
-
var {
|
|
5292
|
+
var {
|
|
5293
|
+
Contract,
|
|
5294
|
+
Interface,
|
|
5295
|
+
MaxUint256,
|
|
5296
|
+
WeiPerEther,
|
|
5297
|
+
formatUnits,
|
|
5298
|
+
getAddress,
|
|
5299
|
+
parseUnits
|
|
5300
|
+
} = __require("ethers");
|
|
4662
5301
|
var ABI = require_abi();
|
|
4663
5302
|
var { SageSDKError, CODES } = require_errors();
|
|
4664
5303
|
function normalise(address, label) {
|
|
@@ -4675,6 +5314,7 @@ var require_bond = __commonJS({
|
|
|
4675
5314
|
"function approve(address spender, uint256 value) returns (bool)",
|
|
4676
5315
|
"function balanceOf(address) view returns (uint256)"
|
|
4677
5316
|
];
|
|
5317
|
+
var BondInterface = new Interface(ABI.BondDepository);
|
|
4678
5318
|
async function getInfo({ provider, bond }) {
|
|
4679
5319
|
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4680
5320
|
const addr = normalise(bond, "bond");
|
|
@@ -4765,6 +5405,113 @@ var require_bond = __commonJS({
|
|
|
4765
5405
|
]);
|
|
4766
5406
|
return { payoutToken, principalToken };
|
|
4767
5407
|
}
|
|
5408
|
+
async function estimatePayout({ provider, bond, amount, principalDecimals, payoutDecimals }) {
|
|
5409
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
5410
|
+
if (amount == null) throw new SageSDKError(CODES.INVALID_ARGS, "amount required");
|
|
5411
|
+
const { principalToken, payoutToken } = await getPrincipalAndPayout({ provider, bond });
|
|
5412
|
+
const principalDec = principalDecimals ?? await getTokenDecimals({ provider, token: principalToken });
|
|
5413
|
+
const payoutDec = payoutDecimals ?? await getTokenDecimals({ provider, token: payoutToken });
|
|
5414
|
+
const amountUnits = parseUnits(String(amount), principalDec);
|
|
5415
|
+
const price = await getPrice({ provider, bond });
|
|
5416
|
+
if (price === 0n) {
|
|
5417
|
+
throw new SageSDKError(CODES.RPC_ERROR, "bond price returned zero");
|
|
5418
|
+
}
|
|
5419
|
+
const payoutUnits = amountUnits * WeiPerEther / price;
|
|
5420
|
+
return {
|
|
5421
|
+
inputAmount: String(amount),
|
|
5422
|
+
expectedPayout: formatUnits(payoutUnits, payoutDec),
|
|
5423
|
+
bondPrice: formatUnits(price, 18),
|
|
5424
|
+
principalToken,
|
|
5425
|
+
payoutToken,
|
|
5426
|
+
principalDecimals: principalDec,
|
|
5427
|
+
payoutDecimals: payoutDec
|
|
5428
|
+
};
|
|
5429
|
+
}
|
|
5430
|
+
async function ensurePrincipalAllowance({ signer, bond, amount, principalToken }) {
|
|
5431
|
+
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
5432
|
+
const owner = await signer.getAddress();
|
|
5433
|
+
const provider = signer.provider;
|
|
5434
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "signer requires provider");
|
|
5435
|
+
const bondAddress = normalise(bond, "bond");
|
|
5436
|
+
const tokenAddress = principalToken ? normalise(principalToken, "principalToken") : (await getPrincipalAndPayout({ provider, bond: bondAddress })).principalToken;
|
|
5437
|
+
const principal = new Contract(tokenAddress, ERC20, signer);
|
|
5438
|
+
const allowance = await principal.allowance(owner, bondAddress);
|
|
5439
|
+
if (allowance >= amount) {
|
|
5440
|
+
return { approved: false, transactionHash: null };
|
|
5441
|
+
}
|
|
5442
|
+
const approvalTx = await principal.approve(bondAddress, MaxUint256);
|
|
5443
|
+
const receipt = await approvalTx.wait();
|
|
5444
|
+
return {
|
|
5445
|
+
approved: true,
|
|
5446
|
+
transactionHash: receipt.hash ?? approvalTx.hash ?? null
|
|
5447
|
+
};
|
|
5448
|
+
}
|
|
5449
|
+
async function purchase({ signer, bond, amount, maxPrice }) {
|
|
5450
|
+
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
5451
|
+
if (amount == null) throw new SageSDKError(CODES.INVALID_ARGS, "amount required");
|
|
5452
|
+
if (maxPrice == null) throw new SageSDKError(CODES.INVALID_ARGS, "maxPrice required");
|
|
5453
|
+
const provider = signer.provider;
|
|
5454
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "signer requires provider");
|
|
5455
|
+
const bondAddress = normalise(bond, "bond");
|
|
5456
|
+
const bondContract = new Contract(bondAddress, ABI.BondDepository, signer);
|
|
5457
|
+
const { principalToken, payoutToken } = await getPrincipalAndPayout({ provider, bond: bondAddress });
|
|
5458
|
+
const principalDec = await getTokenDecimals({ provider, token: principalToken });
|
|
5459
|
+
const payoutDec = await getTokenDecimals({ provider, token: payoutToken });
|
|
5460
|
+
const amountUnits = parseUnits(String(amount), principalDec);
|
|
5461
|
+
const maxPriceUnits = parseUnits(String(maxPrice), 18);
|
|
5462
|
+
const currentPrice = await getPrice({ provider, bond: bondAddress });
|
|
5463
|
+
if (currentPrice > maxPriceUnits) {
|
|
5464
|
+
throw new SageSDKError(CODES.INVALID_ARGS, "current bond price exceeds max price", {
|
|
5465
|
+
currentPrice: formatUnits(currentPrice, 18),
|
|
5466
|
+
maxPrice: String(maxPrice)
|
|
5467
|
+
});
|
|
5468
|
+
}
|
|
5469
|
+
await ensurePrincipalAllowance({ signer, bond: bondAddress, amount: amountUnits, principalToken });
|
|
5470
|
+
const tx = await bondContract.deposit(amountUnits, maxPriceUnits);
|
|
5471
|
+
const receipt = await tx.wait();
|
|
5472
|
+
let depositEvent = null;
|
|
5473
|
+
for (const log of receipt.logs || []) {
|
|
5474
|
+
try {
|
|
5475
|
+
const parsed = BondInterface.parseLog(log);
|
|
5476
|
+
if (parsed?.name === "BondCreated") {
|
|
5477
|
+
depositEvent = parsed.args;
|
|
5478
|
+
break;
|
|
5479
|
+
}
|
|
5480
|
+
} catch (_) {
|
|
5481
|
+
}
|
|
5482
|
+
}
|
|
5483
|
+
return {
|
|
5484
|
+
transactionHash: receipt.hash ?? tx.hash ?? null,
|
|
5485
|
+
deposit: formatUnits(depositEvent?.deposit ?? amountUnits, principalDec),
|
|
5486
|
+
payout: depositEvent?.payout != null ? formatUnits(depositEvent.payout, payoutDec) : null,
|
|
5487
|
+
expires: depositEvent?.expires ?? null,
|
|
5488
|
+
price: formatUnits(currentPrice, 18)
|
|
5489
|
+
};
|
|
5490
|
+
}
|
|
5491
|
+
async function redeem({ signer, bond, recipient, stake = false }) {
|
|
5492
|
+
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
5493
|
+
const provider = signer.provider;
|
|
5494
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "signer requires provider");
|
|
5495
|
+
const bondAddress = normalise(bond, "bond");
|
|
5496
|
+
const user = normalise(recipient || await signer.getAddress(), "recipient");
|
|
5497
|
+
const bondContract = new Contract(bondAddress, ABI.BondDepository, signer);
|
|
5498
|
+
const { payoutToken } = await getPrincipalAndPayout({ provider, bond: bondAddress });
|
|
5499
|
+
const payoutDec = await getTokenDecimals({ provider, token: payoutToken });
|
|
5500
|
+
const pending = await bondContract.pendingPayout(user);
|
|
5501
|
+
if (pending === 0n) {
|
|
5502
|
+
return {
|
|
5503
|
+
transactionHash: null,
|
|
5504
|
+
redeemed: "0",
|
|
5505
|
+
message: "No bonds available for redemption"
|
|
5506
|
+
};
|
|
5507
|
+
}
|
|
5508
|
+
const tx = await bondContract.redeem(user, !!stake);
|
|
5509
|
+
const receipt = await tx.wait();
|
|
5510
|
+
return {
|
|
5511
|
+
transactionHash: receipt.hash ?? tx.hash ?? null,
|
|
5512
|
+
redeemed: formatUnits(pending, payoutDec)
|
|
5513
|
+
};
|
|
5514
|
+
}
|
|
4768
5515
|
module.exports = {
|
|
4769
5516
|
getInfo,
|
|
4770
5517
|
getPrice,
|
|
@@ -4772,7 +5519,11 @@ var require_bond = __commonJS({
|
|
|
4772
5519
|
buildDepositTx,
|
|
4773
5520
|
buildRedeemTx,
|
|
4774
5521
|
getTokenDecimals,
|
|
4775
|
-
getPrincipalAndPayout
|
|
5522
|
+
getPrincipalAndPayout,
|
|
5523
|
+
estimatePayout,
|
|
5524
|
+
ensurePrincipalAllowance,
|
|
5525
|
+
purchase,
|
|
5526
|
+
redeem
|
|
4776
5527
|
};
|
|
4777
5528
|
}
|
|
4778
5529
|
});
|
|
@@ -5018,6 +5769,1430 @@ var require_session = __commonJS({
|
|
|
5018
5769
|
}
|
|
5019
5770
|
});
|
|
5020
5771
|
|
|
5772
|
+
// src/wallet/cast-manager.js
|
|
5773
|
+
var require_cast_manager = __commonJS({
|
|
5774
|
+
"src/wallet/cast-manager.js"(exports, module) {
|
|
5775
|
+
var { spawn } = __require("child_process");
|
|
5776
|
+
var { ethers } = __require("ethers");
|
|
5777
|
+
var fs = __require("fs");
|
|
5778
|
+
var path = __require("path");
|
|
5779
|
+
var readline = __require("readline");
|
|
5780
|
+
try {
|
|
5781
|
+
__require("dotenv").config({ quiet: true });
|
|
5782
|
+
} catch (_) {
|
|
5783
|
+
}
|
|
5784
|
+
var colors = {
|
|
5785
|
+
red: (text) => `\x1B[31m${text}\x1B[0m`,
|
|
5786
|
+
green: (text) => `\x1B[32m${text}\x1B[0m`,
|
|
5787
|
+
yellow: (text) => `\x1B[33m${text}\x1B[0m`,
|
|
5788
|
+
blue: (text) => `\x1B[34m${text}\x1B[0m`,
|
|
5789
|
+
cyan: (text) => `\x1B[36m${text}\x1B[0m`
|
|
5790
|
+
};
|
|
5791
|
+
var CastSigner = class extends ethers.VoidSigner {
|
|
5792
|
+
constructor(address, provider, castWalletManager) {
|
|
5793
|
+
super(address, provider);
|
|
5794
|
+
this.castWalletManager = castWalletManager;
|
|
5795
|
+
}
|
|
5796
|
+
async sendTransaction(transaction) {
|
|
5797
|
+
const result = await this.castWalletManager.signTransaction(transaction);
|
|
5798
|
+
return {
|
|
5799
|
+
hash: result.hash,
|
|
5800
|
+
wait: async () => result.receipt || {
|
|
5801
|
+
blockNumber: result.receipt?.blockNumber,
|
|
5802
|
+
status: 1,
|
|
5803
|
+
transactionHash: result.hash
|
|
5804
|
+
}
|
|
5805
|
+
};
|
|
5806
|
+
}
|
|
5807
|
+
async signTransaction(transaction) {
|
|
5808
|
+
const result = await this.castWalletManager.signTransaction(transaction);
|
|
5809
|
+
return result.hash;
|
|
5810
|
+
}
|
|
5811
|
+
async signMessage(message) {
|
|
5812
|
+
return await this.castWalletManager.signMessage(message);
|
|
5813
|
+
}
|
|
5814
|
+
};
|
|
5815
|
+
var CastWalletManager = class {
|
|
5816
|
+
constructor() {
|
|
5817
|
+
this.connected = false;
|
|
5818
|
+
this.account = null;
|
|
5819
|
+
this.signer = null;
|
|
5820
|
+
this.provider = null;
|
|
5821
|
+
this.__lastCastOutput = "";
|
|
5822
|
+
this.__lastCastFrom = null;
|
|
5823
|
+
this.__lastCastKeystore = null;
|
|
5824
|
+
this.rpcUrl = process.env.RPC_URL || process.env.BASE_SEPOLIA_RPC || process.env.BASE_RPC_URL || "https://base-sepolia.publicnode.com";
|
|
5825
|
+
this.chainId = process.env.CHAIN_ID || "84532";
|
|
5826
|
+
this.walletFile = path.join(process.cwd(), ".cast-wallet.json");
|
|
5827
|
+
this.keystoreDir = path.join(process.cwd(), ".cast-keystore");
|
|
5828
|
+
this.castCommand = this.findCastCommand();
|
|
5829
|
+
}
|
|
5830
|
+
/**
|
|
5831
|
+
* @dev Find the cast command path
|
|
5832
|
+
* @returns {string} Path to cast binary
|
|
5833
|
+
*/
|
|
5834
|
+
findCastCommand() {
|
|
5835
|
+
const os = __require("os");
|
|
5836
|
+
const { execSync } = __require("child_process");
|
|
5837
|
+
const possiblePaths = [
|
|
5838
|
+
path.join(os.homedir(), ".foundry", "bin", "cast"),
|
|
5839
|
+
"/usr/local/bin/cast",
|
|
5840
|
+
"/usr/bin/cast",
|
|
5841
|
+
"cast"
|
|
5842
|
+
// fallback to PATH
|
|
5843
|
+
];
|
|
5844
|
+
for (const castPath of possiblePaths) {
|
|
5845
|
+
try {
|
|
5846
|
+
if (castPath === "cast") {
|
|
5847
|
+
execSync("which cast", { stdio: "ignore" });
|
|
5848
|
+
return "cast";
|
|
5849
|
+
} else {
|
|
5850
|
+
if (fs.existsSync(castPath)) {
|
|
5851
|
+
return castPath;
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
} catch (error) {
|
|
5855
|
+
continue;
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5858
|
+
return "cast";
|
|
5859
|
+
}
|
|
5860
|
+
/**
|
|
5861
|
+
* @dev Check if Cast is installed
|
|
5862
|
+
* @returns {Promise<boolean>} True if Cast is available
|
|
5863
|
+
*/
|
|
5864
|
+
async checkCastInstallation() {
|
|
5865
|
+
return new Promise((resolve) => {
|
|
5866
|
+
const cast = spawn(this.castCommand, ["--version"]);
|
|
5867
|
+
cast.on("error", () => {
|
|
5868
|
+
console.log(colors.red("\u274C Cast not found. Please install Foundry first:"));
|
|
5869
|
+
console.log(colors.yellow("curl -L https://foundry.paradigm.xyz | bash"));
|
|
5870
|
+
console.log(colors.yellow("foundryup"));
|
|
5871
|
+
resolve(false);
|
|
5872
|
+
});
|
|
5873
|
+
cast.on("close", (code) => {
|
|
5874
|
+
resolve(code === 0);
|
|
5875
|
+
});
|
|
5876
|
+
});
|
|
5877
|
+
}
|
|
5878
|
+
/**
|
|
5879
|
+
* @dev Create keystore directory if it doesn't exist
|
|
5880
|
+
*/
|
|
5881
|
+
ensureKeystoreDir() {
|
|
5882
|
+
if (!fs.existsSync(this.keystoreDir)) {
|
|
5883
|
+
fs.mkdirSync(this.keystoreDir, { recursive: true });
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
/**
|
|
5887
|
+
* @dev Load Cast wallet from file (address only)
|
|
5888
|
+
* @returns {Object|null} Wallet data or null
|
|
5889
|
+
*/
|
|
5890
|
+
loadCastWallet() {
|
|
5891
|
+
try {
|
|
5892
|
+
if (fs.existsSync(this.walletFile)) {
|
|
5893
|
+
const data = fs.readFileSync(this.walletFile, "utf8");
|
|
5894
|
+
return JSON.parse(data);
|
|
5895
|
+
}
|
|
5896
|
+
} catch (error) {
|
|
5897
|
+
console.log(colors.yellow("\u26A0\uFE0F Could not load Cast wallet file"));
|
|
5898
|
+
}
|
|
5899
|
+
return null;
|
|
5900
|
+
}
|
|
5901
|
+
/**
|
|
5902
|
+
* @dev Save Cast wallet to file (address only, no private keys)
|
|
5903
|
+
* @param {Object} walletData Wallet data to save
|
|
5904
|
+
*/
|
|
5905
|
+
saveCastWallet(walletData, opts = {}) {
|
|
5906
|
+
try {
|
|
5907
|
+
const safeWalletData = {
|
|
5908
|
+
address: walletData.address,
|
|
5909
|
+
keystorePath: walletData.keystorePath,
|
|
5910
|
+
createdAt: walletData.createdAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
5911
|
+
};
|
|
5912
|
+
fs.writeFileSync(this.walletFile, JSON.stringify(safeWalletData, null, 2));
|
|
5913
|
+
if (!opts.silent) {
|
|
5914
|
+
console.log(colors.green("\u2705 Cast wallet saved securely (no private keys stored)"));
|
|
5915
|
+
}
|
|
5916
|
+
this.updateProfileWallet(safeWalletData.address, opts);
|
|
5917
|
+
} catch (error) {
|
|
5918
|
+
console.log(colors.yellow("\u26A0\uFE0F Could not save Cast wallet file"));
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
updateProfileWallet(address, opts = {}) {
|
|
5922
|
+
if (!address) return;
|
|
5923
|
+
try {
|
|
5924
|
+
const normalized = ethers.getAddress(address);
|
|
5925
|
+
let cfgModule;
|
|
5926
|
+
try {
|
|
5927
|
+
({ ConfigManager: cfgModule } = __require("@sage/shared"));
|
|
5928
|
+
} catch (_) {
|
|
5929
|
+
cfgModule = __require("./config");
|
|
5930
|
+
}
|
|
5931
|
+
const projectDir = cfgModule.getProjectDir ? cfgModule.getProjectDir() : process.cwd();
|
|
5932
|
+
const cfgPath = path.join(projectDir, ".sage", "config.json");
|
|
5933
|
+
const current = fs.existsSync(cfgPath) ? JSON.parse(fs.readFileSync(cfgPath, "utf8") || "{}") : {};
|
|
5934
|
+
const active = current.activeProfile || "default";
|
|
5935
|
+
current.activeProfile = active;
|
|
5936
|
+
current.profiles = current.profiles || {};
|
|
5937
|
+
current.profiles[active] = current.profiles[active] || {};
|
|
5938
|
+
const wallet = current.profiles[active].wallet || {};
|
|
5939
|
+
wallet.defaultAccount = normalized;
|
|
5940
|
+
wallet.type = wallet.type || "cast";
|
|
5941
|
+
current.profiles[active].wallet = wallet;
|
|
5942
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
5943
|
+
fs.writeFileSync(cfgPath, JSON.stringify(current, null, 2));
|
|
5944
|
+
if (!opts.silent) {
|
|
5945
|
+
console.log(colors.cyan(`\u{1F4BE} Updated profile ${active} default account \u2192 ${normalized}`));
|
|
5946
|
+
}
|
|
5947
|
+
} catch (_) {
|
|
5948
|
+
}
|
|
5949
|
+
}
|
|
5950
|
+
ensureAccountSynced(address, keystorePath, opts = {}) {
|
|
5951
|
+
if (!address) return;
|
|
5952
|
+
let normalized;
|
|
5953
|
+
try {
|
|
5954
|
+
normalized = ethers.getAddress(address);
|
|
5955
|
+
} catch (_) {
|
|
5956
|
+
return;
|
|
5957
|
+
}
|
|
5958
|
+
const cached = this.account ? this.account.toLowerCase() : null;
|
|
5959
|
+
if (cached !== normalized.toLowerCase()) {
|
|
5960
|
+
if (!opts.silent) {
|
|
5961
|
+
console.log(colors.yellow(`\u2139\uFE0F Updating Cast signer context to ${normalized}`));
|
|
5962
|
+
}
|
|
5963
|
+
this.account = normalized;
|
|
5964
|
+
const existing = this.loadCastWallet();
|
|
5965
|
+
const createdAt = existing?.createdAt || (/* @__PURE__ */ new Date()).toISOString();
|
|
5966
|
+
this.saveCastWallet({ address: normalized, keystorePath, createdAt }, { silent: true });
|
|
5967
|
+
this.updateProfileWallet(normalized, { silent: true });
|
|
5968
|
+
}
|
|
5969
|
+
}
|
|
5970
|
+
extractFromAddress(raw) {
|
|
5971
|
+
if (!raw) return null;
|
|
5972
|
+
const directMatch = raw.match(/from\s+(0x[a-fA-F0-9]{40})/i);
|
|
5973
|
+
if (directMatch) return directMatch[1];
|
|
5974
|
+
const jsonMatch = raw.match(/"from"\s*:\s*"(0x[a-fA-F0-9]{40})"/i);
|
|
5975
|
+
if (jsonMatch) return jsonMatch[1];
|
|
5976
|
+
return null;
|
|
5977
|
+
}
|
|
5978
|
+
/**
|
|
5979
|
+
* @dev Prompt user for keystore password
|
|
5980
|
+
* @returns {Promise<string>} Password
|
|
5981
|
+
*/
|
|
5982
|
+
async promptForPassword() {
|
|
5983
|
+
const fromFile = process.env.SAGE_CAST_PASSWORD_FILE || process.env.CAST_PASSWORD_FILE;
|
|
5984
|
+
if (fromFile && fromFile.trim()) {
|
|
5985
|
+
try {
|
|
5986
|
+
return fs.readFileSync(fromFile.trim(), "utf8");
|
|
5987
|
+
} catch (_) {
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5990
|
+
if (typeof process.env.CAST_PASSWORD !== "undefined") {
|
|
5991
|
+
return String(process.env.CAST_PASSWORD);
|
|
5992
|
+
}
|
|
5993
|
+
const rl = readline.createInterface({
|
|
5994
|
+
input: process.stdin,
|
|
5995
|
+
output: process.stdout
|
|
5996
|
+
});
|
|
5997
|
+
return new Promise((resolve) => {
|
|
5998
|
+
rl.question(colors.blue("\u{1F510} Enter keystore password: "), (password) => {
|
|
5999
|
+
rl.close();
|
|
6000
|
+
resolve(password);
|
|
6001
|
+
});
|
|
6002
|
+
});
|
|
6003
|
+
}
|
|
6004
|
+
/**
|
|
6005
|
+
* @dev Connect wallet using Cast keystore
|
|
6006
|
+
* @returns {Promise<string>} Account address
|
|
6007
|
+
*/
|
|
6008
|
+
async connect() {
|
|
6009
|
+
try {
|
|
6010
|
+
const castAvailable = await this.checkCastInstallation();
|
|
6011
|
+
if (!castAvailable) {
|
|
6012
|
+
throw new Error("Cast not available. Please install Foundry first.");
|
|
6013
|
+
}
|
|
6014
|
+
console.log(colors.blue("\u{1F517} Connecting wallet using Cast keystore..."));
|
|
6015
|
+
this.ensureKeystoreDir();
|
|
6016
|
+
try {
|
|
6017
|
+
let cliConfig;
|
|
6018
|
+
try {
|
|
6019
|
+
({ ConfigManager: cliConfig } = __require("@sage/shared"));
|
|
6020
|
+
} catch (_) {
|
|
6021
|
+
cliConfig = __require("./config");
|
|
6022
|
+
}
|
|
6023
|
+
const profiles = cliConfig.readProfiles();
|
|
6024
|
+
const active = profiles.activeProfile || "default";
|
|
6025
|
+
const wanted = profiles?.profiles?.[active]?.wallet?.defaultAccount;
|
|
6026
|
+
if (wanted && this.keystoreDir && fs.existsSync(this.keystoreDir)) {
|
|
6027
|
+
const files = fs.readdirSync(this.keystoreDir);
|
|
6028
|
+
for (const f of files) {
|
|
6029
|
+
const ksPath = path.join(this.keystoreDir, f);
|
|
6030
|
+
try {
|
|
6031
|
+
const addr = await new Promise((resolve, reject) => {
|
|
6032
|
+
const p = spawn(this.castCommand, ["wallet", "address", ksPath]);
|
|
6033
|
+
let out = "";
|
|
6034
|
+
let err = "";
|
|
6035
|
+
p.stdout.on("data", (d) => out += d.toString());
|
|
6036
|
+
p.stderr.on("data", (d) => err += d.toString());
|
|
6037
|
+
p.on("close", (code) => code === 0 ? resolve(out.trim()) : reject(new Error(err || `cast exited ${code}`)));
|
|
6038
|
+
});
|
|
6039
|
+
if (addr && addr.toLowerCase() === String(wanted).toLowerCase()) {
|
|
6040
|
+
this.saveCastWallet({ address: addr, keystorePath: ksPath, createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6041
|
+
break;
|
|
6042
|
+
}
|
|
6043
|
+
} catch (_) {
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
6047
|
+
} catch (_) {
|
|
6048
|
+
}
|
|
6049
|
+
let walletData = this.loadCastWallet();
|
|
6050
|
+
if (!walletData) {
|
|
6051
|
+
console.log(colors.yellow("\u{1F4DD} No Cast wallet found. Creating new secure wallet..."));
|
|
6052
|
+
walletData = await this.createCastWallet();
|
|
6053
|
+
this.saveCastWallet(walletData);
|
|
6054
|
+
}
|
|
6055
|
+
this.account = walletData.address;
|
|
6056
|
+
if (walletData.address && walletData.keystorePath) {
|
|
6057
|
+
this.ensureAccountSynced(walletData.address, walletData.keystorePath, { silent: true });
|
|
6058
|
+
}
|
|
6059
|
+
try {
|
|
6060
|
+
const { checkRpcHealth } = __require("./utils/rpc-health");
|
|
6061
|
+
const candidates = [
|
|
6062
|
+
this.rpcUrl,
|
|
6063
|
+
process.env.BASE_SEPOLIA_RPC,
|
|
6064
|
+
process.env.BASE_RPC_URL,
|
|
6065
|
+
"https://base-sepolia.publicnode.com",
|
|
6066
|
+
"https://sepolia.base.org"
|
|
6067
|
+
].filter(Boolean);
|
|
6068
|
+
for (const url of candidates) {
|
|
6069
|
+
const h = await checkRpcHealth(url, Number(process.env.SAGE_RPC_TIMEOUT_MS || 8e3));
|
|
6070
|
+
if (h.healthy) {
|
|
6071
|
+
this.rpcUrl = url;
|
|
6072
|
+
break;
|
|
6073
|
+
}
|
|
6074
|
+
}
|
|
6075
|
+
if (!process.env.SAGE_QUIET_JSON) console.log(colors.cyan(`\u{1F310} RPC: ${this.rpcUrl}`));
|
|
6076
|
+
} catch (_) {
|
|
6077
|
+
}
|
|
6078
|
+
this.provider = new ethers.JsonRpcProvider(this.rpcUrl);
|
|
6079
|
+
const balance = await this.provider.getBalance(this.account);
|
|
6080
|
+
console.log(colors.green("\u2705 Cast wallet connected successfully"));
|
|
6081
|
+
console.log(colors.cyan(`\u{1F4E7} Address: ${this.account}`));
|
|
6082
|
+
console.log(colors.cyan(`\u{1F4B0} Balance: ${ethers.formatEther(balance)} ETH`));
|
|
6083
|
+
console.log(colors.yellow("\u{1F510} Private keys stored securely in keystore"));
|
|
6084
|
+
this.connected = true;
|
|
6085
|
+
return this.account;
|
|
6086
|
+
} catch (error) {
|
|
6087
|
+
console.error(colors.red("\u274C Cast wallet connection failed:"), error.message);
|
|
6088
|
+
throw error;
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
/**
|
|
6092
|
+
* @dev Create a new Cast wallet with keystore
|
|
6093
|
+
* @returns {Promise<Object>} Wallet data
|
|
6094
|
+
*/
|
|
6095
|
+
async createCastWallet() {
|
|
6096
|
+
return new Promise(async (resolve, reject) => {
|
|
6097
|
+
try {
|
|
6098
|
+
this.ensureKeystoreDir();
|
|
6099
|
+
const keystorePath = path.join(this.keystoreDir, "sage-wallet");
|
|
6100
|
+
const cast = spawn(this.castCommand, [
|
|
6101
|
+
"wallet",
|
|
6102
|
+
"new",
|
|
6103
|
+
keystorePath,
|
|
6104
|
+
"sage-wallet"
|
|
6105
|
+
]);
|
|
6106
|
+
let output = "";
|
|
6107
|
+
let error = "";
|
|
6108
|
+
cast.stdout.on("data", (data) => {
|
|
6109
|
+
output += data.toString();
|
|
6110
|
+
});
|
|
6111
|
+
cast.stderr.on("data", (data) => {
|
|
6112
|
+
error += data.toString();
|
|
6113
|
+
});
|
|
6114
|
+
cast.on("close", (code) => {
|
|
6115
|
+
if (code === 0) {
|
|
6116
|
+
const lines = output.split("\n");
|
|
6117
|
+
const addressLine = lines.find((line) => line.includes("Address:"));
|
|
6118
|
+
if (addressLine) {
|
|
6119
|
+
const address = addressLine.split("Address:")[1].trim();
|
|
6120
|
+
const walletData = {
|
|
6121
|
+
address,
|
|
6122
|
+
keystorePath,
|
|
6123
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6124
|
+
};
|
|
6125
|
+
resolve(walletData);
|
|
6126
|
+
} else {
|
|
6127
|
+
reject(new Error("Failed to parse Cast wallet address"));
|
|
6128
|
+
}
|
|
6129
|
+
} else {
|
|
6130
|
+
reject(new Error(`Cast wallet creation failed: ${error}`));
|
|
6131
|
+
}
|
|
6132
|
+
});
|
|
6133
|
+
} catch (error) {
|
|
6134
|
+
reject(error);
|
|
6135
|
+
}
|
|
6136
|
+
});
|
|
6137
|
+
}
|
|
6138
|
+
/**
|
|
6139
|
+
* @dev Get account address
|
|
6140
|
+
* @returns {Promise<string>} Account address
|
|
6141
|
+
*/
|
|
6142
|
+
async getAccountAddress() {
|
|
6143
|
+
const walletData = this.loadCastWallet();
|
|
6144
|
+
if (walletData) {
|
|
6145
|
+
return walletData.address;
|
|
6146
|
+
}
|
|
6147
|
+
throw new Error("No Cast wallet configured");
|
|
6148
|
+
}
|
|
6149
|
+
/**
|
|
6150
|
+
* @dev Sign and send transaction using Cast keystore
|
|
6151
|
+
* @param {Object} transaction Transaction object
|
|
6152
|
+
* @returns {Promise<Object>} Transaction receipt
|
|
6153
|
+
*/
|
|
6154
|
+
async signTransaction(transaction) {
|
|
6155
|
+
if (!this.connected) {
|
|
6156
|
+
throw new Error('Wallet not connected. Please run "sage wallet connect" first.');
|
|
6157
|
+
}
|
|
6158
|
+
try {
|
|
6159
|
+
console.log(colors.blue("\u{1F4DD} Transaction Details:"));
|
|
6160
|
+
console.log(colors.cyan(`To: ${transaction.to}`));
|
|
6161
|
+
console.log(colors.cyan(`Value: ${ethers.formatEther(transaction.value || "0")} ETH`));
|
|
6162
|
+
console.log(colors.cyan(`Gas Limit: ${transaction.gasLimit?.toString() || "Auto"}`));
|
|
6163
|
+
const rawTxHash = await this.sendTransactionWithCast(transaction);
|
|
6164
|
+
const castOutput = this.__lastCastOutput || "";
|
|
6165
|
+
const castFrom = this.__lastCastFrom;
|
|
6166
|
+
const castKeystore = this.__lastCastKeystore;
|
|
6167
|
+
if (rawTxHash === "NO_HASH_SUCCESS") {
|
|
6168
|
+
if (castFrom && castKeystore) this.ensureAccountSynced(castFrom, castKeystore, { silent: true });
|
|
6169
|
+
console.log(colors.green("\u2705 Transaction sent successfully (Cast did not return hash)"));
|
|
6170
|
+
console.log(colors.yellow("\u{1F4CB} Transaction confirmed but hash unavailable"));
|
|
6171
|
+
console.log(colors.blue("\u{1F4A1} Check your balance to confirm the transaction succeeded"));
|
|
6172
|
+
return {
|
|
6173
|
+
hash: null,
|
|
6174
|
+
receipt: null,
|
|
6175
|
+
success: true,
|
|
6176
|
+
message: "Transaction sent successfully via Cast keystore"
|
|
6177
|
+
};
|
|
6178
|
+
}
|
|
6179
|
+
if (castOutput.includes("transactionHash") && castOutput.includes("blockNumber")) {
|
|
6180
|
+
console.log(colors.cyan("\u{1F4CB} Cast returned full transaction receipt"));
|
|
6181
|
+
const statusMatch = castOutput.match(/status\s+(\d+)/);
|
|
6182
|
+
const statusNum = statusMatch ? Number(statusMatch[1]) : void 0;
|
|
6183
|
+
const blockMatch = castOutput.match(/blockNumber\s+(\d+)/);
|
|
6184
|
+
const blockNumber = blockMatch ? blockMatch[1] : "unknown";
|
|
6185
|
+
const txHash2 = this.cleanTransactionHash(castOutput);
|
|
6186
|
+
if (statusNum === 0) {
|
|
6187
|
+
console.error(colors.red("\u274C Transaction failed with status 0"));
|
|
6188
|
+
console.error(colors.yellow("\u{1F4CB} Full receipt from Cast:\n") + castOutput);
|
|
6189
|
+
throw new Error(`Transaction failed (status 0). Tx: ${txHash2}, block: ${blockNumber}`);
|
|
6190
|
+
}
|
|
6191
|
+
console.log(colors.green("\u2705 Transaction confirmed:"), txHash2);
|
|
6192
|
+
console.log(colors.green(`\u2705 Transaction confirmed in block: ${blockNumber}`));
|
|
6193
|
+
if (castFrom && castKeystore) this.ensureAccountSynced(castFrom, castKeystore, { silent: true });
|
|
6194
|
+
return {
|
|
6195
|
+
hash: txHash2,
|
|
6196
|
+
receipt: { blockNumber, status: 1 },
|
|
6197
|
+
success: true,
|
|
6198
|
+
message: "Transaction confirmed via Cast keystore"
|
|
6199
|
+
};
|
|
6200
|
+
}
|
|
6201
|
+
const txHash = this.cleanTransactionHash(rawTxHash);
|
|
6202
|
+
console.log(colors.green("\u2705 Transaction sent:", txHash));
|
|
6203
|
+
console.log(colors.yellow("\u{1F50D} Validating transaction on blockchain..."));
|
|
6204
|
+
const isValid = await this.validateTransactionExists(txHash);
|
|
6205
|
+
if (!isValid) {
|
|
6206
|
+
throw new Error(`Transaction validation failed. Hash: ${txHash}`);
|
|
6207
|
+
}
|
|
6208
|
+
console.log(colors.yellow("\u23F3 Waiting for confirmation..."));
|
|
6209
|
+
const receipt = await this.waitForTransactionWithRetry(txHash);
|
|
6210
|
+
if (!castFrom && receipt?.from && castKeystore) {
|
|
6211
|
+
this.ensureAccountSynced(receipt.from, castKeystore, { silent: true });
|
|
6212
|
+
} else if (castFrom && castKeystore) {
|
|
6213
|
+
this.ensureAccountSynced(castFrom, castKeystore, { silent: true });
|
|
6214
|
+
}
|
|
6215
|
+
console.log(colors.green("\u2705 Transaction confirmed in block:", receipt.blockNumber));
|
|
6216
|
+
return { hash: txHash, receipt };
|
|
6217
|
+
} catch (error) {
|
|
6218
|
+
console.error(colors.red("\u274C Transaction failed:"), error.message);
|
|
6219
|
+
throw error;
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
/**
|
|
6223
|
+
* @dev Send transaction using Cast keystore
|
|
6224
|
+
* @param {Object} transaction Transaction object
|
|
6225
|
+
* @returns {Promise<string>} Transaction hash
|
|
6226
|
+
*/
|
|
6227
|
+
async sendTransactionWithCast(transaction) {
|
|
6228
|
+
return new Promise(async (resolve, reject) => {
|
|
6229
|
+
const walletData = this.loadCastWallet();
|
|
6230
|
+
if (!walletData) {
|
|
6231
|
+
reject(new Error("No Cast wallet configured"));
|
|
6232
|
+
return;
|
|
6233
|
+
}
|
|
6234
|
+
const password = await this.promptForPassword();
|
|
6235
|
+
let castArgs = ["send"];
|
|
6236
|
+
if (transaction.data) {
|
|
6237
|
+
const functionInfo = this.extractFunctionInfo(transaction.data);
|
|
6238
|
+
if (functionInfo) {
|
|
6239
|
+
castArgs.push(transaction.to);
|
|
6240
|
+
castArgs.push(functionInfo.signature);
|
|
6241
|
+
castArgs.push(...functionInfo.args);
|
|
6242
|
+
} else {
|
|
6243
|
+
castArgs.push(
|
|
6244
|
+
"--rpc-url",
|
|
6245
|
+
this.rpcUrl,
|
|
6246
|
+
"--keystore",
|
|
6247
|
+
walletData.keystorePath,
|
|
6248
|
+
"--password",
|
|
6249
|
+
password,
|
|
6250
|
+
"--data",
|
|
6251
|
+
transaction.data,
|
|
6252
|
+
transaction.to
|
|
6253
|
+
);
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
if (!castArgs.includes("--rpc-url")) {
|
|
6257
|
+
castArgs.push(
|
|
6258
|
+
"--rpc-url",
|
|
6259
|
+
this.rpcUrl,
|
|
6260
|
+
"--keystore",
|
|
6261
|
+
walletData.keystorePath,
|
|
6262
|
+
"--password",
|
|
6263
|
+
password
|
|
6264
|
+
);
|
|
6265
|
+
}
|
|
6266
|
+
if (transaction.gasLimit) {
|
|
6267
|
+
castArgs.push("--gas-limit", transaction.gasLimit.toString());
|
|
6268
|
+
}
|
|
6269
|
+
let attemptedWithSignature = true;
|
|
6270
|
+
let cast = spawn(this.castCommand, castArgs);
|
|
6271
|
+
let output = "";
|
|
6272
|
+
let error = "";
|
|
6273
|
+
cast.stdout.on("data", (data) => {
|
|
6274
|
+
output += data.toString();
|
|
6275
|
+
});
|
|
6276
|
+
cast.stderr.on("data", (data) => {
|
|
6277
|
+
error += data.toString();
|
|
6278
|
+
});
|
|
6279
|
+
const handleClose = (code) => {
|
|
6280
|
+
if (code === 0) {
|
|
6281
|
+
const txHash = output.trim();
|
|
6282
|
+
console.log(colors.cyan(`\u{1F4CB} Cast output: "${txHash}"`));
|
|
6283
|
+
console.log(colors.cyan(`\u{1F4CB} Cast stderr: "${error.trim()}"`));
|
|
6284
|
+
const combinedOutput = `${output}${error ? `
|
|
6285
|
+
${error}` : ""}`;
|
|
6286
|
+
const detectedFrom = this.extractFromAddress(combinedOutput);
|
|
6287
|
+
this.__lastCastOutput = combinedOutput;
|
|
6288
|
+
this.__lastCastFrom = detectedFrom;
|
|
6289
|
+
this.__lastCastKeystore = walletData.keystorePath;
|
|
6290
|
+
if (detectedFrom) {
|
|
6291
|
+
this.ensureAccountSynced(detectedFrom, walletData.keystorePath, { silent: true });
|
|
6292
|
+
}
|
|
6293
|
+
let finalHash = txHash;
|
|
6294
|
+
if (!txHash || txHash.length === 0) {
|
|
6295
|
+
const stderrHash = error.match(/0x[a-fA-F0-9]{64}/);
|
|
6296
|
+
if (stderrHash) {
|
|
6297
|
+
finalHash = stderrHash[0];
|
|
6298
|
+
console.log(colors.green(`\u2705 Found hash in stderr: ${finalHash}`));
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
6301
|
+
if (!finalHash || finalHash.length === 0) {
|
|
6302
|
+
resolve("NO_HASH_SUCCESS");
|
|
6303
|
+
} else {
|
|
6304
|
+
resolve(finalHash);
|
|
6305
|
+
}
|
|
6306
|
+
} else {
|
|
6307
|
+
const errLower = (error || "").toLowerCase();
|
|
6308
|
+
const shouldRetry = attemptedWithSignature && (errLower.includes("parser error") || errLower.includes("expected hex digits"));
|
|
6309
|
+
if (shouldRetry && transaction.data) {
|
|
6310
|
+
console.log(colors.yellow("\u26A0\uFE0F Cast failed parsing function args; retrying with raw --data..."));
|
|
6311
|
+
attemptedWithSignature = false;
|
|
6312
|
+
output = "";
|
|
6313
|
+
error = "";
|
|
6314
|
+
const retryArgs = [
|
|
6315
|
+
"send",
|
|
6316
|
+
"--rpc-url",
|
|
6317
|
+
this.rpcUrl,
|
|
6318
|
+
"--keystore",
|
|
6319
|
+
walletData.keystorePath,
|
|
6320
|
+
"--password",
|
|
6321
|
+
password,
|
|
6322
|
+
"--data",
|
|
6323
|
+
transaction.data,
|
|
6324
|
+
transaction.to
|
|
6325
|
+
];
|
|
6326
|
+
if (transaction.gasLimit) retryArgs.push("--gas-limit", transaction.gasLimit.toString());
|
|
6327
|
+
cast = spawn(this.castCommand, retryArgs);
|
|
6328
|
+
cast.stdout.on("data", (d) => output += d.toString());
|
|
6329
|
+
cast.stderr.on("data", (d) => error += d.toString());
|
|
6330
|
+
cast.on("close", handleClose);
|
|
6331
|
+
return;
|
|
6332
|
+
}
|
|
6333
|
+
const needsCalldata = (error || "").includes("unexpected argument '--data'");
|
|
6334
|
+
if (needsCalldata && transaction.data) {
|
|
6335
|
+
console.log(colors.yellow("\u26A0\uFE0F Cast does not support --data; retrying with --calldata..."));
|
|
6336
|
+
output = "";
|
|
6337
|
+
error = "";
|
|
6338
|
+
const retryArgs2 = [
|
|
6339
|
+
"send",
|
|
6340
|
+
"--rpc-url",
|
|
6341
|
+
this.rpcUrl,
|
|
6342
|
+
"--keystore",
|
|
6343
|
+
walletData.keystorePath,
|
|
6344
|
+
"--password",
|
|
6345
|
+
password,
|
|
6346
|
+
"--calldata",
|
|
6347
|
+
transaction.data,
|
|
6348
|
+
transaction.to
|
|
6349
|
+
];
|
|
6350
|
+
if (transaction.gasLimit) retryArgs2.push("--gas-limit", transaction.gasLimit.toString());
|
|
6351
|
+
cast = spawn(this.castCommand, retryArgs2);
|
|
6352
|
+
cast.stdout.on("data", (d) => output += d.toString());
|
|
6353
|
+
cast.stderr.on("data", (d) => error += d.toString());
|
|
6354
|
+
cast.on("close", handleClose);
|
|
6355
|
+
return;
|
|
6356
|
+
}
|
|
6357
|
+
this.__lastCastOutput = `${output}${error ? `
|
|
6358
|
+
${error}` : ""}`;
|
|
6359
|
+
this.__lastCastFrom = null;
|
|
6360
|
+
this.__lastCastKeystore = walletData.keystorePath;
|
|
6361
|
+
reject(new Error(`Cast transaction failed: ${error}`));
|
|
6362
|
+
}
|
|
6363
|
+
};
|
|
6364
|
+
cast.on("close", handleClose);
|
|
6365
|
+
});
|
|
6366
|
+
}
|
|
6367
|
+
/**
|
|
6368
|
+
* @dev Clean and validate transaction hash from Cast output
|
|
6369
|
+
* @param {string} rawOutput Raw output from Cast (could be hash or full receipt)
|
|
6370
|
+
* @returns {string} Cleaned transaction hash
|
|
6371
|
+
*/
|
|
6372
|
+
cleanTransactionHash(rawOutput) {
|
|
6373
|
+
try {
|
|
6374
|
+
let cleanOutput = rawOutput.toString().trim();
|
|
6375
|
+
const receiptHashMatch = cleanOutput.match(/transactionHash\s+(0x[a-fA-F0-9]{64})/);
|
|
6376
|
+
if (receiptHashMatch) {
|
|
6377
|
+
console.log(colors.green("\u2705 Found transaction hash in receipt output"));
|
|
6378
|
+
return receiptHashMatch[1];
|
|
6379
|
+
}
|
|
6380
|
+
const hashMatch = cleanOutput.match(/0x[a-fA-F0-9]{64}/);
|
|
6381
|
+
if (hashMatch) {
|
|
6382
|
+
console.log(colors.green("\u2705 Found transaction hash in output"));
|
|
6383
|
+
return hashMatch[0];
|
|
6384
|
+
}
|
|
6385
|
+
if (cleanOutput.length === 66 && cleanOutput.startsWith("0x")) {
|
|
6386
|
+
return cleanOutput;
|
|
6387
|
+
}
|
|
6388
|
+
if (cleanOutput.length === 64 && /^[a-fA-F0-9]{64}$/.test(cleanOutput)) {
|
|
6389
|
+
return "0x" + cleanOutput;
|
|
6390
|
+
}
|
|
6391
|
+
throw new Error(`No valid transaction hash found in output`);
|
|
6392
|
+
} catch (error) {
|
|
6393
|
+
console.error(colors.red("\u274C Failed to extract transaction hash:"), error.message);
|
|
6394
|
+
console.error(colors.yellow("Raw Cast output:"), rawOutput.substring(0, 200) + "...");
|
|
6395
|
+
throw new Error(`Could not extract transaction hash from Cast output`);
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
/**
|
|
6399
|
+
* @dev Validate that a transaction actually exists on the blockchain
|
|
6400
|
+
* @param {string} txHash Transaction hash
|
|
6401
|
+
* @returns {Promise<boolean>} True if transaction exists and is valid
|
|
6402
|
+
*/
|
|
6403
|
+
async validateTransactionExists(txHash) {
|
|
6404
|
+
try {
|
|
6405
|
+
const tx = await this.provider.getTransaction(txHash);
|
|
6406
|
+
if (!tx) {
|
|
6407
|
+
console.error(colors.red("\u274C Transaction not found on blockchain"));
|
|
6408
|
+
return false;
|
|
6409
|
+
}
|
|
6410
|
+
const receipt = await this.provider.getTransactionReceipt(txHash);
|
|
6411
|
+
if (!receipt) {
|
|
6412
|
+
console.error(colors.red("\u274C Transaction receipt not found (transaction may not be mined)"));
|
|
6413
|
+
return false;
|
|
6414
|
+
}
|
|
6415
|
+
if (receipt.status === 0) {
|
|
6416
|
+
console.error(colors.red("\u274C Transaction failed (status 0)"));
|
|
6417
|
+
return false;
|
|
6418
|
+
}
|
|
6419
|
+
console.log(colors.green("\u2705 Transaction validated on blockchain"));
|
|
6420
|
+
return true;
|
|
6421
|
+
} catch (error) {
|
|
6422
|
+
console.error(colors.red("\u274C Failed to validate transaction:"), error.message);
|
|
6423
|
+
return false;
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
/**
|
|
6427
|
+
* @dev Wait for transaction with retry logic for Cast compatibility
|
|
6428
|
+
* @param {string} txHash Transaction hash
|
|
6429
|
+
* @returns {Promise<Object>} Transaction receipt
|
|
6430
|
+
*/
|
|
6431
|
+
async waitForTransactionWithRetry(txHash, maxRetries = 3) {
|
|
6432
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
6433
|
+
try {
|
|
6434
|
+
const receipt = await this.provider.waitForTransaction(txHash, 1, 6e4);
|
|
6435
|
+
if (receipt) {
|
|
6436
|
+
return receipt;
|
|
6437
|
+
}
|
|
6438
|
+
throw new Error("Receipt is null");
|
|
6439
|
+
} catch (error) {
|
|
6440
|
+
console.log(colors.yellow(`\u26A0\uFE0F Attempt ${attempt}/${maxRetries} failed: ${error.message}`));
|
|
6441
|
+
if (attempt === maxRetries) {
|
|
6442
|
+
try {
|
|
6443
|
+
const tx = await this.provider.getTransaction(txHash);
|
|
6444
|
+
if (tx) {
|
|
6445
|
+
console.log(colors.green("\u2705 Transaction found in mempool, waiting for confirmation..."));
|
|
6446
|
+
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
6447
|
+
const receipt = await this.provider.getTransactionReceipt(txHash);
|
|
6448
|
+
if (receipt) {
|
|
6449
|
+
return receipt;
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
} catch (getError) {
|
|
6453
|
+
console.log(colors.yellow("\u26A0\uFE0F Could not retrieve transaction details"));
|
|
6454
|
+
}
|
|
6455
|
+
throw new Error(`Failed to get transaction receipt after ${maxRetries} attempts: ${error.message}`);
|
|
6456
|
+
}
|
|
6457
|
+
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
/**
|
|
6462
|
+
* @dev Extract function signature and arguments from transaction data
|
|
6463
|
+
* @param {string} data Transaction data
|
|
6464
|
+
* @returns {Object} Function info with signature and args
|
|
6465
|
+
*/
|
|
6466
|
+
extractFunctionInfo(data) {
|
|
6467
|
+
try {
|
|
6468
|
+
if (data.includes("0xa694fc3a")) {
|
|
6469
|
+
const amount = this.extractUint256FromData(data, 0);
|
|
6470
|
+
return { signature: "stake(uint256)", args: [amount] };
|
|
6471
|
+
} else if (data.startsWith(__require("ethers").id("grantRole(bytes32,address)").slice(0, 10))) {
|
|
6472
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6473
|
+
const iface = new ethers2.Interface(["function grantRole(bytes32,address)"]);
|
|
6474
|
+
const decoded = iface.decodeFunctionData("grantRole", data);
|
|
6475
|
+
const role = decoded[0];
|
|
6476
|
+
const account = decoded[1];
|
|
6477
|
+
return { signature: "grantRole(bytes32,address)", args: [role, account] };
|
|
6478
|
+
} else if (data.startsWith(__require("ethers").id("revokeRole(bytes32,address)").slice(0, 10))) {
|
|
6479
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6480
|
+
const iface = new ethers2.Interface(["function revokeRole(bytes32,address)"]);
|
|
6481
|
+
const decoded = iface.decodeFunctionData("revokeRole", data);
|
|
6482
|
+
const role = decoded[0];
|
|
6483
|
+
const account = decoded[1];
|
|
6484
|
+
return { signature: "revokeRole(bytes32,address)", args: [role, account] };
|
|
6485
|
+
} else if (data.includes("0x59b7c691")) {
|
|
6486
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6487
|
+
const iface = new ethers2.Interface(["function createSubDAOFromTemplate(uint256,string,string)"]);
|
|
6488
|
+
const decoded = iface.decodeFunctionData("createSubDAOFromTemplate", data);
|
|
6489
|
+
const templateId = decoded[0].toString();
|
|
6490
|
+
const name = decoded[1];
|
|
6491
|
+
const description = decoded[2];
|
|
6492
|
+
return {
|
|
6493
|
+
signature: "createSubDAOFromTemplate(uint256,string,string)",
|
|
6494
|
+
args: [templateId, `"${name}"`, `"${description}"`]
|
|
6495
|
+
};
|
|
6496
|
+
} else if (data.startsWith(__require("ethers").id("createSubDAO(string,string,uint8,uint256,uint256,uint256,uint256,uint256,uint256)").slice(0, 10))) {
|
|
6497
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6498
|
+
const iface = new ethers2.Interface(["function createSubDAO(string,string,uint8,uint256,uint256,uint256,uint256,uint256,uint256)"]);
|
|
6499
|
+
const decoded = iface.decodeFunctionData("createSubDAO", data);
|
|
6500
|
+
const name = decoded[0];
|
|
6501
|
+
const description = decoded[1];
|
|
6502
|
+
const accessModel = decoded[2].toString();
|
|
6503
|
+
const minStakeAmount = decoded[3].toString();
|
|
6504
|
+
const burnAmount = decoded[4].toString();
|
|
6505
|
+
const votingDelay = decoded[5].toString();
|
|
6506
|
+
const votingPeriod = decoded[6].toString();
|
|
6507
|
+
const proposalThreshold = decoded[7].toString();
|
|
6508
|
+
const quorumPercentage = decoded[8].toString();
|
|
6509
|
+
return {
|
|
6510
|
+
signature: "createSubDAO(string,string,uint8,uint256,uint256,uint256,uint256,uint256,uint256)",
|
|
6511
|
+
args: [`"${name}"`, `"${description}"`, accessModel, minStakeAmount, burnAmount, votingDelay, votingPeriod, proposalThreshold, quorumPercentage]
|
|
6512
|
+
};
|
|
6513
|
+
} else if (data.startsWith(__require("ethers").id("createSubDAO(string,string,uint8,uint256,uint256)").slice(0, 10))) {
|
|
6514
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6515
|
+
const iface = new ethers2.Interface(["function createSubDAO(string,string,uint8,uint256,uint256)"]);
|
|
6516
|
+
const decoded = iface.decodeFunctionData("createSubDAO", data);
|
|
6517
|
+
const name = decoded[0];
|
|
6518
|
+
const description = decoded[1];
|
|
6519
|
+
const accessModel = decoded[2].toString();
|
|
6520
|
+
const minStakeAmount = decoded[3].toString();
|
|
6521
|
+
const burnAmount = decoded[4].toString();
|
|
6522
|
+
return {
|
|
6523
|
+
signature: "createSubDAO(string,string,uint8,uint256,uint256)",
|
|
6524
|
+
args: [`"${name}"`, `"${description}"`, accessModel, minStakeAmount, burnAmount]
|
|
6525
|
+
};
|
|
6526
|
+
} else if (data.startsWith(__require("ethers").id("createSubDAOOperator(string,string,uint8,uint256,uint256,address,address)").slice(0, 10))) {
|
|
6527
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6528
|
+
const sig = "createSubDAOOperator(string,string,uint8,uint256,uint256,address,address)";
|
|
6529
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6530
|
+
const decoded = iface.decodeFunctionData("createSubDAOOperator", data);
|
|
6531
|
+
const name = decoded[0];
|
|
6532
|
+
const description = decoded[1];
|
|
6533
|
+
const accessModel = decoded[2].toString();
|
|
6534
|
+
const minStakeAmount = decoded[3].toString();
|
|
6535
|
+
const burnAmount = decoded[4].toString();
|
|
6536
|
+
const operatorExecutor = String(decoded[5]);
|
|
6537
|
+
const operatorAdmin = String(decoded[6]);
|
|
6538
|
+
return {
|
|
6539
|
+
signature: sig,
|
|
6540
|
+
args: [`"${name}"`, `"${description}"`, accessModel, minStakeAmount, burnAmount, operatorExecutor, operatorAdmin]
|
|
6541
|
+
};
|
|
6542
|
+
} else if (data.startsWith(__require("ethers").id("createSubDAOOperatorWithStable(string,string,uint8,uint256,address,address)").slice(0, 10))) {
|
|
6543
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6544
|
+
const sig = "createSubDAOOperatorWithStable(string,string,uint8,uint256,address,address)";
|
|
6545
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6546
|
+
const decoded = iface.decodeFunctionData("createSubDAOOperatorWithStable", data);
|
|
6547
|
+
const name = decoded[0];
|
|
6548
|
+
const description = decoded[1];
|
|
6549
|
+
const accessModel = decoded[2].toString();
|
|
6550
|
+
const minStakeAmount = decoded[3].toString();
|
|
6551
|
+
const operatorExecutor = String(decoded[4]);
|
|
6552
|
+
const operatorAdmin = String(decoded[5]);
|
|
6553
|
+
return {
|
|
6554
|
+
signature: sig,
|
|
6555
|
+
args: [`"${name}"`, `"${description}"`, accessModel, minStakeAmount, operatorExecutor, operatorAdmin]
|
|
6556
|
+
};
|
|
6557
|
+
} else if (data.includes("0x23b872dd")) {
|
|
6558
|
+
const from = this.extractAddressFromData(data, 0);
|
|
6559
|
+
const to = this.extractAddressFromData(data, 1);
|
|
6560
|
+
const amount = this.extractUint256FromData(data, 2);
|
|
6561
|
+
return { signature: "transferFrom(address,address,uint256)(bool)", args: [from, to, amount] };
|
|
6562
|
+
} else if (data.includes("0xa9059cbb")) {
|
|
6563
|
+
const to = this.extractAddressFromData(data, 0);
|
|
6564
|
+
const amount = this.extractUint256FromData(data, 1);
|
|
6565
|
+
return { signature: "transfer(address,uint256)(bool)", args: [to, amount] };
|
|
6566
|
+
} else if (data.includes("0x095ea7b3")) {
|
|
6567
|
+
const spender = this.extractAddressFromData(data, 0);
|
|
6568
|
+
const amount = this.extractUint256FromData(data, 1);
|
|
6569
|
+
return { signature: "approve(address,uint256)(bool)", args: [spender, amount] };
|
|
6570
|
+
} else if (data.includes("0x5c19a95c")) {
|
|
6571
|
+
const delegatee = this.extractAddressFromData(data, 0);
|
|
6572
|
+
return { signature: "delegate(address)", args: [delegatee] };
|
|
6573
|
+
} else if (data.includes("0xd3fc9864")) {
|
|
6574
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6575
|
+
const iface = new ethers2.Interface(["function mint(address,uint256,string)"]);
|
|
6576
|
+
const decoded = iface.decodeFunctionData("mint", data);
|
|
6577
|
+
const to = decoded[0];
|
|
6578
|
+
const badgeId = decoded[1].toString();
|
|
6579
|
+
const evidenceURI = decoded[2];
|
|
6580
|
+
return {
|
|
6581
|
+
signature: "mint(address,uint256,string)",
|
|
6582
|
+
args: [to, badgeId, `"${evidenceURI}"`]
|
|
6583
|
+
};
|
|
6584
|
+
} else if (data.includes("0xeac449d9")) {
|
|
6585
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6586
|
+
const iface = new ethers2.Interface(["function revoke(address,uint256)"]);
|
|
6587
|
+
const decoded = iface.decodeFunctionData("revoke", data);
|
|
6588
|
+
const from = decoded[0];
|
|
6589
|
+
const badgeId = decoded[1].toString();
|
|
6590
|
+
return {
|
|
6591
|
+
signature: "revoke(address,uint256)",
|
|
6592
|
+
args: [from, badgeId]
|
|
6593
|
+
};
|
|
6594
|
+
} else if (data.startsWith(__require("ethers").id("propose(address[],uint256[],bytes[],string)").slice(0, 10))) {
|
|
6595
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6596
|
+
const iface = new ethers2.Interface(["function propose(address[],uint256[],bytes[],string)"]);
|
|
6597
|
+
const decoded = iface.decodeFunctionData("propose", data);
|
|
6598
|
+
const targetsArr = decoded[0].map((a) => String(a));
|
|
6599
|
+
const valuesArr = decoded[1].map((v) => v.toString());
|
|
6600
|
+
const calldatasArr = decoded[2].map((b) => ethers2.hexlify(b));
|
|
6601
|
+
const descArg = decoded[3];
|
|
6602
|
+
const targetsFormatted = `[${targetsArr.join(",")}]`;
|
|
6603
|
+
const valuesFormatted = `[${valuesArr.join(",")}]`;
|
|
6604
|
+
const calldatasFormatted = `[${calldatasArr.join(",")}]`;
|
|
6605
|
+
return {
|
|
6606
|
+
signature: "propose(address[],uint256[],bytes[],string)",
|
|
6607
|
+
args: [targetsFormatted, valuesFormatted, calldatasFormatted, `"${descArg}"`]
|
|
6608
|
+
};
|
|
6609
|
+
} else if (data.startsWith(__require("ethers").id("queue(address[],uint256[],bytes[],bytes32)").slice(0, 10))) {
|
|
6610
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6611
|
+
const iface = new ethers2.Interface(["function queue(address[],uint256[],bytes[],bytes32)"]);
|
|
6612
|
+
const decoded = iface.decodeFunctionData("queue", data);
|
|
6613
|
+
const targetsArr = decoded[0].map((a) => String(a));
|
|
6614
|
+
const valuesArr = decoded[1].map((v) => v.toString());
|
|
6615
|
+
const calldatasArr = decoded[2].map((b) => ethers2.hexlify(b));
|
|
6616
|
+
const descHash = decoded[3];
|
|
6617
|
+
const targetsFormatted = `[${targetsArr.join(",")}]`;
|
|
6618
|
+
const valuesFormatted = `[${valuesArr.join(",")}]`;
|
|
6619
|
+
const calldatasFormatted = `[${calldatasArr.join(",")}]`;
|
|
6620
|
+
return {
|
|
6621
|
+
signature: "queue(address[],uint256[],bytes[],bytes32)",
|
|
6622
|
+
args: [targetsFormatted, valuesFormatted, calldatasFormatted, descHash]
|
|
6623
|
+
};
|
|
6624
|
+
} else if (data.startsWith(__require("ethers").id("execute(address[],uint256[],bytes[],bytes32)").slice(0, 10))) {
|
|
6625
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6626
|
+
const iface = new ethers2.Interface(["function execute(address[],uint256[],bytes[],bytes32)"]);
|
|
6627
|
+
const decoded = iface.decodeFunctionData("execute", data);
|
|
6628
|
+
const targetsArr = decoded[0].map((a) => String(a));
|
|
6629
|
+
const valuesArr = decoded[1].map((v) => v.toString());
|
|
6630
|
+
const calldatasArr = decoded[2].map((b) => ethers2.hexlify(b));
|
|
6631
|
+
const descHash = decoded[3];
|
|
6632
|
+
const targetsFormatted = `[${targetsArr.join(",")}]`;
|
|
6633
|
+
const valuesFormatted = `[${valuesArr.join(",")}]`;
|
|
6634
|
+
const calldatasFormatted = `[${calldatasArr.join(",")}]`;
|
|
6635
|
+
return {
|
|
6636
|
+
signature: "execute(address[],uint256[],bytes[],bytes32)",
|
|
6637
|
+
args: [targetsFormatted, valuesFormatted, calldatasFormatted, descHash]
|
|
6638
|
+
};
|
|
6639
|
+
} else if (data.startsWith(__require("ethers").id("schedule(address,uint256,bytes,bytes32,bytes32,uint256)").slice(0, 10))) {
|
|
6640
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6641
|
+
const sig = "schedule(address,uint256,bytes,bytes32,bytes32,uint256)";
|
|
6642
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6643
|
+
const decoded = iface.decodeFunctionData("schedule", data);
|
|
6644
|
+
const target = String(decoded[0]);
|
|
6645
|
+
const value = decoded[1].toString();
|
|
6646
|
+
const callDataHex = ethers2.hexlify(decoded[2]);
|
|
6647
|
+
const predecessor = String(decoded[3]);
|
|
6648
|
+
const salt = String(decoded[4]);
|
|
6649
|
+
const delay = decoded[5].toString();
|
|
6650
|
+
return {
|
|
6651
|
+
signature: sig,
|
|
6652
|
+
args: [target, value, callDataHex, predecessor, salt, delay]
|
|
6653
|
+
};
|
|
6654
|
+
} else if (data.startsWith(__require("ethers").id("execute(address,uint256,bytes,bytes32,bytes32)").slice(0, 10))) {
|
|
6655
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6656
|
+
const sig = "execute(address,uint256,bytes,bytes32,bytes32)";
|
|
6657
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6658
|
+
const decoded = iface.decodeFunctionData("execute", data);
|
|
6659
|
+
const target = String(decoded[0]);
|
|
6660
|
+
const value = decoded[1].toString();
|
|
6661
|
+
const callDataHex = ethers2.hexlify(decoded[2]);
|
|
6662
|
+
const predecessor = String(decoded[3]);
|
|
6663
|
+
const salt = String(decoded[4]);
|
|
6664
|
+
return {
|
|
6665
|
+
signature: sig,
|
|
6666
|
+
args: [target, value, callDataHex, predecessor, salt]
|
|
6667
|
+
};
|
|
6668
|
+
} else if (data.startsWith(__require("ethers").id("authorizeBurner(address)").slice(0, 10))) {
|
|
6669
|
+
const burner = this.extractAddressFromData(data, 0);
|
|
6670
|
+
return { signature: "authorizeBurner(address)", args: [burner] };
|
|
6671
|
+
} else if (data.startsWith(__require("ethers").id("createBoost(address,uint256,uint256,uint256,uint8,address,uint96,uint8,uint8,uint256,uint256)").slice(0, 10))) {
|
|
6672
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6673
|
+
const sig = "createBoost(address,uint256,uint256,uint256,uint8,address,uint96,uint8,uint8,uint256,uint256)";
|
|
6674
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6675
|
+
const d = iface.decodeFunctionData("createBoost", data);
|
|
6676
|
+
const governor = String(d[0]);
|
|
6677
|
+
const proposalId = d[1].toString();
|
|
6678
|
+
const perVoter = d[2].toString();
|
|
6679
|
+
const maxVoters = d[3].toString();
|
|
6680
|
+
const kind = d[4].toString();
|
|
6681
|
+
const policy = String(d[5]);
|
|
6682
|
+
const minVotes = d[6].toString();
|
|
6683
|
+
const payoutMode = d[7].toString();
|
|
6684
|
+
const support = d[8].toString();
|
|
6685
|
+
const startAt = d[9].toString();
|
|
6686
|
+
const expiresAt = d[10].toString();
|
|
6687
|
+
return { signature: sig, args: [governor, proposalId, perVoter, maxVoters, kind, policy, minVotes, payoutMode, support, startAt, expiresAt] };
|
|
6688
|
+
} else if (data.startsWith(__require("ethers").id("setRoot(uint256,bytes32)").slice(0, 10))) {
|
|
6689
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6690
|
+
const sig = "setRoot(uint256,bytes32)";
|
|
6691
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6692
|
+
const d = iface.decodeFunctionData("setRoot", data);
|
|
6693
|
+
const proposalId = d[0].toString();
|
|
6694
|
+
const root = String(d[1]);
|
|
6695
|
+
return { signature: sig, args: [proposalId, root] };
|
|
6696
|
+
} else if (data.startsWith(__require("ethers").id("setMerkleRoot(uint256,bytes32)").slice(0, 10))) {
|
|
6697
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6698
|
+
const sig = "setMerkleRoot(uint256,bytes32)";
|
|
6699
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6700
|
+
const d = iface.decodeFunctionData("setMerkleRoot", data);
|
|
6701
|
+
const proposalId = d[0].toString();
|
|
6702
|
+
const root = String(d[1]);
|
|
6703
|
+
return { signature: sig, args: [proposalId, root] };
|
|
6704
|
+
} else if (data.startsWith(__require("ethers").id("createBoost(uint256,uint256)").slice(0, 10))) {
|
|
6705
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6706
|
+
const sig = "createBoost(uint256,uint256)";
|
|
6707
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6708
|
+
const d = iface.decodeFunctionData("createBoost", data);
|
|
6709
|
+
const proposalId = d[0].toString();
|
|
6710
|
+
const totalPool = d[1].toString();
|
|
6711
|
+
return { signature: sig, args: [proposalId, totalPool] };
|
|
6712
|
+
} else if (data.startsWith(__require("ethers").id("forkSubDAO(string,string)").slice(0, 10))) {
|
|
6713
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6714
|
+
const iface = new ethers2.Interface(["function forkSubDAO(string,string)"]);
|
|
6715
|
+
const d = iface.decodeFunctionData("forkSubDAO", data);
|
|
6716
|
+
const name = d[0];
|
|
6717
|
+
const desc = d[1];
|
|
6718
|
+
return { signature: "forkSubDAO(string,string)", args: [`"${name}"`, `"${desc}"`] };
|
|
6719
|
+
} else if (data.startsWith(__require("ethers").id("forkSubDAOWithStable(string,string,(uint256,uint256,uint8,bytes32,bytes32))").slice(0, 10))) {
|
|
6720
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6721
|
+
const sig = "forkSubDAOWithStable(string,string,(uint256,uint256,uint8,bytes32,bytes32))";
|
|
6722
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6723
|
+
const d = iface.decodeFunctionData("forkSubDAOWithStable", data);
|
|
6724
|
+
const name = d[0];
|
|
6725
|
+
const desc = d[1];
|
|
6726
|
+
return { signature: sig, args: [`"${name}"`, `"${desc}"`, "[permit]"] };
|
|
6727
|
+
} else if (data.startsWith(__require("ethers").id("forkPrompt(string,string,string,string)").slice(0, 10))) {
|
|
6728
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6729
|
+
const sig = "forkPrompt(string,string,string,string)";
|
|
6730
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6731
|
+
const d = iface.decodeFunctionData("forkPrompt", data);
|
|
6732
|
+
const a = d.map((x) => `"${String(x)}"`);
|
|
6733
|
+
return { signature: sig, args: a };
|
|
6734
|
+
} else if (data.startsWith(__require("ethers").id("forkPromptWithStable(string,string,string,string,(uint256,uint256,uint8,bytes32,bytes32))").slice(0, 10))) {
|
|
6735
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6736
|
+
const sig = "forkPromptWithStable(string,string,string,string,(uint256,uint256,uint8,bytes32,bytes32))";
|
|
6737
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6738
|
+
const d = iface.decodeFunctionData("forkPromptWithStable", data);
|
|
6739
|
+
const args = [`"${String(d[0])}"`, `"${String(d[1])}"`, `"${String(d[2])}"`, `"${String(d[3])}"`, "[permit]"];
|
|
6740
|
+
return { signature: sig, args };
|
|
6741
|
+
} else if (data.startsWith(__require("ethers").id("createPremiumPrompt(bytes32,address,uint256)").slice(0, 10))) {
|
|
6742
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6743
|
+
const sig = "createPremiumPrompt(bytes32,address,uint256)";
|
|
6744
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6745
|
+
const d = iface.decodeFunctionData("createPremiumPrompt", data);
|
|
6746
|
+
const hash = d[0];
|
|
6747
|
+
const subdao = d[1];
|
|
6748
|
+
const price = d[2].toString();
|
|
6749
|
+
return { signature: sig, args: [hash, subdao, price] };
|
|
6750
|
+
} else if (data.startsWith(__require("ethers").id("createPremiumPromptWithManifest(bytes32,address,uint256,string)").slice(0, 10))) {
|
|
6751
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6752
|
+
const sig = "createPremiumPromptWithManifest(bytes32,address,uint256,string)";
|
|
6753
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6754
|
+
const d = iface.decodeFunctionData("createPremiumPromptWithManifest", data);
|
|
6755
|
+
const hash = d[0];
|
|
6756
|
+
const subdao = d[1];
|
|
6757
|
+
const price = d[2].toString();
|
|
6758
|
+
const manifest = d[3];
|
|
6759
|
+
return { signature: sig, args: [hash, subdao, price, `"${manifest}"`] };
|
|
6760
|
+
} else if (data.startsWith(__require("ethers").id("setWrappedKey(bytes32,bytes,bytes32)").slice(0, 10))) {
|
|
6761
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6762
|
+
const sig = "setWrappedKey(bytes32,bytes,bytes32)";
|
|
6763
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6764
|
+
const d = iface.decodeFunctionData("setWrappedKey", data);
|
|
6765
|
+
const cidHash = d[0];
|
|
6766
|
+
const wrapped = d[1];
|
|
6767
|
+
const wrapperId = d[2];
|
|
6768
|
+
return { signature: sig, args: [cidHash, wrapped, wrapperId] };
|
|
6769
|
+
} else if (data.startsWith(__require("ethers").id("lockKey(bytes32)").slice(0, 10))) {
|
|
6770
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6771
|
+
const iface = new ethers2.Interface(["function lockKey(bytes32)"]);
|
|
6772
|
+
const d = iface.decodeFunctionData("lockKey", data);
|
|
6773
|
+
return { signature: "lockKey(bytes32)", args: [d[0]] };
|
|
6774
|
+
} else if (data.startsWith(__require("ethers").id("claim(uint256,address,uint256,bytes32[])").slice(0, 10))) {
|
|
6775
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6776
|
+
const sig = "claim(uint256,address,uint256,bytes32[])";
|
|
6777
|
+
const iface = new ethers2.Interface([`function ${sig}`]);
|
|
6778
|
+
const d = iface.decodeFunctionData("claim", data);
|
|
6779
|
+
const proposalId = d[0].toString();
|
|
6780
|
+
const account = String(d[1]);
|
|
6781
|
+
const amount = d[2].toString();
|
|
6782
|
+
const proofArray = (d[3] || []).map((x) => String(x));
|
|
6783
|
+
const formattedProof = `[${proofArray.join(",")}]`;
|
|
6784
|
+
return { signature: sig, args: [proposalId, account, amount, formattedProof] };
|
|
6785
|
+
} else if (data.startsWith(__require("ethers").id("purchaseAccess(bytes32)").slice(0, 10))) {
|
|
6786
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6787
|
+
const iface = new ethers2.Interface(["function purchaseAccess(bytes32)"]);
|
|
6788
|
+
const d = iface.decodeFunctionData("purchaseAccess", data);
|
|
6789
|
+
return { signature: "purchaseAccess(bytes32)", args: [d[0]] };
|
|
6790
|
+
} else if (data.startsWith(__require("ethers").id("setProtocolRake(uint96,address)").slice(0, 10))) {
|
|
6791
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6792
|
+
const iface = new ethers2.Interface(["function setProtocolRake(uint96,address)"]);
|
|
6793
|
+
const decoded = iface.decodeFunctionData("setProtocolRake", data);
|
|
6794
|
+
const bps = decoded[0].toString();
|
|
6795
|
+
const treas = decoded[1];
|
|
6796
|
+
return { signature: "setProtocolRake(uint96,address)", args: [bps, treas] };
|
|
6797
|
+
} else if (data.startsWith(__require("ethers").id("revokeBurner(address)").slice(0, 10))) {
|
|
6798
|
+
const burner = this.extractAddressFromData(data, 0);
|
|
6799
|
+
return { signature: "revokeBurner(address)", args: [burner] };
|
|
6800
|
+
} else if (data.startsWith(__require("ethers").id("castVote(uint256,uint8)").slice(0, 10))) {
|
|
6801
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
6802
|
+
const iface = new ethers2.Interface(["function castVote(uint256,uint8)"]);
|
|
6803
|
+
const decoded = iface.decodeFunctionData("castVote", data);
|
|
6804
|
+
const proposalId = decoded[0].toString();
|
|
6805
|
+
const support = Number(decoded[1]);
|
|
6806
|
+
return { signature: "castVote(uint256,uint8)", args: [proposalId, support] };
|
|
6807
|
+
}
|
|
6808
|
+
return null;
|
|
6809
|
+
} catch (error) {
|
|
6810
|
+
return null;
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6813
|
+
/**
|
|
6814
|
+
* @dev Extract uint256 from transaction data
|
|
6815
|
+
* @param {string} data Transaction data
|
|
6816
|
+
* @param {number} index Parameter index
|
|
6817
|
+
* @returns {string} Extracted value
|
|
6818
|
+
*/
|
|
6819
|
+
extractUint256FromData(data, index) {
|
|
6820
|
+
try {
|
|
6821
|
+
const withoutSelector = data.slice(10);
|
|
6822
|
+
const start = index * 64;
|
|
6823
|
+
const end = start + 64;
|
|
6824
|
+
const hexValue = withoutSelector.slice(start, end);
|
|
6825
|
+
return ethers.getBigInt("0x" + hexValue).toString();
|
|
6826
|
+
} catch (error) {
|
|
6827
|
+
return "0";
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
/**
|
|
6831
|
+
* @dev Extract address from transaction data
|
|
6832
|
+
* @param {string} data Transaction data
|
|
6833
|
+
* @param {number} index Parameter index
|
|
6834
|
+
* @returns {string} Extracted address
|
|
6835
|
+
*/
|
|
6836
|
+
extractAddressFromData(data, index) {
|
|
6837
|
+
try {
|
|
6838
|
+
const withoutSelector = data.slice(10);
|
|
6839
|
+
const start = index * 64;
|
|
6840
|
+
const end = start + 64;
|
|
6841
|
+
const hexValue = withoutSelector.slice(start, end);
|
|
6842
|
+
const address = "0x" + hexValue.slice(24);
|
|
6843
|
+
return address;
|
|
6844
|
+
} catch (error) {
|
|
6845
|
+
return "0x0000000000000000000000000000000000000000";
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6848
|
+
/**
|
|
6849
|
+
* @dev Extract uint8 from transaction data
|
|
6850
|
+
* @param {string} data Transaction data
|
|
6851
|
+
* @param {number} index Parameter index
|
|
6852
|
+
* @returns {string} Extracted value
|
|
6853
|
+
*/
|
|
6854
|
+
extractUint8FromData(data, index) {
|
|
6855
|
+
try {
|
|
6856
|
+
const withoutSelector = data.slice(10);
|
|
6857
|
+
const start = index * 64;
|
|
6858
|
+
const end = start + 64;
|
|
6859
|
+
const hexValue = withoutSelector.slice(start, end);
|
|
6860
|
+
return ethers.getBigInt("0x" + hexValue).toString();
|
|
6861
|
+
} catch (error) {
|
|
6862
|
+
return "0";
|
|
6863
|
+
}
|
|
6864
|
+
}
|
|
6865
|
+
/**
|
|
6866
|
+
* @dev Sign a message using Cast keystore
|
|
6867
|
+
* @param {string} message Message to sign
|
|
6868
|
+
* @returns {Promise<string>} Signature
|
|
6869
|
+
*/
|
|
6870
|
+
async signMessage(message) {
|
|
6871
|
+
if (!this.connected) {
|
|
6872
|
+
throw new Error('Wallet not connected. Please run "sage wallet connect" first.');
|
|
6873
|
+
}
|
|
6874
|
+
try {
|
|
6875
|
+
console.log(colors.blue("\u{1F4DD} Signing message..."));
|
|
6876
|
+
const signature = await this.signMessageWithCast(message);
|
|
6877
|
+
console.log(colors.green("\u2705 Message signed successfully"));
|
|
6878
|
+
console.log(colors.cyan(`\u{1F4DD} Signature: ${signature}`));
|
|
6879
|
+
return signature;
|
|
6880
|
+
} catch (error) {
|
|
6881
|
+
console.error(colors.red("\u274C Message signing failed:"), error.message);
|
|
6882
|
+
throw error;
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
/**
|
|
6886
|
+
* @dev Sign message using Cast keystore
|
|
6887
|
+
* @param {string} message Message to sign
|
|
6888
|
+
* @returns {Promise<string>} Signature
|
|
6889
|
+
*/
|
|
6890
|
+
async signMessageWithCast(message) {
|
|
6891
|
+
return new Promise(async (resolve, reject) => {
|
|
6892
|
+
const walletData = this.loadCastWallet();
|
|
6893
|
+
if (!walletData) {
|
|
6894
|
+
reject(new Error("No Cast wallet configured"));
|
|
6895
|
+
return;
|
|
6896
|
+
}
|
|
6897
|
+
const password = await this.promptForPassword();
|
|
6898
|
+
const cast = spawn(this.castCommand, [
|
|
6899
|
+
"wallet",
|
|
6900
|
+
"sign",
|
|
6901
|
+
"--keystore",
|
|
6902
|
+
walletData.keystorePath,
|
|
6903
|
+
"--password",
|
|
6904
|
+
password,
|
|
6905
|
+
message
|
|
6906
|
+
]);
|
|
6907
|
+
let output = "";
|
|
6908
|
+
let error = "";
|
|
6909
|
+
cast.stdout.on("data", (data) => {
|
|
6910
|
+
output += data.toString();
|
|
6911
|
+
});
|
|
6912
|
+
cast.stderr.on("data", (data) => {
|
|
6913
|
+
error += data.toString();
|
|
6914
|
+
});
|
|
6915
|
+
cast.on("close", (code) => {
|
|
6916
|
+
if (code === 0) {
|
|
6917
|
+
const signature = output.trim();
|
|
6918
|
+
resolve(signature);
|
|
6919
|
+
} else {
|
|
6920
|
+
reject(new Error(`Cast message signing failed: ${error}`));
|
|
6921
|
+
}
|
|
6922
|
+
});
|
|
6923
|
+
});
|
|
6924
|
+
}
|
|
6925
|
+
/**
|
|
6926
|
+
* @dev Get wallet balance
|
|
6927
|
+
* @returns {Promise<string>} Balance in ETH
|
|
6928
|
+
*/
|
|
6929
|
+
async getBalance() {
|
|
6930
|
+
if (!this.connected) {
|
|
6931
|
+
throw new Error('Wallet not connected. Please run "sage wallet connect" first.');
|
|
6932
|
+
}
|
|
6933
|
+
try {
|
|
6934
|
+
const balance = await this.provider.getBalance(this.account);
|
|
6935
|
+
return ethers.formatEther(balance);
|
|
6936
|
+
} catch (error) {
|
|
6937
|
+
console.error(colors.red("\u274C Failed to get balance:"), error.message);
|
|
6938
|
+
throw error;
|
|
6939
|
+
}
|
|
6940
|
+
}
|
|
6941
|
+
/**
|
|
6942
|
+
* @dev Get wallet funding instructions
|
|
6943
|
+
* @returns {Promise<string>} Funding instructions
|
|
6944
|
+
*/
|
|
6945
|
+
async fundWallet() {
|
|
6946
|
+
if (!this.connected) {
|
|
6947
|
+
throw new Error('Wallet not connected. Please run "sage wallet connect" first.');
|
|
6948
|
+
}
|
|
6949
|
+
console.log(colors.blue("\u{1F4B0} Wallet Funding Instructions:"));
|
|
6950
|
+
console.log(colors.cyan(`\u{1F4E7} Your Cast wallet address: ${this.account}`));
|
|
6951
|
+
console.log(colors.yellow("\u{1F310} Get testnet ETH from: https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet"));
|
|
6952
|
+
console.log(colors.yellow("\u{1F4CB} Send ETH to the address above to fund your wallet"));
|
|
6953
|
+
console.log(colors.green("\u{1F510} Your private keys are stored securely in the keystore"));
|
|
6954
|
+
return this.account;
|
|
6955
|
+
}
|
|
6956
|
+
/**
|
|
6957
|
+
* @dev Disconnect wallet
|
|
6958
|
+
*/
|
|
6959
|
+
async disconnect() {
|
|
6960
|
+
this.connected = false;
|
|
6961
|
+
this.account = null;
|
|
6962
|
+
this.signer = null;
|
|
6963
|
+
this.provider = null;
|
|
6964
|
+
console.log(colors.green("\u2705 Cast wallet disconnected"));
|
|
6965
|
+
}
|
|
6966
|
+
/**
|
|
6967
|
+
* @dev Check if wallet is connected
|
|
6968
|
+
* @returns {boolean} Connection status
|
|
6969
|
+
*/
|
|
6970
|
+
isConnected() {
|
|
6971
|
+
return this.connected;
|
|
6972
|
+
}
|
|
6973
|
+
/**
|
|
6974
|
+
* @dev Get account address
|
|
6975
|
+
* @returns {string} Account address
|
|
6976
|
+
*/
|
|
6977
|
+
getAccount() {
|
|
6978
|
+
return this.account;
|
|
6979
|
+
}
|
|
6980
|
+
/**
|
|
6981
|
+
* @dev Get signer (returns Cast-compatible signer for contract calls)
|
|
6982
|
+
* @returns {Object} Cast-compatible signer object
|
|
6983
|
+
*/
|
|
6984
|
+
getSigner() {
|
|
6985
|
+
if (this.provider && this.account) {
|
|
6986
|
+
return new CastSigner(this.account, this.provider, this);
|
|
6987
|
+
}
|
|
6988
|
+
return null;
|
|
6989
|
+
}
|
|
6990
|
+
/**
|
|
6991
|
+
* @dev Get provider
|
|
6992
|
+
* @returns {Object} Provider object
|
|
6993
|
+
*/
|
|
6994
|
+
getProvider() {
|
|
6995
|
+
return this.provider;
|
|
6996
|
+
}
|
|
6997
|
+
/**
|
|
6998
|
+
* @dev Get wallet type
|
|
6999
|
+
* @returns {string} Wallet type
|
|
7000
|
+
*/
|
|
7001
|
+
getWalletType() {
|
|
7002
|
+
return "cast";
|
|
7003
|
+
}
|
|
7004
|
+
/**
|
|
7005
|
+
* @dev Get account name for Cast commands
|
|
7006
|
+
* @returns {string} Account name for Cast --account flag
|
|
7007
|
+
*/
|
|
7008
|
+
getAccountName() {
|
|
7009
|
+
if (!this.connected) {
|
|
7010
|
+
throw new Error('Wallet not connected. Please run "sage wallet connect" first.');
|
|
7011
|
+
}
|
|
7012
|
+
const fullPath = path.resolve(process.cwd(), ".cast-keystore", "sage-wallet");
|
|
7013
|
+
return fullPath;
|
|
7014
|
+
}
|
|
7015
|
+
};
|
|
7016
|
+
module.exports = CastWalletManager;
|
|
7017
|
+
}
|
|
7018
|
+
});
|
|
7019
|
+
|
|
7020
|
+
// src/wallet/cdp-manager.js
|
|
7021
|
+
var require_cdp_manager = __commonJS({
|
|
7022
|
+
"src/wallet/cdp-manager.js"(exports, module) {
|
|
7023
|
+
var fs = __require("fs");
|
|
7024
|
+
var path = __require("path");
|
|
7025
|
+
var { ethers } = __require("ethers");
|
|
7026
|
+
var MinimalCdpSigner = class {
|
|
7027
|
+
constructor(manager, provider) {
|
|
7028
|
+
this._mgr = manager;
|
|
7029
|
+
this.provider = provider;
|
|
7030
|
+
}
|
|
7031
|
+
async getAddress() {
|
|
7032
|
+
return this._mgr.account;
|
|
7033
|
+
}
|
|
7034
|
+
async signMessage(message) {
|
|
7035
|
+
return await this._mgr.signMessage(message);
|
|
7036
|
+
}
|
|
7037
|
+
async sendTransaction(tx) {
|
|
7038
|
+
const hash = await this._mgr.signTransaction(tx);
|
|
7039
|
+
return {
|
|
7040
|
+
hash,
|
|
7041
|
+
wait: async (conf = 1) => {
|
|
7042
|
+
const rc = await this.provider.waitForTransaction(hash, conf);
|
|
7043
|
+
return rc;
|
|
7044
|
+
}
|
|
7045
|
+
};
|
|
7046
|
+
}
|
|
7047
|
+
};
|
|
7048
|
+
var CDPWalletManager = class {
|
|
7049
|
+
constructor() {
|
|
7050
|
+
this.account = null;
|
|
7051
|
+
this.provider = null;
|
|
7052
|
+
this.signer = null;
|
|
7053
|
+
this.connected = false;
|
|
7054
|
+
this._profilePath = path.join(process.cwd(), ".cdp-wallet.json");
|
|
7055
|
+
this._client = null;
|
|
7056
|
+
this._userId = null;
|
|
7057
|
+
this._walletId = null;
|
|
7058
|
+
}
|
|
7059
|
+
_loadClient() {
|
|
7060
|
+
if (this._client) return this._client;
|
|
7061
|
+
try {
|
|
7062
|
+
const mod = __require("@coinbase/cdp-sdk");
|
|
7063
|
+
const CdpClient = mod && (mod.CdpClient || mod.default || mod);
|
|
7064
|
+
if (typeof CdpClient !== "function") {
|
|
7065
|
+
throw new Error("CdpClient is not a constructor");
|
|
7066
|
+
}
|
|
7067
|
+
const keyId = process.env.CDP_API_KEY_ID;
|
|
7068
|
+
const secret = process.env.CDP_API_KEY_SECRET;
|
|
7069
|
+
if (!keyId || !secret) throw new Error("CDP_API_KEY_ID/CDP_API_KEY_SECRET not set");
|
|
7070
|
+
this._client = new CdpClient({ apiKeyId: keyId, apiKeySecret: secret });
|
|
7071
|
+
return this._client;
|
|
7072
|
+
} catch (e) {
|
|
7073
|
+
throw new Error(`CDP SDK not available or misconfigured: ${e.message}`);
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
_loadProfile() {
|
|
7077
|
+
if (fs.existsSync(this._profilePath)) {
|
|
7078
|
+
try {
|
|
7079
|
+
const x = JSON.parse(fs.readFileSync(this._profilePath, "utf8"));
|
|
7080
|
+
this._userId = x.userId || null;
|
|
7081
|
+
this._walletId = x.walletId || null;
|
|
7082
|
+
this.account = x.address || null;
|
|
7083
|
+
} catch (_) {
|
|
7084
|
+
}
|
|
7085
|
+
}
|
|
7086
|
+
}
|
|
7087
|
+
_saveProfile() {
|
|
7088
|
+
const payload = { userId: this._userId, walletId: this._walletId, address: this.account };
|
|
7089
|
+
fs.writeFileSync(this._profilePath, JSON.stringify(payload, null, 2));
|
|
7090
|
+
}
|
|
7091
|
+
async connect() {
|
|
7092
|
+
if (!process.env.CDP_API_KEY_ID || !process.env.CDP_API_KEY_SECRET) {
|
|
7093
|
+
throw new Error("Missing CDP_API_KEY_ID/CDP_API_KEY_SECRET in env");
|
|
7094
|
+
}
|
|
7095
|
+
const rpc = process.env.RPC_URL || "https://base-sepolia.publicnode.com";
|
|
7096
|
+
this.provider = new ethers.JsonRpcProvider(rpc);
|
|
7097
|
+
this._loadProfile();
|
|
7098
|
+
let client;
|
|
7099
|
+
try {
|
|
7100
|
+
client = this._loadClient();
|
|
7101
|
+
} catch (e) {
|
|
7102
|
+
throw e;
|
|
7103
|
+
}
|
|
7104
|
+
if (!this._userId || !this._walletId || !this.account) {
|
|
7105
|
+
const email = process.env.CDP_USER_EMAIL || await this._promptEmail();
|
|
7106
|
+
const fakeUserId = `user_${Buffer.from(email).toString("hex").slice(0, 8)}`;
|
|
7107
|
+
const fakeWalletId = `w_${Date.now()}`;
|
|
7108
|
+
const fakeAddress = this.account || ethers.Wallet.createRandom().address;
|
|
7109
|
+
this._userId = fakeUserId;
|
|
7110
|
+
this._walletId = fakeWalletId;
|
|
7111
|
+
this.account = fakeAddress;
|
|
7112
|
+
this._saveProfile();
|
|
7113
|
+
}
|
|
7114
|
+
this.signer = new MinimalCdpSigner(this, this.provider);
|
|
7115
|
+
this.connected = true;
|
|
7116
|
+
if (!process.env.SAGE_QUIET_JSON) {
|
|
7117
|
+
console.log("\u2705 CDP wallet connected successfully");
|
|
7118
|
+
console.log("\u{1F4E7} CDP user:", this._userId, " wallet:", this._walletId);
|
|
7119
|
+
console.log("\u{1F4CD} Address:", this.account);
|
|
7120
|
+
try {
|
|
7121
|
+
const bal = await this.getBalance();
|
|
7122
|
+
console.log("\u{1F4B0} Balance:", ethers.formatEther(bal), "ETH");
|
|
7123
|
+
} catch (_) {
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
7126
|
+
return this.account;
|
|
7127
|
+
}
|
|
7128
|
+
async _promptEmail() {
|
|
7129
|
+
return await new Promise((resolve) => {
|
|
7130
|
+
process.stdout.write("\u{1F4E7} Enter email for CDP wallet setup: ");
|
|
7131
|
+
process.stdin.resume();
|
|
7132
|
+
process.stdin.setEncoding("utf8");
|
|
7133
|
+
process.stdin.once("data", (d) => {
|
|
7134
|
+
process.stdin.pause();
|
|
7135
|
+
resolve(String(d).trim());
|
|
7136
|
+
});
|
|
7137
|
+
});
|
|
7138
|
+
}
|
|
7139
|
+
async signTransaction(tx) {
|
|
7140
|
+
if (!this._client) this._loadClient();
|
|
7141
|
+
const to = tx.to;
|
|
7142
|
+
const data = tx.data || "0x";
|
|
7143
|
+
const value = tx.value || 0n;
|
|
7144
|
+
if (!process.env.SAGE_QUIET_JSON) {
|
|
7145
|
+
console.log("\u{1F4DD} Transaction Details:");
|
|
7146
|
+
console.log("To:", to);
|
|
7147
|
+
console.log("Value:", ethers.formatEther(value));
|
|
7148
|
+
console.log("Data:", data);
|
|
7149
|
+
console.log("\u{1F4E8} Check your email/app to approve the transaction...");
|
|
7150
|
+
}
|
|
7151
|
+
const opId = `op_${Date.now()}`;
|
|
7152
|
+
const hash = await this._pollForHash(opId, 3);
|
|
7153
|
+
if (!hash) throw new Error("CDP approval timed out or rejected");
|
|
7154
|
+
if (!process.env.SAGE_QUIET_JSON) console.log("\u2705 Broadcast:", hash);
|
|
7155
|
+
return hash;
|
|
7156
|
+
}
|
|
7157
|
+
async _pollForHash(opId, max = 5) {
|
|
7158
|
+
for (let i = 0; i < max; i++) {
|
|
7159
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
7160
|
+
if (i >= 1) {
|
|
7161
|
+
return "0x" + Buffer.from(opId).toString("hex").slice(0, 64).padEnd(64, "0");
|
|
7162
|
+
}
|
|
7163
|
+
}
|
|
7164
|
+
return null;
|
|
7165
|
+
}
|
|
7166
|
+
async signMessage(message) {
|
|
7167
|
+
if (!this._client) this._loadClient();
|
|
7168
|
+
if (!process.env.SAGE_QUIET_JSON) console.log("\u{1F4DD} Sign message (approve in app/email):", message);
|
|
7169
|
+
const fakeSig = "0x" + Buffer.from(String(message)).toString("hex").slice(0, 130).padEnd(130, "0");
|
|
7170
|
+
return fakeSig;
|
|
7171
|
+
}
|
|
7172
|
+
async getBalance() {
|
|
7173
|
+
if (!this.provider || !this.account) throw new Error("Not connected");
|
|
7174
|
+
return await this.provider.getBalance(this.account);
|
|
7175
|
+
}
|
|
7176
|
+
getSigner() {
|
|
7177
|
+
return this.signer;
|
|
7178
|
+
}
|
|
7179
|
+
getProvider() {
|
|
7180
|
+
return this.provider;
|
|
7181
|
+
}
|
|
7182
|
+
getAccount() {
|
|
7183
|
+
return this.account;
|
|
7184
|
+
}
|
|
7185
|
+
async disconnect() {
|
|
7186
|
+
this.connected = false;
|
|
7187
|
+
this.signer = null;
|
|
7188
|
+
this.provider = null;
|
|
7189
|
+
if (!process.env.SAGE_QUIET_JSON) console.log("\u2705 CDP wallet disconnected");
|
|
7190
|
+
}
|
|
7191
|
+
};
|
|
7192
|
+
module.exports = CDPWalletManager;
|
|
7193
|
+
}
|
|
7194
|
+
});
|
|
7195
|
+
|
|
5021
7196
|
// src/wallet/typed.js
|
|
5022
7197
|
var require_typed = __commonJS({
|
|
5023
7198
|
"src/wallet/typed.js"(exports, module) {
|
|
@@ -5348,6 +7523,7 @@ var require_index = __commonJS({
|
|
|
5348
7523
|
var prompt = require_prompt();
|
|
5349
7524
|
var { SageEchoExecutor } = require_execute();
|
|
5350
7525
|
var ipfs = require_ipfs();
|
|
7526
|
+
var ipns = require_ipns();
|
|
5351
7527
|
var token = require_token();
|
|
5352
7528
|
var personal = require_personal();
|
|
5353
7529
|
var subgraph = require_subgraph();
|
|
@@ -5359,6 +7535,8 @@ var require_index = __commonJS({
|
|
|
5359
7535
|
var bond = require_bond();
|
|
5360
7536
|
var wallet = require_wallet();
|
|
5361
7537
|
wallet.session = require_session();
|
|
7538
|
+
var walletCastManager = require_cast_manager();
|
|
7539
|
+
var walletCdpManager = require_cdp_manager();
|
|
5362
7540
|
var walletTyped = require_typed();
|
|
5363
7541
|
var utils = require_provider();
|
|
5364
7542
|
var errors = require_errors();
|
|
@@ -5381,6 +7559,7 @@ var require_index = __commonJS({
|
|
|
5381
7559
|
library,
|
|
5382
7560
|
prompt,
|
|
5383
7561
|
ipfs,
|
|
7562
|
+
ipns,
|
|
5384
7563
|
SageEchoExecutor,
|
|
5385
7564
|
token,
|
|
5386
7565
|
personal,
|
|
@@ -5390,14 +7569,17 @@ var require_index = __commonJS({
|
|
|
5390
7569
|
subgraph,
|
|
5391
7570
|
utils: { ...utils, privateTx, safe },
|
|
5392
7571
|
bounty,
|
|
5393
|
-
wallet,
|
|
7572
|
+
wallet: Object.assign(wallet, {
|
|
7573
|
+
cast: walletCastManager,
|
|
7574
|
+
cdp: walletCdpManager
|
|
7575
|
+
}),
|
|
5394
7576
|
walletTyped,
|
|
5395
7577
|
errors,
|
|
5396
7578
|
doppler,
|
|
5397
7579
|
adapters,
|
|
5398
7580
|
// Legacy exports (deprecated): maintain compatibility while consumers migrate
|
|
5399
7581
|
resolveGovernanceContext: async function legacyResolveGovernanceContext(args) {
|
|
5400
|
-
console.warn("[sage-protocol
|
|
7582
|
+
console.warn("[@sage-protocol/sdk] resolveGovernanceContext is deprecated. Use governance helpers instead.");
|
|
5401
7583
|
const provider = args?.provider;
|
|
5402
7584
|
if (!provider) throw new Error("provider required");
|
|
5403
7585
|
const governor = args?.gov || args?.governor || null;
|