Skip to content

Commit 799189e

Browse files
committed
sui/cli: stub
1 parent c8ab9e6 commit 799189e

5 files changed

Lines changed: 283 additions & 43 deletions

File tree

cli/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"ntt": "src/index.ts"
1515
},
1616
"dependencies": {
17+
"@mysten/sui": "^1.33.0",
18+
"@wormhole-foundation/sdk-sui-ntt": "^0.5.0",
1719
"chalk": "^5.3.0",
1820
"yargs": "^17.7.2"
1921
}

cli/src/getSigner.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import solana from "@wormhole-foundation/sdk/platforms/solana";
22
import * as myEvmSigner from "./evmsigner.js";
3+
import * as mySuiSigner from "./suisigner.js";
34
import { ChainContext, Wormhole, chainToPlatform, type Chain, type ChainAddress, type Network, type Signer } from "@wormhole-foundation/sdk";
45
import { Keypair } from "@solana/web3.js";
56
import fs from "fs";
@@ -93,6 +94,25 @@ export async function getSigner<N extends Network, C extends Chain>(
9394
throw new Error("Unsupported signer type");
9495
}
9596
break;
97+
case "Sui":
98+
switch (type) {
99+
case "privateKey":
100+
const privateKey = source ?? process.env.SUI_PRIVATE_KEY;
101+
if (!privateKey) {
102+
throw new Error("SUI_PRIVATE_KEY env var not set");
103+
}
104+
signer = await mySuiSigner.getSuiSigner(
105+
await chain.getRpc(),
106+
privateKey,
107+
{ debug: false }
108+
);
109+
break;
110+
case "ledger":
111+
throw new Error("Ledger not yet supported on Sui");
112+
default:
113+
throw new Error("Unsupported signer type");
114+
}
115+
break;
96116
default:
97117
throw new Error("Unrecognized platform: " + platform);
98118
}

cli/src/index.ts

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import readline from "readline";
2020
import { ChainContext, UniversalAddress, Wormhole, assertChain, canonicalAddress, chainToPlatform, chains, isNetwork, networks, platforms, signSendWait, toUniversal, type AccountAddress, type Chain, type ChainAddress, type Network, type Platform } from "@wormhole-foundation/sdk";
2121
import "@wormhole-foundation/sdk-evm-ntt";
2222
import "@wormhole-foundation/sdk-solana-ntt";
23+
import "@wormhole-foundation/sdk-sui-ntt";
2324
import "@wormhole-foundation/sdk-definitions-ntt";
2425
import type { Ntt, NttTransceiver } from "@wormhole-foundation/sdk-definitions-ntt";
2526

@@ -335,6 +336,16 @@ yargs(hideBin(process.argv))
335336
type: "number",
336337
default: 50000,
337338
})
339+
.option("sui-gas-budget", {
340+
describe: "Gas budget for Sui deployment",
341+
type: "number",
342+
default: 100000000,
343+
})
344+
.option("sui-package-path", {
345+
describe: "Path to Sui Move package directory (relative to project root)",
346+
type: "string",
347+
default: "sui",
348+
})
338349
.option("signer-type", options.signerType)
339350
.option("skip-verify", options.skipVerify)
340351
.option("ver", options.version)
@@ -385,11 +396,11 @@ yargs(hideBin(process.argv))
385396
// let's deploy
386397

387398
// TODO: factor out to function to get chain context
388-
const wh = new Wormhole(network, [solana.Platform, evm.Platform], overrides);
399+
const wh = new Wormhole(network, [solana.Platform, evm.Platform], overrides); // TODO: Add sui.Platform
389400
const ch = wh.getChain(chain);
390401

391402
// TODO: make manager configurable
392-
const deployedManager = await deploy(version, mode, ch, token, signerType, !argv["skip-verify"], argv["yes"], argv["payer"], argv["program-key"], argv["binary"], argv["solana-priority-fee"]);
403+
const deployedManager = await deploy(version, mode, ch, token, signerType, !argv["skip-verify"], argv["yes"], argv["payer"], argv["program-key"], argv["binary"], argv["solana-priority-fee"], argv["sui-gas-budget"], argv["sui-package-path"]);
393404

394405
const [config, _ctx, _ntt, decimals] =
395406
await pullChainConfig(network, deployedManager, overrides);
@@ -463,7 +474,7 @@ yargs(hideBin(process.argv))
463474
await askForConfirmation();
464475
}
465476

466-
const wh = new Wormhole(network, [solana.Platform, evm.Platform], overrides);
477+
const wh = new Wormhole(network, [solana.Platform, evm.Platform], overrides); // TODO: Add sui.Platform
467478
const ch = wh.getChain(chain);
468479

469480
const [_, ctx, ntt] = await pullChainConfig(
@@ -1459,7 +1470,9 @@ async function deploy<N extends Network, C extends Chain>(
14591470
solanaPayer?: string,
14601471
solanaProgramKeyPath?: string,
14611472
solanaBinaryPath?: string,
1462-
solanaPriorityFee?: number
1473+
solanaPriorityFee?: number,
1474+
suiGasBudget?: number,
1475+
suiPackagePath?: string
14631476
): Promise<ChainAddress<C>> {
14641477
if (version === null) {
14651478
await warnLocalDeployment(yes);
@@ -1476,6 +1489,9 @@ async function deploy<N extends Network, C extends Chain>(
14761489
}
14771490
const solanaCtx = ch as ChainContext<N, SolanaChains>;
14781491
return await deploySolana(worktree, version, mode, solanaCtx, token, solanaPayer, true, solanaProgramKeyPath, solanaBinaryPath, solanaPriorityFee) as ChainAddress<C>;
1492+
case "Sui":
1493+
const suiCtx = ch as ChainContext<N, Chain>; // TODO: Use proper SuiChains type
1494+
return await deploySui(worktree, version, mode, suiCtx, token, signerType, true, evmVerify, suiGasBudget, suiPackagePath) as ChainAddress<C>;
14791495
default:
14801496
throw new Error("Unsupported platform");
14811497
}
@@ -1826,6 +1842,56 @@ async function deploySolana<N extends Network, C extends SolanaChains>(
18261842
return { chain: ch.chain, address: toUniversal(ch.chain, providedProgramId) };
18271843
}
18281844

1845+
async function deploySui<N extends Network, C extends Chain>(
1846+
pwd: string,
1847+
version: string | null,
1848+
mode: Ntt.Mode,
1849+
ch: ChainContext<N, C>,
1850+
token: string,
1851+
signerType: SignerType,
1852+
initialize: boolean,
1853+
skipVerify?: boolean,
1854+
gasBudget?: number,
1855+
packagePath?: string
1856+
): Promise<ChainAddress<C>> {
1857+
const finalPackagePath = packagePath || "sui";
1858+
const finalGasBudget = gasBudget || 100000000;
1859+
1860+
console.log(`Deploying Sui NTT contracts in ${mode} mode...`);
1861+
console.log(`Package path: ${finalPackagePath}`);
1862+
console.log(`Gas budget: ${finalGasBudget}`);
1863+
console.log(`Target chain: ${ch.chain}`);
1864+
console.log(`Token: ${token}`);
1865+
1866+
// Build the Move packages
1867+
console.log("Building Move packages...");
1868+
try {
1869+
execSync(`cd ${pwd}/${finalPackagePath} && sui move build`, { stdio: "inherit" });
1870+
} catch (e) {
1871+
console.error("Failed to build Move packages");
1872+
throw e;
1873+
}
1874+
1875+
// TODO: Deploy the Move packages using sui client publish
1876+
// This is a placeholder implementation that shows the expected flow
1877+
console.log("TODO: Implement Sui package deployment");
1878+
console.log("Expected deployment steps:");
1879+
console.log("1. sui client publish --gas-budget " + finalGasBudget);
1880+
console.log("2. Initialize NTT manager with token and mode");
1881+
console.log("3. Set up transceivers and configuration");
1882+
console.log("4. Return the deployed manager object ID");
1883+
1884+
// For now, return a placeholder address
1885+
// In a real implementation, this would:
1886+
// 1. Use sui client publish to deploy packages
1887+
// 2. Initialize the NTT manager with proper parameters
1888+
// 3. Return the actual manager address
1889+
const placeholderAddress = "0x0000000000000000000000000000000000000000000000000000000000000000";
1890+
1891+
console.log(chalk.green("Deployment placeholder completed"));
1892+
return { chain: ch.chain, address: toUniversal(ch.chain, placeholderAddress) };
1893+
}
1894+
18291895
async function missingConfigs(
18301896
deps: Partial<{ [C in Chain]: Deployment<Chain> }>,
18311897
verbose: boolean,
@@ -2092,7 +2158,7 @@ async function pullChainConfig<N extends Network, C extends Chain>(
20922158
manager: ChainAddress<C>,
20932159
overrides?: WormholeConfigOverrides<N>
20942160
): Promise<[ChainConfig, ChainContext<typeof network, C>, Ntt<typeof network, C>, number]> {
2095-
const wh = new Wormhole(network, [solana.Platform, evm.Platform], overrides);
2161+
const wh = new Wormhole(network, [solana.Platform, evm.Platform], overrides); // TODO: Add sui.Platform
20962162
const ch = wh.getChain(manager.chain);
20972163

20982164
const nativeManagerAddress = canonicalAddress(manager);

cli/src/suisigner.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type {
2+
Network,
3+
SignOnlySigner,
4+
SignedTx,
5+
Signer,
6+
UnsignedTransaction,
7+
} from '@wormhole-foundation/sdk-connect';
8+
import {
9+
PlatformNativeSigner,
10+
chainToPlatform,
11+
isNativeSigner,
12+
} from '@wormhole-foundation/sdk-connect';
13+
import {
14+
SuiPlatform,
15+
type SuiChains,
16+
_platform
17+
} from '@wormhole-foundation/sdk-sui';
18+
import { SuiClient } from '@mysten/sui/client';
19+
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
20+
import { Transaction } from '@mysten/sui/transactions';
21+
22+
export async function getSuiSigner(
23+
rpc: SuiClient,
24+
key: string | Ed25519Keypair,
25+
opts?: {
26+
debug?: boolean;
27+
},
28+
): Promise<Signer> {
29+
const keypair: Ed25519Keypair =
30+
typeof key === 'string' ? Ed25519Keypair.fromSecretKey(key) : key;
31+
32+
const chain = (await SuiPlatform.chainFromRpc(rpc))[1];
33+
const address = keypair.getPublicKey().toSuiAddress();
34+
35+
return new SuiNativeSigner(
36+
chain,
37+
address,
38+
keypair,
39+
rpc,
40+
opts,
41+
);
42+
}
43+
44+
export class SuiNativeSigner<N extends Network, C extends SuiChains = SuiChains>
45+
extends PlatformNativeSigner<Ed25519Keypair, N, C>
46+
implements SignOnlySigner<N, C>
47+
{
48+
constructor(
49+
_chain: C,
50+
_address: string,
51+
_signer: Ed25519Keypair,
52+
readonly client: SuiClient,
53+
readonly opts?: { debug?: boolean },
54+
) {
55+
super(_chain, _address, _signer);
56+
}
57+
58+
chain(): C {
59+
return this._chain;
60+
}
61+
62+
address(): string {
63+
return this._address;
64+
}
65+
66+
async sign(tx: UnsignedTransaction<N, C>[]): Promise<SignedTx[]> {
67+
const signed = [];
68+
69+
for (const txn of tx) {
70+
const { transaction, description } = txn;
71+
if (this.opts?.debug)
72+
console.log(`Signing: ${description} for ${this.address()}`);
73+
74+
// Build the transaction block
75+
const txb = new Transaction();
76+
// TODO: This is a placeholder - we'll need to properly construct
77+
// Sui transactions based on the transaction data
78+
79+
// Sign the transaction
80+
const result = await this.client.signAndExecuteTransaction({
81+
signer: this._signer,
82+
transaction: txb,
83+
});
84+
85+
signed.push(result.digest);
86+
}
87+
return signed;
88+
}
89+
}
90+
91+
export function isSuiNativeSigner<N extends Network>(
92+
signer: Signer<N>,
93+
): signer is SuiNativeSigner<N> {
94+
return (
95+
isNativeSigner(signer) &&
96+
chainToPlatform(signer.chain()) === _platform &&
97+
isSuiKeypair(signer.unwrap())
98+
);
99+
}
100+
101+
function isSuiKeypair(thing: any): thing is Ed25519Keypair {
102+
return (
103+
typeof thing.getPublicKey === 'function' &&
104+
typeof thing.signPersonalMessage === 'function' &&
105+
typeof thing.signTransaction === 'function'
106+
);
107+
}

0 commit comments

Comments
 (0)