@suiware/ai-tools
Version:
Pluggable tools for Vercel AI SDK which allow AI assistants to interact with Sui Network and perform various actions.
414 lines (405 loc) • 12.2 kB
JavaScript
'use strict';
var ai = require('ai');
var z = require('zod');
var transactions = require('@mysten/sui/transactions');
var utils = require('@mysten/sui/utils');
var BigNumber = require('bignumber.js');
var client = require('@mysten/sui/client');
var ed25519 = require('@mysten/sui/keypairs/ed25519');
var secp256k1 = require('@mysten/sui/keypairs/secp256k1');
var secp256r1 = require('@mysten/sui/keypairs/secp256r1');
var EnvFileRW = require('env-file-rw');
var path = require('path');
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(-9).toFixed(8);
}
function suiToMist(amount) {
return (typeof amount === "string" ? parseFloat(amount) : amount) * __pow(10, utils.SUI_DECIMALS);
}
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();
}
// src/services/SuiService.ts
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
});
}
};
// src/services/SuiStakingService.ts
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);
});
}
};
// src/ai/tools/suiStakeTool.ts
var suiStakeTool = ai.tool({
description: "Stake Sui",
parameters: z__default.default.object({
amount: z__default.default.number().min(1).describe("The amount of SUI to stake")
}),
execute: (_0) => __async(null, [_0], function* ({ amount }) {
const suiStakingService = new SuiStakingService();
const digest = yield suiStakingService.stake(amount);
return { digest };
})
});
exports.suiStakeTool = suiStakeTool;
//# sourceMappingURL=suiStakeTool.js.map
//# sourceMappingURL=suiStakeTool.js.map