@nexstone/rift-cli 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/LICENSE +201 -0
  2. package/bin/run.js +22 -0
  3. package/dist/commands/algo.d.ts +32 -0
  4. package/dist/commands/algo.js +719 -0
  5. package/dist/commands/audit.d.ts +13 -0
  6. package/dist/commands/audit.js +37 -0
  7. package/dist/commands/auth-status.d.ts +14 -0
  8. package/dist/commands/auth-status.js +118 -0
  9. package/dist/commands/auth.d.ts +14 -0
  10. package/dist/commands/auth.js +275 -0
  11. package/dist/commands/backtest.d.ts +26 -0
  12. package/dist/commands/backtest.js +283 -0
  13. package/dist/commands/collect/start.d.ts +11 -0
  14. package/dist/commands/collect/start.js +78 -0
  15. package/dist/commands/collect/status.d.ts +6 -0
  16. package/dist/commands/collect/status.js +60 -0
  17. package/dist/commands/compare.d.ts +16 -0
  18. package/dist/commands/compare.js +130 -0
  19. package/dist/commands/config.d.ts +16 -0
  20. package/dist/commands/config.js +143 -0
  21. package/dist/commands/cost.d.ts +20 -0
  22. package/dist/commands/cost.js +104 -0
  23. package/dist/commands/cross-asset.d.ts +14 -0
  24. package/dist/commands/cross-asset.js +39 -0
  25. package/dist/commands/data/fetch.d.ts +15 -0
  26. package/dist/commands/data/fetch.js +82 -0
  27. package/dist/commands/data/list.d.ts +6 -0
  28. package/dist/commands/data/list.js +28 -0
  29. package/dist/commands/data-inventory.d.ts +9 -0
  30. package/dist/commands/data-inventory.js +24 -0
  31. package/dist/commands/deposit.d.ts +10 -0
  32. package/dist/commands/deposit.js +222 -0
  33. package/dist/commands/doctor.d.ts +6 -0
  34. package/dist/commands/doctor.js +87 -0
  35. package/dist/commands/funding-browser.d.ts +12 -0
  36. package/dist/commands/funding-browser.js +33 -0
  37. package/dist/commands/guide.d.ts +6 -0
  38. package/dist/commands/guide.js +15 -0
  39. package/dist/commands/home.d.ts +23 -0
  40. package/dist/commands/home.js +210 -0
  41. package/dist/commands/init.d.ts +7 -0
  42. package/dist/commands/init.js +122 -0
  43. package/dist/commands/install.d.ts +9 -0
  44. package/dist/commands/install.js +89 -0
  45. package/dist/commands/interactive.d.ts +17 -0
  46. package/dist/commands/interactive.js +179 -0
  47. package/dist/commands/lessons.d.ts +12 -0
  48. package/dist/commands/lessons.js +33 -0
  49. package/dist/commands/montecarlo.d.ts +19 -0
  50. package/dist/commands/montecarlo.js +168 -0
  51. package/dist/commands/more.d.ts +11 -0
  52. package/dist/commands/more.js +227 -0
  53. package/dist/commands/new.d.ts +14 -0
  54. package/dist/commands/new.js +306 -0
  55. package/dist/commands/pairs.d.ts +22 -0
  56. package/dist/commands/pairs.js +147 -0
  57. package/dist/commands/perp/close.d.ts +12 -0
  58. package/dist/commands/perp/close.js +57 -0
  59. package/dist/commands/perp/long.d.ts +14 -0
  60. package/dist/commands/perp/long.js +38 -0
  61. package/dist/commands/perp/short.d.ts +14 -0
  62. package/dist/commands/perp/short.js +27 -0
  63. package/dist/commands/perp/status.d.ts +9 -0
  64. package/dist/commands/perp/status.js +26 -0
  65. package/dist/commands/portfolio/alerts.d.ts +6 -0
  66. package/dist/commands/portfolio/alerts.js +47 -0
  67. package/dist/commands/portfolio/backtest.d.ts +12 -0
  68. package/dist/commands/portfolio/backtest.js +178 -0
  69. package/dist/commands/portfolio/create.d.ts +7 -0
  70. package/dist/commands/portfolio/create.js +195 -0
  71. package/dist/commands/portfolio/start.d.ts +9 -0
  72. package/dist/commands/portfolio/start.js +64 -0
  73. package/dist/commands/portfolio/status.d.ts +6 -0
  74. package/dist/commands/portfolio/status.js +128 -0
  75. package/dist/commands/portfolio/stop.d.ts +6 -0
  76. package/dist/commands/portfolio/stop.js +81 -0
  77. package/dist/commands/portfolio-backtest.d.ts +13 -0
  78. package/dist/commands/portfolio-backtest.js +37 -0
  79. package/dist/commands/portfolio-matrix.d.ts +12 -0
  80. package/dist/commands/portfolio-matrix.js +30 -0
  81. package/dist/commands/quick-test.d.ts +17 -0
  82. package/dist/commands/quick-test.js +45 -0
  83. package/dist/commands/research.d.ts +57 -0
  84. package/dist/commands/research.js +1976 -0
  85. package/dist/commands/scout.d.ts +14 -0
  86. package/dist/commands/scout.js +184 -0
  87. package/dist/commands/serve.d.ts +9 -0
  88. package/dist/commands/serve.js +1176 -0
  89. package/dist/commands/setup/proxy.d.ts +10 -0
  90. package/dist/commands/setup/proxy.js +267 -0
  91. package/dist/commands/spot/buy.d.ts +14 -0
  92. package/dist/commands/spot/buy.js +38 -0
  93. package/dist/commands/spot/sell.d.ts +14 -0
  94. package/dist/commands/spot/sell.js +39 -0
  95. package/dist/commands/strategies/list.d.ts +6 -0
  96. package/dist/commands/strategies/list.js +34 -0
  97. package/dist/commands/sweep.d.ts +19 -0
  98. package/dist/commands/sweep.js +137 -0
  99. package/dist/commands/sync.d.ts +17 -0
  100. package/dist/commands/sync.js +54 -0
  101. package/dist/commands/test-trade.d.ts +6 -0
  102. package/dist/commands/test-trade.js +97 -0
  103. package/dist/commands/trade.d.ts +26 -0
  104. package/dist/commands/trade.js +274 -0
  105. package/dist/commands/transfer.d.ts +13 -0
  106. package/dist/commands/transfer.js +65 -0
  107. package/dist/commands/verify.d.ts +16 -0
  108. package/dist/commands/verify.js +38 -0
  109. package/dist/commands/walkforward.d.ts +20 -0
  110. package/dist/commands/walkforward.js +191 -0
  111. package/dist/commands/withdraw.d.ts +12 -0
  112. package/dist/commands/withdraw.js +55 -0
  113. package/dist/commands/workbench-create.d.ts +13 -0
  114. package/dist/commands/workbench-create.js +39 -0
  115. package/dist/lib/account-mode.d.ts +44 -0
  116. package/dist/lib/account-mode.js +96 -0
  117. package/dist/lib/analyzer.d.ts +4 -0
  118. package/dist/lib/analyzer.js +62 -0
  119. package/dist/lib/base-command.d.ts +35 -0
  120. package/dist/lib/base-command.js +49 -0
  121. package/dist/lib/credentials.d.ts +46 -0
  122. package/dist/lib/credentials.js +137 -0
  123. package/dist/lib/engine-passthrough.d.ts +28 -0
  124. package/dist/lib/engine-passthrough.js +60 -0
  125. package/dist/lib/fees.d.ts +52 -0
  126. package/dist/lib/fees.js +97 -0
  127. package/dist/lib/python-bridge.d.ts +24 -0
  128. package/dist/lib/python-bridge.js +182 -0
  129. package/dist/lib/setup-status.d.ts +32 -0
  130. package/dist/lib/setup-status.js +121 -0
  131. package/dist/lib/status-footer.d.ts +35 -0
  132. package/dist/lib/status-footer.js +101 -0
  133. package/dist/lib/tui.d.ts +130 -0
  134. package/dist/lib/tui.js +300 -0
  135. package/dist/lib/walletconnect.d.ts +70 -0
  136. package/dist/lib/walletconnect.js +407 -0
  137. package/package.json +49 -0
@@ -0,0 +1,13 @@
1
+ import { GatedCommand } from '../lib/base-command.js';
2
+ export default class Audit extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ export: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ last: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ strategy: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,37 @@
1
+ import { Flags } from '@oclif/core';
2
+ import * as path from 'node:path';
3
+ import { GatedCommand } from '../lib/base-command.js';
4
+ import { passthroughToEngine } from '../lib/engine-passthrough.js';
5
+ export default class Audit extends GatedCommand {
6
+ static description = 'Export compliance-grade audit trail of all live trades';
7
+ static examples = [
8
+ '$ rift audit',
9
+ '$ rift audit --export json --last 90',
10
+ '$ rift audit --strategy trend_follow --output ./audit.csv',
11
+ ];
12
+ static flags = {
13
+ export: Flags.string({ description: 'Export format: csv or json', default: 'csv' }),
14
+ last: Flags.string({ description: 'Days of history to include', default: '30' }),
15
+ strategy: Flags.string({ description: 'Filter by strategy name', default: '' }),
16
+ output: Flags.string({ description: 'Custom output path', default: '' }),
17
+ json: Flags.boolean({ description: 'Emit raw JSON only', default: false }),
18
+ };
19
+ async run() {
20
+ const { flags } = await this.parse(Audit);
21
+ const args = ['--export', flags.export, '--last', flags.last];
22
+ if (flags.strategy)
23
+ args.push('--strategy', flags.strategy);
24
+ // Resolve --output to an absolute path so the file lands where the user
25
+ // expects (their cwd), not in the engine subprocess's cwd (engine/).
26
+ if (flags.output)
27
+ args.push('--output', path.resolve(flags.output));
28
+ await passthroughToEngine({
29
+ command: 'audit',
30
+ args,
31
+ log: (m) => this.log(m),
32
+ error: (m) => this.error(m),
33
+ exit: (c) => this.exit(c),
34
+ jsonOnly: flags.json,
35
+ });
36
+ }
37
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `rift auth-status` — show current API wallet registration + recent token activity.
3
+ *
4
+ * Thin TS wrapper around the Python `agent-status` command (which inspects
5
+ * ~/.rift/credentials and ~/.rift/tokens/ via rift_trade modules and emits
6
+ * structured NDJSON). Renders a human-readable summary; suppresses the
7
+ * persistent footer because this command IS the status display.
8
+ */
9
+ import { GatedCommand } from '../lib/base-command.js';
10
+ export default class AuthStatus extends GatedCommand {
11
+ static description: string;
12
+ static examples: string[];
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * `rift auth-status` — show current API wallet registration + recent token activity.
3
+ *
4
+ * Thin TS wrapper around the Python `agent-status` command (which inspects
5
+ * ~/.rift/credentials and ~/.rift/tokens/ via rift_trade modules and emits
6
+ * structured NDJSON). Renders a human-readable summary; suppresses the
7
+ * persistent footer because this command IS the status display.
8
+ */
9
+ import { GatedCommand } from '../lib/base-command.js';
10
+ import { runEngine } from '../lib/python-bridge.js';
11
+ // Same ANSI helpers used elsewhere in the codebase
12
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
13
+ const amber = (s) => `\x1b[33m${s}\x1b[0m`;
14
+ const gray = (s) => `\x1b[2m\x1b[37m${s}\x1b[0m`;
15
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
16
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
17
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
18
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
19
+ function shortAddr(addr) {
20
+ if (!addr)
21
+ return '—';
22
+ if (addr.length < 14)
23
+ return addr;
24
+ return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
25
+ }
26
+ export default class AuthStatus extends GatedCommand {
27
+ static description = 'Show RIFT auth state — API wallet + recent authorization tokens.';
28
+ static examples = [
29
+ '$ rift auth-status',
30
+ ];
31
+ async run() {
32
+ let result = null;
33
+ try {
34
+ await runEngine('agent-status', [], (msg) => {
35
+ if (msg.type === 'result' && msg.command === 'agent-status') {
36
+ result = msg;
37
+ }
38
+ });
39
+ }
40
+ catch (err) {
41
+ this.error(`Failed to read agent status: ${err}`);
42
+ }
43
+ if (!result) {
44
+ this.error('No response from engine');
45
+ }
46
+ // TypeScript inference doesn't narrow through async callback assignment.
47
+ const r = result;
48
+ this.log('');
49
+ this.log(` ${bold('RIFT auth status')}`);
50
+ this.log(` ${dim('─'.repeat(50))}`);
51
+ this.log('');
52
+ if (!r.registered) {
53
+ this.log(` ${gray('○')} ${bold('No API wallet registered.')}`);
54
+ this.log('');
55
+ this.log(` ${dim('To enable trading:')}`);
56
+ this.log(` ${cyan('rift agent-pair --local-main-key <0x...>')}`);
57
+ this.log('');
58
+ this.log(` ${dim('Or stay in research-only mode (no wallet, no live trading):')}`);
59
+ this.log(` ${cyan('rift backtest trend_follow --pair BTC --tf 4h')}`);
60
+ this.log('');
61
+ return;
62
+ }
63
+ const agent = r.agent;
64
+ const tokens = r.tokens;
65
+ const networkLabel = green('mainnet');
66
+ this.log(` ${green('●')} ${bold('API wallet registered')} on ${networkLabel}`);
67
+ this.log('');
68
+ this.log(` ${dim('Address:')} ${cyan(agent.address)}`);
69
+ this.log(` ${dim('Name (HL):')} ${agent.name}`);
70
+ this.log(` ${dim('Registered:')} ${agent.registered_at}`);
71
+ if (agent.registered_tx) {
72
+ this.log(` ${dim('Tx hash:')} ${dim(agent.registered_tx)}`);
73
+ }
74
+ // Pull live HL state — mode + tradeable collateral — using the shared
75
+ // helper that mirrors Python's read_collateral. Best-effort: if the
76
+ // info endpoint is unreachable (e.g. geo-restricted) we skip silently.
77
+ try {
78
+ const { readCollateral, hlBaseUrl } = await import('../lib/account-mode.js');
79
+ // Main wallet address (the issuer) is what HL queries by. For an
80
+ // API-wallet-only context we'd need the main wallet; here we use
81
+ // the most-recent token's issuer if available, otherwise skip.
82
+ const mainAddr = (r.tokens?.recent?.[0]?.issuer) || null;
83
+ if (mainAddr) {
84
+ const c = await readCollateral(hlBaseUrl(), mainAddr);
85
+ const modeColor = c.mode === 'unknown' ? amber : c.mode === 'standard' ? green : cyan;
86
+ this.log(` ${dim('Account mode:')} ${modeColor(c.mode)}`);
87
+ if (c.mode === 'standard') {
88
+ this.log(` ${dim('Tradeable:')} $${c.total.toFixed(2)} USDC`);
89
+ }
90
+ else {
91
+ this.log(` ${dim('Tradeable:')} $${c.total.toFixed(2)} USDC ${dim(`(perp $${c.perpAvailable.toFixed(2)} + spot $${c.spotUsdc.toFixed(2)})`)}`);
92
+ }
93
+ }
94
+ }
95
+ catch {
96
+ // Best-effort; don't fail the whole status command if HL is unreachable
97
+ }
98
+ this.log('');
99
+ this.log(` ${bold(`Auth tokens (${tokens.active} active / ${tokens.total} total)`)}`);
100
+ if (tokens.recent.length === 0) {
101
+ this.log(` ${gray('No tokens issued. Issue one: rift token-issue --coins ETH --max-notional 500 --max-daily 2000')}`);
102
+ }
103
+ else {
104
+ this.log(` ${dim('id')} ${dim('coins')} ${dim('max/trade')} ${dim('max/day')} ${dim('expires')}`);
105
+ for (const t of tokens.recent) {
106
+ const status = t.revoked
107
+ ? red('revoked')
108
+ : t.valid
109
+ ? green('valid')
110
+ : amber('expired');
111
+ const coins = Array.isArray(t.scope_coins) ? t.scope_coins.join(',') : t.scope_coins;
112
+ const expires = t.expires_at ? t.expires_at.slice(0, 19).replace('T', ' ') : dim('never');
113
+ this.log(` ${t.id.slice(0, 8)}… ${coins.padEnd(11)} $${t.scope_max_notional.padEnd(10)} $${t.scope_max_daily.padEnd(9)} ${expires} ${status}`);
114
+ }
115
+ }
116
+ this.log('');
117
+ }
118
+ }
@@ -0,0 +1,14 @@
1
+ import { GatedCommand } from '../lib/base-command.js';
2
+ export default class Auth extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ action: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {};
9
+ run(): Promise<void>;
10
+ private showStatus;
11
+ private resetCredentials;
12
+ private walletConnectSetup;
13
+ private saveAndFinish;
14
+ }
@@ -0,0 +1,275 @@
1
+ import { Args } from '@oclif/core';
2
+ import { GatedCommand } from '../lib/base-command.js';
3
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
4
+ import { createInterface } from 'node:readline';
5
+ import { saveCredentials, loadCredentials, hasFullSetup, maskKey, getAccountAddress } from '../lib/credentials.js';
6
+ import { connectWallet, requestAgentApproval, requestBuilderFeeApproval, postApprovalToHyperliquid, disconnectWallet, showQRCode, } from '../lib/walletconnect.js';
7
+ import { BUILDER_FEE_DISPLAY, recordOnChainApproval, hasApprovedFees, approveFees } from '../lib/fees.js';
8
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
9
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
10
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
11
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
12
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
13
+ function ask(question) {
14
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
15
+ return new Promise(resolve => {
16
+ rl.question(question, answer => {
17
+ rl.close();
18
+ resolve(answer.trim());
19
+ });
20
+ });
21
+ }
22
+ export default class Auth extends GatedCommand {
23
+ static description = 'Set up wallet credentials for Hyperliquid live trading';
24
+ static examples = [
25
+ '$ rift auth setup',
26
+ '$ rift auth status',
27
+ '$ rift auth reset',
28
+ ];
29
+ static args = {
30
+ action: Args.string({
31
+ description: 'Action: setup, status, or reset',
32
+ required: false,
33
+ }),
34
+ };
35
+ static flags = {};
36
+ async run() {
37
+ const { args } = await this.parse(Auth);
38
+ switch (args.action) {
39
+ case 'setup':
40
+ return this.walletConnectSetup();
41
+ case 'status':
42
+ return this.showStatus();
43
+ case 'reset':
44
+ return this.resetCredentials();
45
+ default:
46
+ // No action → show status or guide to setup
47
+ if (hasFullSetup()) {
48
+ return this.showStatus();
49
+ }
50
+ return this.walletConnectSetup();
51
+ }
52
+ }
53
+ showStatus() {
54
+ const creds = loadCredentials();
55
+ if (!creds) {
56
+ this.log('');
57
+ this.log(` No credentials configured.`);
58
+ this.log(` Run: ${cyan('rift auth setup')}`);
59
+ this.log('');
60
+ return;
61
+ }
62
+ this.log('');
63
+ this.log(` ${bold('RIFT Account Status')}`);
64
+ this.log(` ${dim('─'.repeat(45))}`);
65
+ const mainWallet = getAccountAddress(creds);
66
+ const agentOk = creds.agent_approved !== false; // absent = trust file
67
+ const builderOk = creds.builder_fee_approved === true;
68
+ this.log(` Main wallet: ${mainWallet}`);
69
+ this.log(` API wallet: ${creds.address || maskKey(creds.private_key)}`);
70
+ this.log(` Network: ${creds.network}`);
71
+ this.log(` Agent: ${agentOk ? green('✔ approved') : red('✘ not approved')}`);
72
+ this.log(` Builder fee: ${builderOk ? green('✔ approved') : red('✘ not approved')}`);
73
+ this.log(` ${dim('─'.repeat(45))}`);
74
+ if (!agentOk || !builderOk) {
75
+ this.log(` Run ${cyan('rift auth setup')} to complete setup.`);
76
+ }
77
+ else {
78
+ this.log(` ${green('Ready for live trading.')} Run: ${cyan('rift live')}`);
79
+ }
80
+ this.log('');
81
+ }
82
+ async resetCredentials() {
83
+ const confirm = await ask(`\n ${red('This will remove all credentials. Continue?')} ${dim('(yes/no)')}: `);
84
+ if (confirm.toLowerCase() !== 'yes' && confirm.toLowerCase() !== 'y') {
85
+ this.log(dim('\n Cancelled.\n'));
86
+ return;
87
+ }
88
+ const credPath = (process.env.HOME || '~') + '/.rift/credentials.json';
89
+ const fs = await import('node:fs');
90
+ if (fs.existsSync(credPath)) {
91
+ fs.unlinkSync(credPath);
92
+ }
93
+ this.log(`\n ${green('✔')} Credentials removed. Run ${cyan('rift auth setup')} to set up again.\n`);
94
+ }
95
+ async walletConnectSetup() {
96
+ const isMainnet = true;
97
+ this.log('');
98
+ this.log(` ${bold('╔═══════════════════════════════════════════╗')}`);
99
+ this.log(` ${bold('║ RIFT Account Setup ║')}`);
100
+ this.log(` ${bold('╚═══════════════════════════════════════════╝')}`);
101
+ this.log('');
102
+ // Fee consent (if not already given)
103
+ if (!hasApprovedFees()) {
104
+ this.log(` RIFT is ${bold('free')} for research, backtesting, and simulation.`);
105
+ this.log(` Live trading has a ${bold(BUILDER_FEE_DISPLAY)} builder fee per trade,`);
106
+ this.log(` collected on-chain by Hyperliquid to support RIFT development.`);
107
+ this.log('');
108
+ this.log(dim(' Example: $10,000 trade → $10 fee. Code is fully open-source.'));
109
+ this.log('');
110
+ const feeAnswer = await ask(` ${cyan('Agree to continue?')} ${dim('(yes/no)')}: `);
111
+ if (feeAnswer.toLowerCase() !== 'yes' && feeAnswer.toLowerCase() !== 'y') {
112
+ this.log(`\n ${dim('Setup cancelled.')}\n`);
113
+ return;
114
+ }
115
+ approveFees();
116
+ this.log(` ${green('✔')} Fee acknowledged`);
117
+ this.log('');
118
+ }
119
+ this.log(dim(' Connect your Hyperliquid wallet to set up live trading.'));
120
+ this.log(dim(' You\'ll approve two actions (one-time, from your phone).'));
121
+ this.log('');
122
+ // Step 1: Connect wallet via WalletConnect QR
123
+ this.log(` ${bold('Step 1:')} Connect your wallet`);
124
+ this.log('');
125
+ let session;
126
+ try {
127
+ session = await connectWallet((uri) => {
128
+ this.log(` Scan this QR code with your wallet app`);
129
+ this.log(` ${dim('(MetaMask, Rabby, Rainbow, Trust, etc.)')}`);
130
+ this.log('');
131
+ showQRCode(uri);
132
+ this.log('');
133
+ this.log(` ${dim('Or paste this URI in your wallet:')}`);
134
+ this.log(` ${dim(uri.slice(0, 60) + '...')}`);
135
+ this.log('');
136
+ this.log(dim(' Waiting for connection...'));
137
+ }, (msg) => this.log(` ${dim(msg)}`));
138
+ }
139
+ catch (error) {
140
+ this.log(` ${red('✘')} Connection failed: ${error?.message || 'Unknown error'}`);
141
+ this.log('');
142
+ this.log(` ${dim('Make sure your wallet app supports WalletConnect (Rabby, MetaMask Mobile, etc.)')}`);
143
+ this.log(` ${dim('Or paste your API wallet private key directly:')} ${cyan('rift auth setup --key 0x...')}`);
144
+ this.log('');
145
+ return;
146
+ }
147
+ this.log(` ${green('✔')} Connected: ${bold(session.account)}`);
148
+ this.log('');
149
+ // Step 2: Check Hyperliquid account
150
+ this.log(` ${bold('Step 2:')} Checking Hyperliquid account...`);
151
+ // Mode-aware collateral read via shared helper (mirrors Python's
152
+ // rift_data.account_mode.read_collateral so Python and TS surfaces
153
+ // agree on the same wallet's numbers).
154
+ let tradeable = 0;
155
+ let perpEquity = 0;
156
+ let spotUsdc = 0;
157
+ let mode = 'unknown';
158
+ try {
159
+ const { readCollateral, hlBaseUrl } = await import('../lib/account-mode.js');
160
+ const c = await readCollateral(hlBaseUrl(isMainnet), session.account);
161
+ tradeable = c.total;
162
+ perpEquity = c.perpAccountValue;
163
+ spotUsdc = c.spotUsdc;
164
+ mode = c.mode;
165
+ }
166
+ catch {
167
+ // If we can't check, continue anyway — user might have geo-restrictions on info endpoint
168
+ }
169
+ if (tradeable > 0) {
170
+ this.log(` ${green('✔')} Hyperliquid account found (${mode} mode)`);
171
+ this.log(` ${green('✔')} Tradeable: $${tradeable.toLocaleString()} USDC${mode === 'standard' ? '' : ` ${dim(`(perp $${perpEquity.toFixed(2)} + spot $${spotUsdc.toFixed(2)})`)}`}`);
172
+ }
173
+ else {
174
+ this.log(` ${dim('!')} Could not verify balance (may need funds for live trading)`);
175
+ }
176
+ this.log('');
177
+ // Step 3: Generate API wallet and approve agent
178
+ this.log(` ${bold('Step 3:')} Authorize RIFT to trade`);
179
+ this.log('');
180
+ this.log(dim(' RIFT will create a secure API wallet that can place'));
181
+ this.log(dim(' trades on your behalf but CANNOT withdraw your funds.'));
182
+ this.log('');
183
+ // Generate a fresh API wallet keypair
184
+ const apiKey = generatePrivateKey();
185
+ const apiAccount = privateKeyToAccount(apiKey);
186
+ const agentAddress = apiAccount.address;
187
+ this.log(dim(' Please approve in your wallet app...'));
188
+ const agentResult = await requestAgentApproval(session, agentAddress, isMainnet);
189
+ if (!agentResult.success) {
190
+ this.log(` ${red('✘')} Agent approval failed: ${agentResult.error}`);
191
+ await disconnectWallet(session);
192
+ return;
193
+ }
194
+ // Post the signed approval to Hyperliquid (using the SAME nonce that was signed)
195
+ try {
196
+ await postApprovalToHyperliquid(agentResult.action, agentResult.signature, agentResult.nonce, isMainnet);
197
+ this.log(` ${green('✔')} API wallet authorized`);
198
+ }
199
+ catch (error) {
200
+ this.log(` ${red('✘')} Failed to submit agent approval: ${error?.message}`);
201
+ await disconnectWallet(session);
202
+ return;
203
+ }
204
+ this.log('');
205
+ // Step 4: Approve builder fee
206
+ this.log(` ${bold('Step 4:')} Approve builder fee (${BUILDER_FEE_DISPLAY} per trade)`);
207
+ this.log('');
208
+ this.log(dim(' This supports RIFT development. Only applies to live trades.'));
209
+ this.log(dim(' Backtesting and simulation are free.'));
210
+ this.log('');
211
+ this.log(dim(' Please approve in your wallet app...'));
212
+ const builderResult = await requestBuilderFeeApproval(session, isMainnet);
213
+ if (!builderResult.success) {
214
+ this.log(` ${red('✘')} Builder fee approval failed: ${builderResult.error}`);
215
+ // Still save credentials — agent is approved, just builder fee isn't
216
+ this.saveAndFinish(apiKey, agentAddress, session.account, isMainnet, true, false);
217
+ await disconnectWallet(session);
218
+ return;
219
+ }
220
+ // Post the signed builder fee approval (using the SAME nonce that was signed)
221
+ try {
222
+ await postApprovalToHyperliquid(builderResult.action, builderResult.signature, builderResult.nonce, isMainnet);
223
+ this.log(` ${green('✔')} Builder fee approved (${BUILDER_FEE_DISPLAY})`);
224
+ recordOnChainApproval();
225
+ }
226
+ catch (error) {
227
+ this.log(` ${red('✘')} Failed to submit builder fee: ${error?.message}`);
228
+ this.saveAndFinish(apiKey, agentAddress, session.account, isMainnet, true, false);
229
+ await disconnectWallet(session);
230
+ return;
231
+ }
232
+ // Save everything and finish
233
+ this.saveAndFinish(apiKey, agentAddress, session.account, isMainnet, true, true);
234
+ // Session persists — do NOT disconnect. User can withdraw/transfer later without re-scanning.
235
+ this.log('');
236
+ this.log(` ${bold('═'.repeat(50))}`);
237
+ this.log('');
238
+ this.log(` ${green('✔ RIFT is ready.')}`);
239
+ this.log('');
240
+ this.log(` Main wallet: ${session.account}`);
241
+ this.log(` API wallet: ${agentAddress}`);
242
+ if (tradeable > 0) {
243
+ this.log(` Balance: $${tradeable.toLocaleString()} USDC ${dim(`(${mode} mode)`)}`);
244
+ }
245
+ this.log(` Builder fee: ${BUILDER_FEE_DISPLAY} approved ${green('✔')}`);
246
+ this.log(` Network: mainnet`);
247
+ this.log('');
248
+ this.log(` ${dim('Your wallet session is saved — you won\'t need to scan again.')}`);
249
+ this.log(` ${dim('Trading happens silently via the API wallet.')}`);
250
+ this.log(` ${dim('Withdrawals and transfers send a notification to your phone.')}`);
251
+ this.log('');
252
+ this.log(` ${bold('Next steps:')}`);
253
+ this.log(` ${cyan('rift guide')} ${dim('— 9-step research-to-trade journey')}`);
254
+ this.log(` ${cyan('rift balance')} ${dim('— check spot + perps balances')}`);
255
+ this.log(` ${cyan('rift algo --pair SUI')} ${dim('— start algo trading')}`);
256
+ this.log(` ${cyan('rift buy HYPE --amount 10')} ${dim('— buy spot tokens')}`);
257
+ this.log('');
258
+ this.log(` ${bold('═'.repeat(50))}`);
259
+ this.log('');
260
+ }
261
+ saveAndFinish(apiKey, apiAddress, mainAddress, _isMainnet, // legacy positional, always true now
262
+ agentApproved, builderApproved) {
263
+ const creds = {
264
+ private_key: apiKey,
265
+ address: apiAddress,
266
+ account_address: mainAddress,
267
+ type: 'walletconnect',
268
+ network: 'mainnet',
269
+ registered_at: new Date().toISOString(),
270
+ agent_approved: agentApproved,
271
+ builder_fee_approved: builderApproved,
272
+ };
273
+ saveCredentials(creds);
274
+ }
275
+ }
@@ -0,0 +1,26 @@
1
+ import { GatedCommand } from '../lib/base-command.js';
2
+ export default class Backtest extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ strategy: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ pair: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ tf: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ equity: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
+ leverage: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
13
+ export: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ analyze: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ 'all-pairs': import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ top: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
17
+ };
18
+ run(): Promise<void>;
19
+ private runAnalysis;
20
+ private exportResults;
21
+ private renderResult;
22
+ private row;
23
+ private rowColored;
24
+ private renderAllPairs;
25
+ private progressBar;
26
+ }