Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"deploy:rfq-vault-executor:all": "ts-node scripts/deploy/deployRFQVaultExecutorAllChains.ts",
"deploy:rfq-vault-executor": "hardhat run scripts/deploy/deployRFQVaultExecutor.ts --network",
"deploy:sui-rfq-vault": "ts-node scripts/sui/deployRfqVault.ts",
"fund:sui-rfq-vault": "ts-node scripts/sui/fundRfqVault.ts",
"sui:rfq-vault:kms-solver": "ts-node scripts/sui/printKmsSolverKey.ts",
"sui:rfq-vault:build": "sui move build --path sui/rfq-vault",
"sui:rfq-vault:test": "sui move test --path sui/rfq-vault",
Expand Down
315 changes: 315 additions & 0 deletions scripts/sui/fundRfqVault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
import { spawnSync } from 'child_process';
import { config as dotenvConfig } from 'dotenv';
import { mkdtemp, rm } from 'fs/promises';
import { resolve } from 'path';
import { tmpdir } from 'os';

import {
SUI_RFQ_VAULT_GAS_BUDGET,
SUI_RFQ_VAULT_NETWORK_ALIAS,
SUI_RFQ_VAULT_RPC_URL,
} from './rfqVaultDeploymentConfig';

dotenvConfig({ path: resolve(__dirname, '../../.env') });

const REPO_ROOT = resolve(__dirname, '../..');
const DEPLOYER_ALIAS = 'rfq-vault-funder';
const TEMP_ENV_ALIAS_PREFIX = 'rfqvault_fund_';
const DEPLOYER_SCHEMES = new Set(['ed25519', 'secp256k1', 'secp256r1']);

// Edit these constants for manual funding runs.
const FUND_AMOUNT = '10';
const FUND_TOKEN_DECIMALS = 6;
const FUND_TOKEN_TYPE =
'0x44f838219cf67b058f3b37907b655f226153c18e33dfcd0da559a844fea9b1c1::usdsui::USDSUI';
const FUND_SOURCE_COIN_ID =
'0xdb07fb3f5389a24866aa8056902cbc9e5fd943967f764a9525c6500cdcf4135a';
const RFQ_VAULT_PACKAGE =
'0x4479fe1d936051823526e9a0c890c3d582001d28c6d57ce26d7bb9159a8c5638';
const RFQ_VAULT_OBJECT =
'0xae8664656a8a5644cc8d7f7c4d1fcf7c5123bdb093a588be46aba55e06f71e6c';
const FUND_GAS_BUDGET = SUI_RFQ_VAULT_GAS_BUDGET || '1000000000';
const FUND_NETWORK_ALIAS = SUI_RFQ_VAULT_NETWORK_ALIAS || 'mainnet';
const FUND_RPC_URL = SUI_RFQ_VAULT_RPC_URL || 'https://fullnode.mainnet.sui.io:443';

type SuiObjectChange = {
type?: string;
objectId?: string;
objectType?: string;
};

type SuiCommandResult = {
digest?: string;
objectChanges?: SuiObjectChange[];
};

function resolveDeployerConfig(): { secret: string; scheme: string } {
const secret = process.env.SUI_DEPLOYER?.trim();
if (!secret) {
throw new Error('SUI_DEPLOYER is required');
}

const scheme = (
process.env.SUI_DEPLOYER_SCHEME?.trim() || 'ed25519'
).toLowerCase();
if (!DEPLOYER_SCHEMES.has(scheme)) {
throw new Error(
`SUI_DEPLOYER_SCHEME must be one of ${Array.from(DEPLOYER_SCHEMES).join(', ')}`,
);
}

return { secret, scheme };
}
Comment on lines +46 to +62

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files scripts/sui/fundRfqVault.ts scripts/sui | sed -n '1,120p'
printf '\n--- outline ---\n'
ast-grep outline scripts/sui/fundRfqVault.ts --view expanded
printf '\n--- line count ---\n'
wc -l scripts/sui/fundRfqVault.ts
printf '\n--- relevant slices ---\n'
sed -n '1,220p' scripts/sui/fundRfqVault.ts | cat -n
printf '\n--- deployment config refs ---\n'
rg -n "GAS_BUDGET|NETWORK_ALIAS|RPC_URL|DEPLOYER_SCHEMES|SUI_DEPLOYER|SUI_DEPLOYER_SCHEME|keytool import|switch --address|spawnSync|timeout" scripts/sui -n -A 3 -B 3

Repository: SocketDotTech/openrouter

Length of output: 21147


🌐 Web query:

Sui CLI keytool import mnemonic alias ignored documented bug

💡 Result:

The issue where the --alias flag was ignored during the sui keytool import command is a documented bug that has been addressed in previous updates to the Sui CLI [1][2]. Previously, users encountered a bug where providing the --alias flag when importing a key via a mnemonic phrase failed to correctly assign or record the specified alias [1][2]. This behavior was identified and resolved through pull requests in the official Sui repository, ensuring that the alias provided during the import process is now correctly stored [1][2]. If you are encountering this issue, ensure your Sui CLI version is up to date. You can specify an alias during import using the following syntax: sui keytool import '<MNEMONIC_PHRASE>' <KEY_SCHEME> --alias <YOUR_ALIAS> If an alias is not provided, the CLI will automatically generate one for the imported key [3]. Additionally, if you need to change an existing alias after an import, you can use the command: sui keytool update-alias <OLD_ALIAS> <NEW_ALIAS> [4]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- import/switch section ---'
sed -n '217,255p' scripts/sui/fundRfqVault.ts | cat -n

printf '\n%s\n' '--- version pin/search ---'
rg -n "sui cli|Sui CLI|keytool import|update-alias|version" . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'

printf '\n%s\n' '--- package metadata ---'
git ls-files package.json pnpm-lock.yaml yarn.lock package-lock.json | sed -n '1,120p'

Repository: SocketDotTech/openrouter

Length of output: 50381


Do not pass the deployer secret on argv. deployer.secret is sent to sui keytool import as a positional argument, so the raw key/mnemonic is visible in ps and /proc/<pid>/cmdline for the lifetime of the process. This should use a non-argv import path or be restricted to a fully isolated single-tenant host.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawnSync } from 'child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/sui/fundRfqVault.ts` around lines 46 - 62, The deployer secret
handling in resolveDeployerConfig() and the subsequent sui keytool import path
should not pass the raw secret as a positional argv argument. Update the import
flow to use a non-argv mechanism such as stdin or a temp file/input channel, and
ensure the code that consumes deployer.secret no longer embeds it in process
arguments. If that cannot be changed, restrict the script to an isolated
single-tenant environment and make the unsafe path explicit.


function assertPositiveInteger(name: string, value: string): void {
if (!/^[1-9][0-9]*$/.test(value)) {
throw new Error(`${name} must be a positive integer`);
}
}

function parseTokenAmount(amount: string, decimals: number): string {
const trimmed = amount.trim();
if (!/^[0-9]+(\.[0-9]+)?$/.test(trimmed)) {
throw new Error('FUND_AMOUNT must be a positive decimal string');
}

const [whole, fractional = ''] = trimmed.split('.');
if (fractional.length > decimals) {
throw new Error(
`FUND_AMOUNT has too many decimal places; token supports ${decimals}`,
);
}

const units = `${whole}${fractional.padEnd(decimals, '0')}`.replace(
/^0+/,
'',
);
if (!units) {
throw new Error('FUND_AMOUNT must be greater than zero');
}

return units;
}

function childEnv(configDir: string): NodeJS.ProcessEnv {
return {
PATH: process.env.PATH || '/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin',
HOME: configDir,
SUI_CONFIG_DIR: configDir,
};
}

function redact(value: string, redactions: readonly string[]): string {
const withExplicitRedactions = redactions.reduce(
(acc, secret) => (secret ? acc.split(secret).join('[redacted]') : acc),
value,
);
return withExplicitRedactions.replace(
/secret recovery phrase\s*:\s*\[[^\]]*]/gi,
'secret recovery phrase : [redacted]',
);
}

function runSui(
args: string[],
params: {
cwd?: string;
env?: NodeJS.ProcessEnv;
label: string;
redactions?: readonly string[];
},
): string {
console.log(`[sui] ${params.label}`);
const result = spawnSync('sui', args, {
cwd: params.cwd ?? REPO_ROOT,
env: params.env,
encoding: 'utf8',
});

if (result.error) {
throw result.error;
}

if (result.status !== 0) {
const output = redact(
[result.stdout, result.stderr].filter(Boolean).join('\n'),
params.redactions ?? [],
);
throw new Error(`${params.label} failed\n${output}`);
}

return result.stdout.trim();
}
Comment on lines +113 to +142

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

No timeout on spawnSync calls.

Every sui invocation (env setup, keytool import, split-coin, vault call) runs via spawnSync with no timeout option. If any call hangs (RPC stall, network partition, or an unexpected interactive prompt), the script blocks indefinitely with no way to recover other than killing the process.

Proposed fix: add a timeout
   const result = spawnSync('sui', args, {
     cwd: params.cwd ?? REPO_ROOT,
     env: params.env,
     encoding: 'utf8',
+    timeout: 120_000,
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function runSui(
args: string[],
params: {
cwd?: string;
env?: NodeJS.ProcessEnv;
label: string;
redactions?: readonly string[];
},
): string {
console.log(`[sui] ${params.label}`);
const result = spawnSync('sui', args, {
cwd: params.cwd ?? REPO_ROOT,
env: params.env,
encoding: 'utf8',
});
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
const output = redact(
[result.stdout, result.stderr].filter(Boolean).join('\n'),
params.redactions ?? [],
);
throw new Error(`${params.label} failed\n${output}`);
}
return result.stdout.trim();
}
function runSui(
args: string[],
params: {
cwd?: string;
env?: NodeJS.ProcessEnv;
label: string;
redactions?: readonly string[];
},
): string {
console.log(`[sui] ${params.label}`);
const result = spawnSync('sui', args, {
cwd: params.cwd ?? REPO_ROOT,
env: params.env,
encoding: 'utf8',
timeout: 120_000,
});
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
const output = redact(
[result.stdout, result.stderr].filter(Boolean).join('\n'),
params.redactions ?? [],
);
throw new Error(`${params.label} failed\n${output}`);
}
return result.stdout.trim();
}
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawnSync } from 'child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/sui/fundRfqVault.ts` around lines 113 - 142, The runSui helper
currently calls spawnSync without any timeout, so any hung sui invocation can
block the script indefinitely. Update runSui to pass a timeout option (and
handle the timeout case explicitly in the existing result.error/result.status
flow), and keep the behavior consistent for all callers such as the env setup,
keytool import, split-coin, and vault call steps.


function runSuiJson<T>(
args: string[],
params: {
cwd?: string;
env?: NodeJS.ProcessEnv;
label: string;
redactions?: readonly string[];
},
): T {
const stdout = runSui(args, params);
try {
return JSON.parse(stdout) as T;
} catch (err) {
throw new Error(
`${params.label} returned invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
);
}
}

function tempEnvAlias(networkAlias: string): string {
return `${TEMP_ENV_ALIAS_PREFIX}${networkAlias.replace(/[^A-Za-z0-9_]/g, '_')}`;
}

function findCreatedCoinId(
result: SuiCommandResult,
coinType: string,
): string {
const expectedObjectType = `0x2::coin::Coin<${coinType}>`;
const coinId = result.objectChanges?.find(
(change) =>
change.type === 'created' &&
change.objectId &&
change.objectType === expectedObjectType,
)?.objectId;
if (!coinId) {
throw new Error(
`Split coin object not found in Sui result for ${expectedObjectType}`,
);
}

return coinId;
}

async function main() {
assertPositiveInteger('FUND_GAS_BUDGET', FUND_GAS_BUDGET);
if (!FUND_SOURCE_COIN_ID.trim()) {
throw new Error('FUND_SOURCE_COIN_ID is required');
}
const deployer = resolveDeployerConfig();
const amountInBaseUnits = parseTokenAmount(
FUND_AMOUNT,
FUND_TOKEN_DECIMALS,
);
const configDir = await mkdtemp(resolve(tmpdir(), 'sui-rfq-vault-fund-'));
const clientConfig = resolve(configDir, 'client.yaml');
const env = childEnv(configDir);
const networkAlias = tempEnvAlias(FUND_NETWORK_ALIAS);

try {
runSui(
[
'client',
'--client.config',
clientConfig,
'-y',
'new-env',
'--alias',
networkAlias,
'--rpc',
FUND_RPC_URL,
],
{ env, label: 'create temporary Sui client env' },
);
runSui(
[
'keytool',
'import',
'--alias',
DEPLOYER_ALIAS,
'--json',
deployer.secret,
deployer.scheme,
],
Comment on lines +219 to +226

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sui --version

Repository: SocketDotTech/openrouter

Length of output: 203


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section of the script.
wc -l scripts/sui/fundRfqVault.ts
sed -n '180,270p' scripts/sui/fundRfqVault.ts

Repository: SocketDotTech/openrouter

Length of output: 2257


🌐 Web query:

sui keytool import mnemonic alias bug fixed upstream release notes alias not kept when importing a key via mnemonic

💡 Result:

The bug where the --alias flag in the sui keytool import command was not correctly saved when importing a key via mnemonic has been fixed [1][2]. This issue was addressed in the Sui repository through pull requests such as #17935 and #20111 [1][2]. The fix ensures that when a user provides an alias during the mnemonic import process, the alias is properly recorded in the Sui CLI Keystore rather than being ignored or replaced by a default random alias [1][2]. If you are experiencing issues with aliases not persisting, ensure you are using an updated version of the Sui CLI. You can always verify or change an alias after importing a key using the update-alias command [3][4][5]: sui keytool update-alias

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline scripts/sui/fundRfqVault.ts --view expanded
printf '\n---\n'
rg -n "resolveDeployerConfig|DEPLOYER_ALIAS|update-alias|mnemonic|secret|scheme" scripts/sui/fundRfqVault.ts

Repository: SocketDotTech/openrouter

Length of output: 2035


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '40,62p' scripts/sui/fundRfqVault.ts

Repository: SocketDotTech/openrouter

Length of output: 727


Don't rely on keytool import --alias for mnemonic inputs. Older sui releases can drop the alias during mnemonic import, which breaks the later switch --address DEPLOYER_ALIAS step. This should either pin a fixed CLI version or set the alias explicitly after import.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawnSync } from 'child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/sui/fundRfqVault.ts` around lines 219 - 226, The mnemonic import flow
in keytool import currently depends on --alias, which can be lost on older Sui
releases and break the later switch --address DEPLOYER_ALIAS step. Update the
fundRfqVault import path to either pin a known-safe Sui CLI version or
explicitly set/verify the alias after import in the keytool import/deployer
handling logic so DEPLOYER_ALIAS is guaranteed to exist.

{
env,
label: 'import SUI_DEPLOYER into temporary keystore',
redactions: [deployer.secret],
},
);
runSui(
[
'client',
'--client.config',
clientConfig,
'switch',
'--env',
networkAlias,
],
{ env, label: 'select Sui env' },
);
runSui(
[
'client',
'--client.config',
clientConfig,
'switch',
'--address',
DEPLOYER_ALIAS,
],
{ env, label: 'select Sui funder' },
);

const splitResult = runSuiJson<SuiCommandResult>(
[
'client',
'--client.config',
clientConfig,
'--json',
'split-coin',
'--coin-id',
FUND_SOURCE_COIN_ID,
'--amounts',
amountInBaseUnits,
'--gas-budget',
FUND_GAS_BUDGET,
],
{ env, label: `split ${FUND_AMOUNT} token units for vault funding` },
);
const splitCoinId = findCreatedCoinId(splitResult, FUND_TOKEN_TYPE);

const fundResult = runSuiJson<SuiCommandResult>(
[
'client',
'--client.config',
clientConfig,
'--json',
'call',
'--package',
RFQ_VAULT_PACKAGE,
'--module',
'vault',
'--function',
'fund',
'--type-args',
FUND_TOKEN_TYPE,
'--args',
RFQ_VAULT_OBJECT,
splitCoinId,
'--gas-budget',
FUND_GAS_BUDGET,
],
{ env, label: 'fund RFQ vault' },
);

console.log('');
console.log('Sui RFQ vault funded');
console.log(`Amount: ${FUND_AMOUNT}`);
console.log(`Base units: ${amountInBaseUnits}`);
console.log(`Token: ${FUND_TOKEN_TYPE}`);
console.log(`Vault: ${RFQ_VAULT_OBJECT}`);
console.log(`Split tx: ${splitResult.digest ?? 'unknown'}`);
console.log(`Split coin: ${splitCoinId}`);
console.log(`Fund tx: ${fundResult.digest ?? 'unknown'}`);
} finally {
await rm(configDir, { recursive: true, force: true });
}
}

main().catch((err) => {
console.error(err);
process.exit(1);
});