arc402-cli 0.2.0
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/README.md +245 -0
- package/dist/abis.d.ts +19 -0
- package/dist/abis.d.ts.map +1 -0
- package/dist/abis.js +177 -0
- package/dist/abis.js.map +1 -0
- package/dist/bundler.d.ts +65 -0
- package/dist/bundler.d.ts.map +1 -0
- package/dist/bundler.js +181 -0
- package/dist/bundler.js.map +1 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +24 -0
- package/dist/client.js.map +1 -0
- package/dist/coinbase-smart-wallet.d.ts +28 -0
- package/dist/coinbase-smart-wallet.d.ts.map +1 -0
- package/dist/coinbase-smart-wallet.js +38 -0
- package/dist/coinbase-smart-wallet.js.map +1 -0
- package/dist/commands/accept.d.ts +3 -0
- package/dist/commands/accept.d.ts.map +1 -0
- package/dist/commands/accept.js +26 -0
- package/dist/commands/accept.js.map +1 -0
- package/dist/commands/agent-handshake.d.ts +3 -0
- package/dist/commands/agent-handshake.d.ts.map +1 -0
- package/dist/commands/agent-handshake.js +61 -0
- package/dist/commands/agent-handshake.js.map +1 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +417 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/agreements.d.ts +3 -0
- package/dist/commands/agreements.d.ts.map +1 -0
- package/dist/commands/agreements.js +344 -0
- package/dist/commands/agreements.js.map +1 -0
- package/dist/commands/arbitrator.d.ts +3 -0
- package/dist/commands/arbitrator.d.ts.map +1 -0
- package/dist/commands/arbitrator.js +157 -0
- package/dist/commands/arbitrator.js.map +1 -0
- package/dist/commands/arena-handshake.d.ts +3 -0
- package/dist/commands/arena-handshake.d.ts.map +1 -0
- package/dist/commands/arena-handshake.js +187 -0
- package/dist/commands/arena-handshake.js.map +1 -0
- package/dist/commands/cancel.d.ts +3 -0
- package/dist/commands/cancel.d.ts.map +1 -0
- package/dist/commands/cancel.js +30 -0
- package/dist/commands/cancel.js.map +1 -0
- package/dist/commands/channel.d.ts +3 -0
- package/dist/commands/channel.d.ts.map +1 -0
- package/dist/commands/channel.js +238 -0
- package/dist/commands/channel.js.map +1 -0
- package/dist/commands/coldstart.d.ts +3 -0
- package/dist/commands/coldstart.d.ts.map +1 -0
- package/dist/commands/coldstart.js +148 -0
- package/dist/commands/coldstart.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +40 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/contract-interaction.d.ts +3 -0
- package/dist/commands/contract-interaction.d.ts.map +1 -0
- package/dist/commands/contract-interaction.js +165 -0
- package/dist/commands/contract-interaction.js.map +1 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +891 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/deliver.d.ts +3 -0
- package/dist/commands/deliver.d.ts.map +1 -0
- package/dist/commands/deliver.js +156 -0
- package/dist/commands/deliver.js.map +1 -0
- package/dist/commands/discover.d.ts +3 -0
- package/dist/commands/discover.d.ts.map +1 -0
- package/dist/commands/discover.js +224 -0
- package/dist/commands/discover.js.map +1 -0
- package/dist/commands/dispute.d.ts +3 -0
- package/dist/commands/dispute.d.ts.map +1 -0
- package/dist/commands/dispute.js +348 -0
- package/dist/commands/dispute.js.map +1 -0
- package/dist/commands/endpoint.d.ts +3 -0
- package/dist/commands/endpoint.d.ts.map +1 -0
- package/dist/commands/endpoint.js +604 -0
- package/dist/commands/endpoint.js.map +1 -0
- package/dist/commands/hire.d.ts +3 -0
- package/dist/commands/hire.d.ts.map +1 -0
- package/dist/commands/hire.js +189 -0
- package/dist/commands/hire.js.map +1 -0
- package/dist/commands/migrate.d.ts +3 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +163 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/negotiate.d.ts +3 -0
- package/dist/commands/negotiate.d.ts.map +1 -0
- package/dist/commands/negotiate.js +247 -0
- package/dist/commands/negotiate.js.map +1 -0
- package/dist/commands/openshell.d.ts +3 -0
- package/dist/commands/openshell.d.ts.map +1 -0
- package/dist/commands/openshell.js +952 -0
- package/dist/commands/openshell.js.map +1 -0
- package/dist/commands/owner.d.ts +3 -0
- package/dist/commands/owner.d.ts.map +1 -0
- package/dist/commands/owner.js +32 -0
- package/dist/commands/owner.js.map +1 -0
- package/dist/commands/policy.d.ts +4 -0
- package/dist/commands/policy.d.ts.map +1 -0
- package/dist/commands/policy.js +248 -0
- package/dist/commands/policy.js.map +1 -0
- package/dist/commands/relay.d.ts +3 -0
- package/dist/commands/relay.d.ts.map +1 -0
- package/dist/commands/relay.js +279 -0
- package/dist/commands/relay.js.map +1 -0
- package/dist/commands/remediate.d.ts +3 -0
- package/dist/commands/remediate.d.ts.map +1 -0
- package/dist/commands/remediate.js +42 -0
- package/dist/commands/remediate.js.map +1 -0
- package/dist/commands/reputation.d.ts +4 -0
- package/dist/commands/reputation.d.ts.map +1 -0
- package/dist/commands/reputation.js +72 -0
- package/dist/commands/reputation.js.map +1 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +332 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/trust.d.ts +3 -0
- package/dist/commands/trust.d.ts.map +1 -0
- package/dist/commands/trust.js +23 -0
- package/dist/commands/trust.js.map +1 -0
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +88 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/wallet.d.ts +3 -0
- package/dist/commands/wallet.d.ts.map +1 -0
- package/dist/commands/wallet.js +2520 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/commands/watchtower.d.ts +3 -0
- package/dist/commands/watchtower.d.ts.map +1 -0
- package/dist/commands/watchtower.js +238 -0
- package/dist/commands/watchtower.js.map +1 -0
- package/dist/commands/workroom.d.ts +3 -0
- package/dist/commands/workroom.d.ts.map +1 -0
- package/dist/commands/workroom.js +855 -0
- package/dist/commands/workroom.js.map +1 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +141 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/config.d.ts +74 -0
- package/dist/daemon/config.d.ts.map +1 -0
- package/dist/daemon/config.js +271 -0
- package/dist/daemon/config.js.map +1 -0
- package/dist/daemon/hire-listener.d.ts +31 -0
- package/dist/daemon/hire-listener.d.ts.map +1 -0
- package/dist/daemon/hire-listener.js +207 -0
- package/dist/daemon/hire-listener.js.map +1 -0
- package/dist/daemon/index.d.ts +29 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +535 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/job-lifecycle.d.ts +62 -0
- package/dist/daemon/job-lifecycle.d.ts.map +1 -0
- package/dist/daemon/job-lifecycle.js +201 -0
- package/dist/daemon/job-lifecycle.js.map +1 -0
- package/dist/daemon/notify.d.ts +22 -0
- package/dist/daemon/notify.d.ts.map +1 -0
- package/dist/daemon/notify.js +148 -0
- package/dist/daemon/notify.js.map +1 -0
- package/dist/daemon/token-metering.d.ts +42 -0
- package/dist/daemon/token-metering.d.ts.map +1 -0
- package/dist/daemon/token-metering.js +178 -0
- package/dist/daemon/token-metering.js.map +1 -0
- package/dist/daemon/userops.d.ts +21 -0
- package/dist/daemon/userops.d.ts.map +1 -0
- package/dist/daemon/userops.js +88 -0
- package/dist/daemon/userops.js.map +1 -0
- package/dist/daemon/wallet-monitor.d.ts +16 -0
- package/dist/daemon/wallet-monitor.d.ts.map +1 -0
- package/dist/daemon/wallet-monitor.js +57 -0
- package/dist/daemon/wallet-monitor.js.map +1 -0
- package/dist/drain-v4.d.ts +2 -0
- package/dist/drain-v4.d.ts.map +1 -0
- package/dist/drain-v4.js +167 -0
- package/dist/drain-v4.js.map +1 -0
- package/dist/endpoint-config.d.ts +36 -0
- package/dist/endpoint-config.d.ts.map +1 -0
- package/dist/endpoint-config.js +96 -0
- package/dist/endpoint-config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/openshell-runtime.d.ts +55 -0
- package/dist/openshell-runtime.d.ts.map +1 -0
- package/dist/openshell-runtime.js +268 -0
- package/dist/openshell-runtime.js.map +1 -0
- package/dist/signing.d.ts +2 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +23 -0
- package/dist/signing.js.map +1 -0
- package/dist/telegram-notify.d.ts +23 -0
- package/dist/telegram-notify.d.ts.map +1 -0
- package/dist/telegram-notify.js +106 -0
- package/dist/telegram-notify.js.map +1 -0
- package/dist/ui/banner.d.ts +7 -0
- package/dist/ui/banner.d.ts.map +1 -0
- package/dist/ui/banner.js +37 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/colors.d.ts +14 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +29 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/format.d.ts +26 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +77 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/spinner.d.ts +8 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +43 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/utils/format.d.ts +10 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +61 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/hash.d.ts +3 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +43 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/time.d.ts +3 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +21 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/wallet-router.d.ts +25 -0
- package/dist/wallet-router.d.ts.map +1 -0
- package/dist/wallet-router.js +153 -0
- package/dist/wallet-router.js.map +1 -0
- package/dist/walletconnect-session.d.ts +12 -0
- package/dist/walletconnect-session.d.ts.map +1 -0
- package/dist/walletconnect-session.js +26 -0
- package/dist/walletconnect-session.js.map +1 -0
- package/dist/walletconnect.d.ts +46 -0
- package/dist/walletconnect.d.ts.map +1 -0
- package/dist/walletconnect.js +267 -0
- package/dist/walletconnect.js.map +1 -0
- package/package.json +38 -0
- package/scripts/authorize-machine-key.ts +43 -0
- package/scripts/drain-wallet.ts +149 -0
- package/scripts/execute-spend-only.ts +81 -0
- package/scripts/register-agent-userop.ts +186 -0
- package/src/abis.ts +187 -0
- package/src/bundler.ts +235 -0
- package/src/client.ts +34 -0
- package/src/coinbase-smart-wallet.ts +51 -0
- package/src/commands/accept.ts +25 -0
- package/src/commands/agent-handshake.ts +67 -0
- package/src/commands/agent.ts +458 -0
- package/src/commands/agreements.ts +324 -0
- package/src/commands/arbitrator.ts +129 -0
- package/src/commands/arena-handshake.ts +217 -0
- package/src/commands/cancel.ts +26 -0
- package/src/commands/channel.ts +208 -0
- package/src/commands/coldstart.ts +156 -0
- package/src/commands/config.ts +35 -0
- package/src/commands/contract-interaction.ts +166 -0
- package/src/commands/daemon.ts +971 -0
- package/src/commands/deliver.ts +116 -0
- package/src/commands/discover.ts +295 -0
- package/src/commands/dispute.ts +373 -0
- package/src/commands/endpoint.ts +619 -0
- package/src/commands/hire.ts +200 -0
- package/src/commands/migrate.ts +175 -0
- package/src/commands/negotiate.ts +270 -0
- package/src/commands/openshell.ts +1053 -0
- package/src/commands/owner.ts +30 -0
- package/src/commands/policy.ts +252 -0
- package/src/commands/relay.ts +272 -0
- package/src/commands/remediate.ts +22 -0
- package/src/commands/reputation.ts +71 -0
- package/src/commands/setup.ts +343 -0
- package/src/commands/trust.ts +15 -0
- package/src/commands/verify.ts +88 -0
- package/src/commands/wallet.ts +2892 -0
- package/src/commands/watchtower.ts +232 -0
- package/src/commands/workroom.ts +889 -0
- package/src/config.ts +153 -0
- package/src/daemon/config.ts +308 -0
- package/src/daemon/hire-listener.ts +226 -0
- package/src/daemon/index.ts +609 -0
- package/src/daemon/job-lifecycle.ts +215 -0
- package/src/daemon/notify.ts +157 -0
- package/src/daemon/token-metering.ts +183 -0
- package/src/daemon/userops.ts +119 -0
- package/src/daemon/wallet-monitor.ts +90 -0
- package/src/drain-v4.ts +159 -0
- package/src/endpoint-config.ts +83 -0
- package/src/index.ts +75 -0
- package/src/openshell-runtime.ts +277 -0
- package/src/signing.ts +28 -0
- package/src/telegram-notify.ts +88 -0
- package/src/ui/banner.ts +41 -0
- package/src/ui/colors.ts +30 -0
- package/src/ui/format.ts +77 -0
- package/src/ui/spinner.ts +46 -0
- package/src/utils/format.ts +48 -0
- package/src/utils/hash.ts +5 -0
- package/src/utils/time.ts +15 -0
- package/src/wallet-router.ts +178 -0
- package/src/walletconnect-session.ts +27 -0
- package/src/walletconnect.ts +294 -0
- package/test/time.test.js +11 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { SignClient } from "@walletconnect/sign-client";
|
|
2
|
+
import { KeyValueStorage } from "@walletconnect/keyvaluestorage";
|
|
3
|
+
import qrcode from "qrcode-terminal";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import { Arc402Config } from "./config";
|
|
7
|
+
import { loadWCSession, saveWCSession, clearWCSession } from "./walletconnect-session";
|
|
8
|
+
import { sendWalletConnectApprovalButton } from "./telegram-notify";
|
|
9
|
+
|
|
10
|
+
// Suppress unhandled rejections from stale WalletConnect sessions (known SDK issue)
|
|
11
|
+
process.on("unhandledRejection", (reason: unknown) => {
|
|
12
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
13
|
+
if (msg.includes("No matching key") || msg.includes("session topic doesn't exist")) return;
|
|
14
|
+
console.error("Unhandled rejection:", msg);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export type TelegramOpts = {
|
|
18
|
+
botToken: string;
|
|
19
|
+
chatId: string;
|
|
20
|
+
threadId?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Infer SignClient instance and session types from the SDK
|
|
24
|
+
type SignClientT = Awaited<ReturnType<typeof SignClient.init>>;
|
|
25
|
+
type WCSession = ReturnType<SignClientT["session"]["get"]>;
|
|
26
|
+
|
|
27
|
+
async function makeSignClient(projectId: string): Promise<SignClientT> {
|
|
28
|
+
const storageDir = path.join(os.homedir(), ".arc402");
|
|
29
|
+
const storagePath = path.join(storageDir, "wc-storage.json");
|
|
30
|
+
return SignClient.init({
|
|
31
|
+
projectId,
|
|
32
|
+
metadata: {
|
|
33
|
+
name: "ARC-402 CLI",
|
|
34
|
+
description: "ARC-402 Protocol CLI",
|
|
35
|
+
url: "https://app.arc402.xyz",
|
|
36
|
+
icons: [],
|
|
37
|
+
},
|
|
38
|
+
storage: new KeyValueStorage({ database: storagePath }),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function walletLinks(encodedUri: string) {
|
|
43
|
+
return {
|
|
44
|
+
"MetaMask": `metamask://wc?uri=${encodedUri}`,
|
|
45
|
+
"Rabby": `rabby://wc?uri=${encodedUri}`,
|
|
46
|
+
"Coinbase Wallet": `cbwallet://wc?uri=${encodedUri}`,
|
|
47
|
+
"Trust Wallet": `trust://wc?uri=${encodedUri}`,
|
|
48
|
+
"Rainbow": `rainbow://wc?uri=${encodedUri}`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Step 1 of the two-step flow: connect the phone wallet (or resume a valid session).
|
|
54
|
+
* On fresh connect, shows deep links + QR and waits for WC approval.
|
|
55
|
+
* Saves session to config after a successful fresh pairing.
|
|
56
|
+
*/
|
|
57
|
+
export async function connectPhoneWallet(
|
|
58
|
+
projectId: string,
|
|
59
|
+
chainId: number,
|
|
60
|
+
config: Arc402Config,
|
|
61
|
+
opts?: { telegramOpts?: TelegramOpts; prompt?: string; hardware?: boolean }
|
|
62
|
+
): Promise<{ client: SignClientT; session: WCSession; account: string }> {
|
|
63
|
+
const client = await makeSignClient(projectId);
|
|
64
|
+
|
|
65
|
+
// Try to restore an existing valid session
|
|
66
|
+
const stored = loadWCSession(config, chainId);
|
|
67
|
+
if (stored) {
|
|
68
|
+
try {
|
|
69
|
+
const session = client.session.get(stored.topic);
|
|
70
|
+
// Verify the session is actually alive on the relay side (MetaMask may have
|
|
71
|
+
// killed it without notifying us). We do a lightweight ping; if it times out
|
|
72
|
+
// or throws the session is stale — fall through to fresh pairing.
|
|
73
|
+
await Promise.race([
|
|
74
|
+
client.ping({ topic: stored.topic }),
|
|
75
|
+
new Promise<never>((_, reject) => setTimeout(() => reject(new Error("ping timeout")), 5000)),
|
|
76
|
+
]);
|
|
77
|
+
return { client, session, account: stored.account };
|
|
78
|
+
} catch {
|
|
79
|
+
// Session stale or timed out — clear locally and do fresh pairing
|
|
80
|
+
clearWCSession(config);
|
|
81
|
+
try {
|
|
82
|
+
await client.disconnect({ topic: stored.topic, reason: { code: 6000, message: "session stale" } });
|
|
83
|
+
} catch { /* best-effort — may already be gone */ }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Fresh pairing flow
|
|
88
|
+
const { uri, approval } = await client.connect({
|
|
89
|
+
requiredNamespaces: {
|
|
90
|
+
eip155: {
|
|
91
|
+
methods: ["eth_sendTransaction", "personal_sign", "wallet_switchEthereumChain"],
|
|
92
|
+
chains: [`eip155:${chainId}`],
|
|
93
|
+
events: ["accountsChanged"],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!uri) throw new Error("Failed to create WalletConnect session");
|
|
99
|
+
|
|
100
|
+
const encodedUri = encodeURIComponent(uri);
|
|
101
|
+
const displayPrompt = opts?.prompt ?? "Connect your phone wallet";
|
|
102
|
+
|
|
103
|
+
if (opts?.hardware) {
|
|
104
|
+
console.log("\n─────────────────────────────────────────────────────");
|
|
105
|
+
console.log("Paste this URI into Ledger Live, Trezor Suite, or any WalletConnect-compatible signer:\n");
|
|
106
|
+
console.log(uri);
|
|
107
|
+
console.log("\n─────────────────────────────────────────────────────");
|
|
108
|
+
console.log("Waiting for connection...");
|
|
109
|
+
} else {
|
|
110
|
+
const links = walletLinks(encodedUri);
|
|
111
|
+
console.log(`\n${displayPrompt}`);
|
|
112
|
+
console.log("─────────────────────────────────────");
|
|
113
|
+
console.log("Tap the link for your wallet app (opens directly):\n");
|
|
114
|
+
for (const [name, link] of Object.entries(links)) {
|
|
115
|
+
console.log(`${name}:\n${link}\n`);
|
|
116
|
+
}
|
|
117
|
+
console.log("Or scan QR:");
|
|
118
|
+
qrcode.generate(uri, { small: true });
|
|
119
|
+
console.log("\nWaiting for approval...");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const telegramOpts = opts?.hardware ? undefined : opts?.telegramOpts;
|
|
123
|
+
if (telegramOpts) {
|
|
124
|
+
await sendWalletConnectApprovalButton({
|
|
125
|
+
botToken: telegramOpts.botToken,
|
|
126
|
+
chatId: telegramOpts.chatId,
|
|
127
|
+
threadId: telegramOpts.threadId,
|
|
128
|
+
prompt: displayPrompt,
|
|
129
|
+
walletLinks: [
|
|
130
|
+
{ label: "🦊 MetaMask", url: `https://metamask.app.link/wc?uri=${encodedUri}` },
|
|
131
|
+
{ label: "🌈 Rainbow", url: `https://rnbwapp.com/wc?uri=${encodedUri}` },
|
|
132
|
+
{ label: "🔵 Trust Wallet", url: `https://link.trustwallet.com/wc?uri=${encodedUri}` },
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
console.log("Approval request sent to Telegram ✓");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const session = await approval();
|
|
139
|
+
const account = session.namespaces.eip155.accounts[0].split(":")[2];
|
|
140
|
+
|
|
141
|
+
// Ensure wallet is on the correct chain before sending any tx
|
|
142
|
+
const hexChainId = `0x${chainId.toString(16)}`;
|
|
143
|
+
const networkName = chainId === 8453 ? "Base" : chainId === 84532 ? "Base Sepolia" : `chain ${chainId}`;
|
|
144
|
+
|
|
145
|
+
// First try adding the chain (MetaMask ignores if already added)
|
|
146
|
+
if (chainId === 8453) {
|
|
147
|
+
try {
|
|
148
|
+
await client.request({
|
|
149
|
+
topic: session.topic,
|
|
150
|
+
chainId: `eip155:1`,
|
|
151
|
+
request: {
|
|
152
|
+
method: "wallet_addEthereumChain",
|
|
153
|
+
params: [{ chainId: hexChainId, chainName: "Base", nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, rpcUrls: ["https://mainnet.base.org"], blockExplorerUrls: ["https://basescan.org"] }],
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
} catch { /* already added or unsupported */ }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Then switch — retry up to 3 times
|
|
160
|
+
let chainSwitched = false;
|
|
161
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
162
|
+
try {
|
|
163
|
+
await client.request({
|
|
164
|
+
topic: session.topic,
|
|
165
|
+
chainId: `eip155:${chainId}`,
|
|
166
|
+
request: {
|
|
167
|
+
method: "wallet_switchEthereumChain",
|
|
168
|
+
params: [{ chainId: hexChainId }],
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
chainSwitched = true;
|
|
172
|
+
break;
|
|
173
|
+
} catch {
|
|
174
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!chainSwitched) {
|
|
178
|
+
console.log(`\n⚠ Could not auto-switch to ${networkName}. IMPORTANT: Switch to ${networkName} in your wallet NOW before approving the next transaction.`);
|
|
179
|
+
console.log(` Otherwise the transaction will go to Ethereum mainnet and fail.`);
|
|
180
|
+
// Give user 5 seconds to read the warning and switch
|
|
181
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Persist session (WC sessions last 7 days by default)
|
|
185
|
+
const expiry = Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60;
|
|
186
|
+
saveWCSession(config, { topic: session.topic, expiry, account, chainId });
|
|
187
|
+
|
|
188
|
+
return { client, session, account };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Step 2 of the two-step flow: send a transaction with an established session.
|
|
193
|
+
* Returns the transaction hash.
|
|
194
|
+
*/
|
|
195
|
+
export async function sendTransactionWithSession(
|
|
196
|
+
client: SignClientT,
|
|
197
|
+
session: WCSession,
|
|
198
|
+
account: string,
|
|
199
|
+
chainId: number,
|
|
200
|
+
tx: { to: string; data: string; value?: string }
|
|
201
|
+
): Promise<string> {
|
|
202
|
+
return client.request<string>({
|
|
203
|
+
topic: session.topic,
|
|
204
|
+
chainId: `eip155:${chainId}`,
|
|
205
|
+
request: {
|
|
206
|
+
method: "eth_sendTransaction",
|
|
207
|
+
params: [{ from: account, to: tx.to, data: tx.data, value: tx.value ?? "0x0" }],
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Convenience wrapper: connect + send in one call.
|
|
214
|
+
* Used by commands that haven't been split into two steps (e.g. policy set-limit).
|
|
215
|
+
*/
|
|
216
|
+
export async function requestPhoneWalletSignature(
|
|
217
|
+
projectId: string,
|
|
218
|
+
chainId: number,
|
|
219
|
+
buildTx: (account: string) => { to: string; data: string; value?: string },
|
|
220
|
+
prompt: string,
|
|
221
|
+
telegramOpts?: TelegramOpts,
|
|
222
|
+
config?: Arc402Config
|
|
223
|
+
): Promise<{ txHash: string; account: string }> {
|
|
224
|
+
if (!config) {
|
|
225
|
+
// Legacy path: no session persistence, disconnect after use
|
|
226
|
+
const client = await makeSignClient(projectId);
|
|
227
|
+
const { uri, approval } = await client.connect({
|
|
228
|
+
requiredNamespaces: {
|
|
229
|
+
eip155: {
|
|
230
|
+
methods: ["eth_sendTransaction", "personal_sign", "wallet_switchEthereumChain"],
|
|
231
|
+
chains: [`eip155:${chainId}`],
|
|
232
|
+
events: ["accountsChanged"],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
if (!uri) throw new Error("Failed to create WalletConnect session");
|
|
237
|
+
const encodedUri = encodeURIComponent(uri);
|
|
238
|
+
const links = walletLinks(encodedUri);
|
|
239
|
+
console.log(`\n${prompt}`);
|
|
240
|
+
console.log("─────────────────────────────────────");
|
|
241
|
+
console.log("Tap the link for your wallet app (opens directly):\n");
|
|
242
|
+
for (const [name, link] of Object.entries(links)) console.log(`${name}:\n${link}\n`);
|
|
243
|
+
console.log("Or scan QR:");
|
|
244
|
+
qrcode.generate(uri, { small: true });
|
|
245
|
+
console.log("\nWaiting for approval...");
|
|
246
|
+
if (telegramOpts) {
|
|
247
|
+
await sendWalletConnectApprovalButton({
|
|
248
|
+
botToken: telegramOpts.botToken,
|
|
249
|
+
chatId: telegramOpts.chatId,
|
|
250
|
+
threadId: telegramOpts.threadId,
|
|
251
|
+
prompt,
|
|
252
|
+
walletLinks: [
|
|
253
|
+
{ label: "🦊 MetaMask", url: `https://metamask.app.link/wc?uri=${encodedUri}` },
|
|
254
|
+
{ label: "🌈 Rainbow", url: `https://rnbwapp.com/wc?uri=${encodedUri}` },
|
|
255
|
+
{ label: "🔵 Trust Wallet", url: `https://link.trustwallet.com/wc?uri=${encodedUri}` },
|
|
256
|
+
],
|
|
257
|
+
});
|
|
258
|
+
console.log("Approval request sent to Telegram ✓");
|
|
259
|
+
}
|
|
260
|
+
const session = await approval();
|
|
261
|
+
const account = session.namespaces.eip155.accounts[0].split(":")[2];
|
|
262
|
+
// Switch chain — retry up to 3 times
|
|
263
|
+
const hexId = `0x${chainId.toString(16)}`;
|
|
264
|
+
let switched = false;
|
|
265
|
+
for (let i = 0; i < 3; i++) {
|
|
266
|
+
try {
|
|
267
|
+
await client.request({
|
|
268
|
+
topic: session.topic,
|
|
269
|
+
chainId: `eip155:${chainId}`,
|
|
270
|
+
request: { method: "wallet_switchEthereumChain", params: [{ chainId: hexId }] },
|
|
271
|
+
});
|
|
272
|
+
switched = true;
|
|
273
|
+
break;
|
|
274
|
+
} catch { await new Promise(r => setTimeout(r, 1000)); }
|
|
275
|
+
}
|
|
276
|
+
if (!switched) {
|
|
277
|
+
const net = chainId === 8453 ? "Base" : chainId === 84532 ? "Base Sepolia" : `chain ${chainId}`;
|
|
278
|
+
console.log(`\n⚠ Could not auto-switch chain. Please switch to ${net} manually in your wallet before approving.`);
|
|
279
|
+
}
|
|
280
|
+
const tx = buildTx(account);
|
|
281
|
+
const txHash = await client.request<string>({
|
|
282
|
+
topic: session.topic,
|
|
283
|
+
chainId: `eip155:${chainId}`,
|
|
284
|
+
request: { method: "eth_sendTransaction", params: [{ from: account, to: tx.to, data: tx.data, value: tx.value ?? "0x0" }] },
|
|
285
|
+
});
|
|
286
|
+
await client.disconnect({ topic: session.topic, reason: { code: 0, message: "done" } });
|
|
287
|
+
return { txHash, account };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const { client, session, account } = await connectPhoneWallet(projectId, chainId, config, { telegramOpts, prompt });
|
|
291
|
+
const tx = buildTx(account);
|
|
292
|
+
const txHash = await sendTransactionWithSession(client, session, account, chainId, tx);
|
|
293
|
+
return { txHash, account };
|
|
294
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const { parseDuration } = require('../dist/utils/time');
|
|
4
|
+
|
|
5
|
+
test('parseDuration supports hours and days', () => {
|
|
6
|
+
const now = Math.floor(Date.now() / 1000);
|
|
7
|
+
const twoHours = parseDuration('2h');
|
|
8
|
+
const sevenDays = parseDuration('7d');
|
|
9
|
+
assert.ok(twoHours > now + 7100 && twoHours < now + 7300);
|
|
10
|
+
assert.ok(sevenDays > now + 604000 && sevenDays < now + 605000);
|
|
11
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|