UNPKG

@suiware/ai-tools

Version:

Pluggable tools for Vercel AI SDK which allow AI assistants to interact with Sui Network and perform various actions.

611 lines (603 loc) 18.5 kB
'use strict'; var ai = require('ai'); var z = require('zod'); var utils = require('@mysten/sui/utils'); var BigNumber = require('bignumber.js'); var client = require('@mysten/sui/client'); var naviSdk = require('navi-sdk'); var EnvFileRW = require('env-file-rw'); var path = require('path'); var ed25519 = require('@mysten/sui/keypairs/ed25519'); var secp256k1 = require('@mysten/sui/keypairs/secp256k1'); var secp256r1 = require('@mysten/sui/keypairs/secp256r1'); var transactions = require('@mysten/sui/transactions'); var suins = require('@mysten/suins'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var z__default = /*#__PURE__*/_interopDefault(z); var BigNumber__default = /*#__PURE__*/_interopDefault(BigNumber); var EnvFileRW__default = /*#__PURE__*/_interopDefault(EnvFileRW); var path__namespace = /*#__PURE__*/_interopNamespace(path); var __pow = Math.pow; var __async = (__this, __arguments, generator) => { return new Promise((resolve2, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve2(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; function formatBalance(balance, decimals) { return BigNumber__default.default(balance.toString()).shiftedBy(-decimals).toFixed(8); } function suiToMist(amount) { return (typeof amount === "string" ? parseFloat(amount) : amount) * __pow(10, utils.SUI_DECIMALS); } function disableConsoleLog() { const originalConsoleLog = console.log; console.log = () => { }; return originalConsoleLog; } function enableConsoleLog(originalConsoleLog) { console.log = originalConsoleLog; } var SUPPORTED_COINS = [ naviSdk.NAVX, naviSdk.Sui, naviSdk.vSui, naviSdk.USDT, naviSdk.WETH, naviSdk.CETUS, naviSdk.haSui, naviSdk.WBTC, naviSdk.AUSD, naviSdk.wUSDC, naviSdk.nUSDC, naviSdk.ETH, naviSdk.USDY, naviSdk.NS, naviSdk.LorenzoBTC, naviSdk.DEEP, naviSdk.FDUSD, naviSdk.BLUE, naviSdk.BUCK, naviSdk.suiUSDT, naviSdk.stSUI, naviSdk.suiBTC, naviSdk.WSOL, naviSdk.LBTC, naviSdk.WAL ]; var Environment = class _Environment { // Singleton. static getInstance() { if (!_Environment.instance) { _Environment.instance = new _Environment(); } return _Environment.instance; } getSetting(name) { var _a; return (_a = this.env) == null ? void 0 : _a.get(name); } setSetting(name, value) { var _a; (_a = this.env) == null ? void 0 : _a.set(name, value); } saveSettings() { var _a; (_a = this.env) == null ? void 0 : _a.save(); } constructor() { var _a; const providedEnvConfigFilePath = (_a = process.env) == null ? void 0 : _a.SUIWARE_MCP_ENV_CONFIG_FILE_PATH; let envPath = ".env"; if (providedEnvConfigFilePath) { envPath = providedEnvConfigFilePath; } if (!path__namespace.isAbsolute(envPath)) { envPath = path__namespace.resolve(process.cwd(), envPath); } try { this.env = new EnvFileRW__default.default(envPath, true); } catch (e) { } } }; function getSetting(name) { var _a; const env = Environment.getInstance(); return ((_a = process.env) == null ? void 0 : _a[name]) || env.getSetting(name); } function saveSettings(settings) { const env = Environment.getInstance(); for (const [key, value] of Object.entries(settings)) { env.setSetting(key, value); process.env[key] = value; } env.saveSettings(); } var SuiService = class _SuiService { constructor() { this.coinInfoMap = /* @__PURE__ */ new Map(); this.init(); const network = this.getNetwork(); const privateKey = this.getPrivateKey(); this.signer = this.extractSignerFromPrivateKey(privateKey); this.client = new client.SuiClient({ url: client.getFullnodeUrl(network) }); } // Singleton. static getInstance() { if (!_SuiService.instance) { _SuiService.instance = new _SuiService(); } return _SuiService.instance; } getNetwork() { return this.network; } getPrivateKey() { return this.privateKey; } getSuiClient() { return this.client; } /** * Execute a transaction. * * @param transaction - The transaction block to execute. * @returns The transaction response. */ executeTransaction(transaction) { return __async(this, null, function* () { const instance = _SuiService.getInstance(); if (!instance.signer) { throw new Error("Signer not found"); } return instance.client.signAndExecuteTransaction({ transaction, signer: instance.signer }); }); } /** * Get the address of the wallet. * * @returns The address of the wallet. */ getAddress() { return this.signer.toSuiAddress(); } /** * Gets the balance of the wallet. * * @returns The balance of the wallet in MIST (smallest Sui unit). */ getBalance() { return __async(this, null, function* () { const address = this.getAddress(); if (!address) { throw new Error("Address not found"); } const balance = yield this.client.getBalance({ owner: address }); return BigInt(balance.totalBalance); }); } /** * Waits for a transaction receipt. * * @param digest - The digest of the transaction to wait for. * @returns The transaction receipt. */ waitForTransactionReceipt(digest) { return __async(this, null, function* () { return this.client.waitForTransaction({ digest, options: { // showEvents: true, // showEffects: true, } }); }); } /** * Transfer the given amount of SUI to the given address. * * @param to - The destination address. * @param value - The amount to transfer in whole SUI units * @returns The transaction digest. */ nativeTransfer(to, value) { return __async(this, null, function* () { const amountInMist = suiToMist(value); const tx = new transactions.Transaction(); const [coin] = tx.splitCoins(tx.gas, [amountInMist]); tx.transferObjects([coin], to); const response = yield this.executeTransaction(tx); if (!response.digest) { throw new Error("Transaction failed"); } return response.digest; }); } createAccount(network) { const keypair = ed25519.Ed25519Keypair.generate(); const address = keypair.toSuiAddress(); const privateKey = `suiprivkey${Buffer.from(keypair.getSecretKey()).toString("hex")}`; this.network = network; this.privateKey = privateKey; this.signer = this.extractSignerFromPrivateKey(privateKey); this.saveConfig(); return { address, privateKey, network }; } static isValidPrivateKey(privateKey) { return privateKey != null && privateKey.startsWith("suiprivkey"); } static isValidSuiAddress(address) { return utils.isValidSuiAddress(address); } static isNotMyOwnAddress(address) { return this.getInstance().getAddress() !== address; } getCoinMetadata(coinType) { return __async(this, null, function* () { if (this.coinInfoMap.has(coinType)) { return this.coinInfoMap.get(coinType) || null; } const metadata = yield this.client.getCoinMetadata({ coinType }); if (!metadata) { return null; } this.coinInfoMap.set(coinType, metadata); return metadata; }); } extractSignerFromPrivateKey(privateKey) { const keypairClasses = [ed25519.Ed25519Keypair, secp256k1.Secp256k1Keypair, secp256r1.Secp256r1Keypair]; for (const KeypairClass of keypairClasses) { try { return KeypairClass.fromSecretKey(privateKey); } catch (e) { } } throw new Error("Failed to initialize keypair from secret key"); } init() { const network = getSetting("SUI_NETWORK"); const privateKey = getSetting("SUI_PRIVATE_KEY"); if (network == null || network.trim() === "") { throw new Error("Network is not set"); } if (privateKey == null || !_SuiService.isValidPrivateKey(privateKey)) { throw new Error("Private key is not valid"); } this.network = network; this.privateKey = privateKey; } saveConfig() { if (!this.network) { throw new Error("Network is not set"); } if (!this.privateKey) { throw new Error("Private key is not set"); } saveSettings({ SUI_NETWORK: this.network, SUI_PRIVATE_KEY: this.privateKey }); } }; var SuinsService = class { constructor(suiClient) { this.suiClient = suiClient; this.suinsClient = new suins.SuinsClient({ client: this.suiClient, network: getSetting("SUI_NETWORK") }); } resolveSuinsName(name) { return __async(this, null, function* () { const nameRecord = yield this.suinsClient.getNameRecord(name); return (nameRecord == null ? void 0 : nameRecord.targetAddress) || null; }); } static isValidSuinsName(name) { return name.endsWith(".sui") || name.startsWith("@"); } }; // src/services/NaviService.ts var NaviService = class _NaviService { constructor() { const privateKey = getSetting("SUI_PRIVATE_KEY"); if (!SuiService.isValidPrivateKey(privateKey)) { throw new Error("Invalid SUI_PRIVATE_KEY in the config"); } const suiNetwork = getSetting("SUI_NETWORK"); this.naviClient = new naviSdk.NAVISDKClient({ privateKeyList: [privateKey], networkType: suiNetwork }); this.account = this.naviClient.accounts[0]; this.suiClient = new client.SuiClient({ url: client.getFullnodeUrl(suiNetwork) }); } getSuiClient() { return this.suiClient; } getAddress() { return this.account.address; } getAllBalances() { return __async(this, null, function* () { const allBalances = yield this.naviClient.getAllBalances(); return _NaviService.getSupportedCoins().map((x) => ({ [_NaviService.naviUsdcToUsdc(x.symbol)]: allBalances[x.address] })); }); } swap(sourceToken, targetToken, amount) { return __async(this, null, function* () { if (this.naviClient.networkType !== "mainnet") { throw new Error("Only mainnet is supported"); } const sourceTokenMetadata = _NaviService.getSupportedCoinBySymbol(sourceToken); const targetTokenMetadata = _NaviService.getSupportedCoinBySymbol(targetToken); const amountIn = (typeof amount === "string" ? parseFloat(amount) : amount) * __pow(10, sourceTokenMetadata.decimal); return yield this.account.swap( sourceTokenMetadata.address, targetTokenMetadata.address, amountIn, 0 ); }); } transfer(token, address, amount) { return __async(this, null, function* () { let resolvedAddress = address; if (SuinsService.isValidSuinsName(address)) { const suinsService = new SuinsService(this.getSuiClient()); resolvedAddress = yield suinsService.resolveSuinsName(address); if (!resolvedAddress) { throw new Error(`Suins name ${address} not found`); } } if (resolvedAddress == this.getAddress()) { throw new Error("You cannot transfer to your own address"); } const sourceTokenMetadata = _NaviService.getSupportedCoinBySymbol(token); const amountIn = (typeof amount === "string" ? parseFloat(amount) : amount) * __pow(10, sourceTokenMetadata.decimal); return yield this.account.sendCoin( sourceTokenMetadata.address, resolvedAddress, amountIn ); }); } getWalletNonZeroCoins() { return __async(this, null, function* () { const allBalances = yield this.suiClient.getAllBalances({ owner: this.account.address }); const coinBalances = {}; for (const { coinType, totalBalance } of allBalances) { if (parseFloat(totalBalance) == 0) { continue; } const coinInfo = this.getSupportedCoinByAddress(coinType); if (coinInfo) { coinBalances[coinInfo.symbol] = formatBalance( totalBalance, coinInfo.decimal ); continue; } const coinMetadata = yield this.fetchCoinMetadata(coinType); if (coinMetadata) { coinBalances[coinMetadata.symbol] = formatBalance( totalBalance, coinMetadata.decimals ); continue; } const decimal = yield this.account.getCoinDecimal(coinType); coinBalances[coinType] = formatBalance(totalBalance, decimal); } return coinBalances; }); } getWalletBalance() { return __async(this, null, function* () { return yield this.naviClient.accounts[0].getWalletBalance(); }); } static getSupportedCoins() { return SUPPORTED_COINS; } static isSupportedCoin(symbol) { return this.getSupportedCoins().some( (coin) => this.naviUsdcToUsdc(coin.symbol).toUpperCase() === symbol.toUpperCase() ); } static getSupportedCoinBySymbol(symbol) { return this.getSupportedCoins().find( (coin) => this.naviUsdcToUsdc(coin.symbol).toUpperCase() === symbol.toUpperCase() ); } static getMissingSupportedCoin(symbol) { return this.getSupportedCoins().find( (coin) => this.naviUsdcToUsdc(coin.symbol).toUpperCase() !== symbol.toUpperCase() ); } getSupportedCoinByAddress(address) { const naviCoins = _NaviService.getSupportedCoins(); return naviCoins.find((x) => x.address === address); } fetchCoinMetadata(coinType) { return __async(this, null, function* () { return this.suiClient.getCoinMetadata({ coinType }); }); } static naviUsdcToUsdc(symbol) { return symbol === "nUSDC" ? "USDC" : symbol; } }; var SuiStakingService = class { constructor() { // @todo: Make the validator address configurable. this.VALIDATOR_ADDRESSES = { mainnet: "0x4fffd0005522be4bc029724c7f0f6ed7093a6bf3a09b90e62f61dc15181e1a3e", // Mysten-1 testnet: "0x6d6e9f9d3d81562a0f9b767594286c69c21fea741b1c2303c5b7696d6c63618a" // Mysten-1 }; this.suiService = SuiService.getInstance(); } /** * Stake SUI tokens to a validator. * * @param amount - The amount to stake in whole SUI units. * @returns The transaction digest. */ stake(amount) { return __async(this, null, function* () { const amountInMist = suiToMist(amount); const tx = new transactions.Transaction(); const [coin] = tx.splitCoins(tx.gas, [tx.pure.u64(amountInMist)]); const validatorAddress = this.VALIDATOR_ADDRESSES[this.suiService.getNetwork()]; tx.moveCall({ target: "0x3::sui_system::request_add_stake", arguments: [ tx.object(utils.SUI_SYSTEM_STATE_OBJECT_ID), coin, tx.pure.address(validatorAddress) ] }); const response = yield this.suiService.executeTransaction(tx); if (!response.digest) { throw new Error("Staking transaction failed"); } return response.digest; }); } /** * Unstake all stakes. * * @returns The transaction digests. */ unstake() { return __async(this, null, function* () { const stakedObjects = yield this.getStakedObjects(); if (stakedObjects.length === 0) { throw new Error("No staked objects found"); } const tx = new transactions.Transaction(); stakedObjects.forEach((x) => { x.stakes.forEach(({ stakedSuiId }) => { tx.moveCall({ target: "0x3::sui_system::request_withdraw_stake", arguments: [ tx.object(utils.SUI_SYSTEM_STATE_OBJECT_ID), tx.object(stakedSuiId) ] }); }); }); const response = yield this.suiService.executeTransaction(tx); if (!response.digest) { throw new Error("Unstaking transaction failed"); } return response.digest; }); } /** * Get all staked objects. * * @returns Array of delegated stake objects. */ getStakedObjects() { return __async(this, null, function* () { const stakedObjects = yield this.suiService.getSuiClient().getStakes({ owner: this.suiService.getAddress() }); return stakedObjects; }); } /** * Get the total staked Sui balance. * * @returns The total staked balance. */ getTotalStakedBalance() { return __async(this, null, function* () { const stakedObjects = yield this.getStakedObjects(); const amountMist = stakedObjects.reduce((total, delegatedStake) => { const stakesTotal = delegatedStake.stakes.reduce( (stakeTotal, stake) => stakeTotal + BigInt(stake.principal), BigInt(0) ); return total + stakesTotal; }, BigInt(0)); return formatBalance(amountMist, 9); }); } }; // src/ai/tools/suiWalletBalanceTool.ts var suiWalletBalanceTool = ai.tool({ description: "Get non-zero wallet balances. Note that the nUSDC balance should be displayed as USDC.", parameters: z__default.default.object({}), execute: () => __async(null, null, function* () { const originalConsoleLog = disableConsoleLog(); const naviService = new NaviService(); const balances = yield naviService.getWalletNonZeroCoins(); enableConsoleLog(originalConsoleLog); const suiStakingService = new SuiStakingService(); const stakedSuiBalance = yield suiStakingService.getTotalStakedBalance(); if (Number(stakedSuiBalance) > 0) { balances["Natively Staked SUI"] = stakedSuiBalance; } return { balances }; }) }); exports.suiWalletBalanceTool = suiWalletBalanceTool; //# sourceMappingURL=suiWalletBalanceTool.js.map //# sourceMappingURL=suiWalletBalanceTool.js.map