@fizzyflow/suisql
Version:
SuiSQL is a library and set of tools for working with decentralized SQL databases on the Sui blockchain and Walrus protocol.
647 lines (646 loc) • 22.2 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var SuiSqlBlockchain_exports = {};
__export(SuiSqlBlockchain_exports, {
default: () => SuiSqlBlockchain
});
module.exports = __toCommonJS(SuiSqlBlockchain_exports);
var import_SuiSqlConsts = require("./SuiSqlConsts.js");
var import_transactions = require("@mysten/sui/transactions");
var import_bcs = require("@mysten/sui/bcs");
var import_SuiSqlLog = __toESM(require("./SuiSqlLog.js"));
class SuiSqlBlockchain {
constructor(params) {
__publicField(this, "suiClient");
__publicField(this, "signer");
__publicField(this, "currentWalletAddress");
__publicField(this, "signAndExecuteTransaction");
__publicField(this, "network", "local");
__publicField(this, "forcedPackageId");
__publicField(this, "bankId");
__publicField(this, "__walCoinType");
this.suiClient = params.suiClient;
this.signer = params.signer;
this.currentWalletAddress = params.currentWalletAddress;
if (params.signAndExecuteTransaction) {
this.signAndExecuteTransaction = params.signAndExecuteTransaction;
}
if (params.network) {
this.network = params.network;
}
}
setPackageId(packageId) {
this.forcedPackageId = packageId;
delete this.bankId;
}
getPackageId() {
if (this.forcedPackageId) {
return this.forcedPackageId;
}
if (import_SuiSqlConsts.packages[this.network]) {
return import_SuiSqlConsts.packages[this.network];
}
return null;
}
async getWriteCapId(dbId) {
if (!this.suiClient) {
throw new Error("suiClient required");
}
const packageId = await this.getPackageId();
if (!packageId) {
throw new Error("can not find bank if do not know the package");
}
const currentAddress = this.getCurrentAddress();
if (!currentAddress) {
return null;
}
const result = await this.suiClient.getOwnedObjects({
owner: currentAddress,
filter: {
StructType: packageId + "::suisql::WriteCap"
},
options: {
showContent: true
}
});
let writeCapId = null;
for (const obj of result.data) {
const fields = (obj?.data?.content).fields;
if (fields?.sui_sql_db_id == dbId) {
writeCapId = obj?.data?.objectId;
}
}
return writeCapId;
}
async getBankId() {
if (this.bankId) {
return this.bankId;
}
const packageId = await this.getPackageId();
if (!packageId) {
throw new Error("can not find bank if do not know the package");
}
if (import_SuiSqlConsts.bankIds[this.network]) {
this.bankId = import_SuiSqlConsts.bankIds[this.network];
return this.bankId;
}
if (!this.suiClient) {
throw new Error("suiClient required");
}
let bankId = null;
const resp = await this.suiClient.queryEvents({
query: { "MoveEventType": "" + packageId + "::suisql_events::NewBankEvent" }
});
if (resp && resp.data && resp.data[0] && resp.data[0].parsedJson) {
bankId = resp.data[0].parsedJson.id;
}
this.bankId = bankId;
return this.bankId;
}
async getFields(dbId) {
const result = await this.suiClient.getObject({
id: dbId,
// normalized id
options: {
"showType": true,
"showOwner": true,
"showPreviousTransaction": true,
"showDisplay": false,
"showContent": true,
"showBcs": false,
"showStorageRebate": true
}
});
let patches = [];
let walrusBlobId = null;
let walrusEndEpoch = null;
let walrusStorageSize = null;
let expectedWalrusBlobId = null;
let owner = null;
if (result?.data?.content) {
const fields = result.data.content.fields;
if (fields && fields.id && fields.id.id) {
patches = fields.patches;
}
if (fields && fields.walrus_blob_id) {
walrusBlobId = fields.walrus_blob_id;
}
if (fields && fields.expected_walrus_blob_id) {
expectedWalrusBlobId = fields.expected_walrus_blob_id;
}
if (fields && fields.walrus_blob && fields.walrus_blob.fields && fields.walrus_blob.fields.storage) {
walrusEndEpoch = parseInt("" + fields.walrus_blob.fields.storage.fields.end_epoch);
}
if (fields && fields.walrus_blob && fields.walrus_blob.fields && fields.walrus_blob.fields.storage) {
walrusStorageSize = parseInt("" + fields.walrus_blob.fields.storage.fields.storage_size);
}
if (result.data.owner) {
owner = result.data.owner;
}
}
return {
patches,
walrusBlobId,
walrusEndEpoch,
walrusStorageSize,
expectedWalrusBlobId,
owner
};
}
// async getFull(walrusBlobId: string) {
// return await this.walrus?.read(walrusBlobId);
// }
// async saveFull(dbId: string, full: Uint8Array) {
// const packageId = await this.getPackageId();
// if (!packageId || !this.suiClient || !this.walrus) {
// throw new Error('no packageId or no signer or no walrus');
// }
// const blobId = await this.walrus.write(full);
// if (!blobId) {
// throw new Error('can not write blob to walrus');
// }
// const tx = new Transaction();
// const target = ''+packageId+'::suisql::clamp_with_walrus';
// const args = [
// tx.object(dbId),
// tx.pure(bcs.string().serialize(blobId)),
// ];
// tx.moveCall({
// target,
// arguments: args,
// typeArguments: [],
// });
// try {
// const txResults = await this.executeTx(tx);
// return true;
// } catch (e) {
// SuiSqlLog.log('executing tx to saveFull failed', e);
// return false;
// }
// // tx.setSenderIfNotSet(this.signer.toSuiAddress());
// // const transactionBytes = await tx.build({ client: this.suiClient });
// // const result = await this.suiClient.signAndExecuteTransaction({
// // signer: this.signer,
// // transaction: transactionBytes,
// // });
// // if (result && result.digest) {
// // try {
// // await this.suiClient.waitForTransaction({
// // digest: result.digest,
// // });
// // return true;
// // } catch (_) {
// // return false;
// // }
// // }
// // return false;
// }
async getWalCoinType() {
if (this.__walCoinType) {
return this.__walCoinType;
}
const packageId = await this.getPackageId();
if (!packageId || !this.suiClient) {
throw new Error("no packageId or no signer");
}
const normalized = await this.suiClient.getNormalizedMoveFunction({
package: packageId,
module: "suisql",
function: "extend_walrus"
});
let walCoinType = null;
if (normalized && normalized.parameters && normalized.parameters.length > 3) {
const walPackage = normalized.parameters[3]?.MutableReference?.Struct?.typeArguments[0]?.Struct?.address;
walCoinType = "" + walPackage + "::wal::WAL";
}
if (!walCoinType) {
throw new Error("can not get walCoinType from extend_walrus method signature");
}
this.__walCoinType = walCoinType;
return walCoinType;
}
async getWalCoinForTx(tx, amount) {
const packageId = await this.getPackageId();
if (!packageId || !this.suiClient) {
throw new Error("no packageId or no signer");
}
const currentAddress = this.getCurrentAddress();
if (!currentAddress) {
throw new Error("no current wallet address");
}
const walCoinType = await this.getWalCoinType();
const walCoin = await this.coinOfAmountToTxCoin(tx, currentAddress, walCoinType, amount, true);
return walCoin;
}
async extendWalrus(dbId, walrusSystemAddress, extendedEpochs, totalPrice) {
const packageId = await this.getPackageId();
if (!packageId || !this.suiClient) {
throw new Error("no packageId or no signer");
}
const currentAddress = this.getCurrentAddress();
if (!currentAddress) {
throw new Error("no current wallet address");
}
const tx = new import_transactions.Transaction();
const target = "" + packageId + "::suisql::extend_walrus";
const normalized = await this.suiClient.getNormalizedMoveFunction({
package: packageId,
module: "suisql",
function: "extend_walrus"
});
let walCoinType = null;
if (normalized && normalized.parameters && normalized.parameters.length > 3) {
const walPackage = normalized.parameters[3]?.MutableReference?.Struct?.typeArguments[0]?.Struct?.address;
walCoinType = "" + walPackage + "::wal::WAL";
}
if (!walCoinType) {
throw new Error("can not get walCoinType from extend_walrus method signature");
}
const walCoin = await this.coinOfAmountToTxCoin(tx, currentAddress, walCoinType, totalPrice || BigInt(1e10), true);
const args = [
tx.object(dbId),
tx.object(walrusSystemAddress),
tx.pure(import_bcs.bcs.u32().serialize(extendedEpochs)),
walCoin
];
tx.moveCall({
target,
arguments: args,
typeArguments: []
});
tx.transferObjects([walCoin], currentAddress);
try {
const txResults = await this.executeTx(tx);
if (txResults && txResults.events && txResults.events.length) {
for (const event of txResults.events) {
if (event && event.type && event.type.indexOf("BlobCertified") !== -1) {
const updatedEndEpoch = event.parsedJson.end_epoch;
if (updatedEndEpoch) {
return parseInt("" + updatedEndEpoch);
}
}
}
}
return true;
} catch (e) {
console.error("fillExpectedWalrus error", e);
return false;
}
}
async clampWithWalrus(dbId, blobAddress, walrusSystemAddress) {
const packageId = await this.getPackageId();
if (!packageId || !this.suiClient) {
throw new Error("no packageId or no signer");
}
const writeCapId = await this.getWriteCapId(dbId);
if (!writeCapId) {
throw new Error("no writeCapId");
}
const tx = new import_transactions.Transaction();
const target = "" + packageId + "::suisql::clamp_with_walrus";
const args = [
tx.object(dbId),
tx.object(writeCapId),
tx.object(walrusSystemAddress),
tx.object(blobAddress)
];
tx.moveCall({
target,
arguments: args,
typeArguments: []
});
try {
const txResults = await this.executeTx(tx);
return true;
} catch (e) {
console.error("fillExpectedWalrus error", e);
return false;
}
}
async fillExpectedWalrus(dbId, blobAddress, walrusSystemAddress) {
const packageId = await this.getPackageId();
if (!packageId || !this.suiClient) {
throw new Error("no packageId or no signer");
}
const tx = new import_transactions.Transaction();
const target = "" + packageId + "::suisql::fill_expected_walrus";
console.log(dbId, walrusSystemAddress, blobAddress);
const args = [
tx.object(dbId),
tx.object(walrusSystemAddress),
tx.object(blobAddress)
];
tx.moveCall({
target,
arguments: args,
typeArguments: []
});
try {
const txResults = await this.executeTx(tx);
return true;
} catch (e) {
console.error("fillExpectedWalrus error", e);
return false;
}
}
async savePatch(dbId, patch, expectedWalrusBlobId) {
const packageId = await this.getPackageId();
if (!packageId || !this.suiClient) {
throw new Error("no packageId or no signer");
}
const writeCapId = await this.getWriteCapId(dbId);
if (!writeCapId) {
throw new Error("no writeCapId");
}
const tx = new import_transactions.Transaction();
const target = "" + packageId + "::suisql::patch" + (expectedWalrusBlobId ? "_and_expect_walrus" : "");
const args = [
tx.object(dbId),
tx.object(writeCapId),
tx.pure(import_bcs.bcs.vector(import_bcs.bcs.u8()).serialize(patch))
];
if (expectedWalrusBlobId) {
args.push(tx.pure(import_bcs.bcs.u256().serialize(expectedWalrusBlobId)));
}
tx.moveCall({
target,
arguments: args,
typeArguments: []
});
try {
const txResults = await this.executeTx(tx);
return true;
} catch (e) {
console.error("savePatch error", e);
return false;
}
}
async getDbId(name) {
const packageId = await this.getPackageId();
const bankId = await this.getBankId();
if (!packageId || !bankId || !this.suiClient) {
throw new Error("no bankId or packageId");
}
const tx = new import_transactions.Transaction();
const target = "" + packageId + "::suisql::find_db_by_name";
const input = new TextEncoder().encode(name);
const args = [
tx.object(bankId),
tx.pure(import_bcs.bcs.vector(import_bcs.bcs.u8()).serialize(input))
];
tx.moveCall({
target,
arguments: args,
typeArguments: []
});
const sender = "0x0000000000000000000000000000000000000000000000000000000000000000";
tx.setSenderIfNotSet(sender);
const sims = await this.suiClient.devInspectTransactionBlock({
transactionBlock: tx,
sender
});
let foundDbId = null;
if (sims && sims.events && sims.events.length) {
for (const event of sims.events) {
if (event && event.type && event.type.indexOf("RemindDBEvent") !== -1) {
foundDbId = event.parsedJson.id;
}
}
}
return foundDbId;
}
async makeDb(name) {
const packageId = await this.getPackageId();
const bankId = await this.getBankId();
if (!packageId || !bankId || !this.suiClient) {
throw new Error("no bankId or packageId or no signer");
}
const tx = new import_transactions.Transaction();
const target = "" + packageId + "::suisql::db";
const input = new TextEncoder().encode(name);
const args = [
tx.object(bankId),
tx.pure(import_bcs.bcs.vector(import_bcs.bcs.u8()).serialize(input))
];
tx.moveCall({
target,
arguments: args,
typeArguments: []
});
let createdDbId = null;
const txResults = await this.executeTx(tx);
if (txResults && txResults.events && txResults.events.length) {
for (const event of txResults.events) {
if (event && event.type && event.type.indexOf("NewDBEvent") !== -1) {
createdDbId = event.parsedJson.id;
}
}
}
if (!createdDbId) {
throw new Error("can not create suiSql db");
}
return createdDbId;
}
async listDatabases(callback) {
const packageId = await this.getPackageId();
const bankId = await this.getBankId();
if (!packageId || !bankId || !this.suiClient) {
throw new Error("no bankId or packageId or no suiClient");
}
const resp = await this.suiClient.getObject({
id: bankId,
options: {
showContent: true
}
});
const mapId = resp.data?.content?.fields?.map?.fields?.id?.id;
let cursor = null;
let hasNextPage = false;
const ret = [];
do {
const resp2 = await this.suiClient.getDynamicFields({
parentId: mapId
});
const thisRunRet = [];
for (const obj of resp2.data) {
let name = obj.name.value;
ret.push("" + name);
thisRunRet.push("" + name);
}
if (callback) {
await callback(thisRunRet);
}
if (resp2 && resp2.hasNextPage) {
hasNextPage = true;
cursor = resp2.nextCursor;
} else {
hasNextPage = false;
}
} while (hasNextPage);
return ret;
}
getCurrentAddress() {
if (!this.suiClient) {
throw new Error("no suiClient");
}
if (this.signer) {
return this.signer.toSuiAddress();
}
if (this.currentWalletAddress) {
return this.currentWalletAddress;
}
return null;
}
async executeTx(tx) {
if (!this.suiClient) {
throw new Error("no suiClient");
}
let digest = null;
if (this.signAndExecuteTransaction) {
digest = await this.signAndExecuteTransaction(tx);
} else if (this.signer) {
tx.setSenderIfNotSet(this.signer.toSuiAddress());
const transactionBytes = await tx.build({ client: this.suiClient });
const result = await this.suiClient.signAndExecuteTransaction({
signer: this.signer,
transaction: transactionBytes,
requestType: "WaitForLocalExecution"
});
if (result && result.digest) {
digest = result.digest;
}
} else {
throw new Error("either signer or signAndExecuteTransaction function required");
}
if (digest) {
const finalResults = await this.suiClient.getTransactionBlock({
digest,
options: {
showEffects: true,
showEvents: true
}
});
return finalResults;
}
return null;
}
async executeRegisterBlobTransaction(tx) {
if (!this.suiClient) {
throw new Error("no suiClient");
}
const results = await this.executeTx(tx);
if (results && results.effects && results.effects) {
const effects = results.effects;
const createdObjectIds = [];
for (const rec of effects.created) {
if (rec?.reference?.objectId) {
createdObjectIds.push(rec.reference.objectId);
}
}
const allObjects = await this.suiClient.multiGetObjects({ ids: createdObjectIds, options: { showType: true } });
if (allObjects && allObjects.length) {
for (const object of allObjects) {
if (object && object.data && object.data.type && object.data.type.indexOf("::blob::Blob") !== -1) {
return object.data.objectId;
}
}
}
}
return null;
}
async coinOfAmountToTxCoin(tx, owner, coinType, amount, addEmptyCoins = false) {
import_SuiSqlLog.default.log("composing coin of amount", coinType, amount);
const expectedAmountAsBigInt = BigInt(amount);
const coinIds = await this.coinObjectsEnoughForAmount(owner, coinType, expectedAmountAsBigInt, addEmptyCoins);
if (!coinIds || !coinIds.length) {
throw new Error("Owner: " + owner + " does not have enough coins of needed type: " + coinType);
}
import_SuiSqlLog.default.log("composing coin objects, count", coinIds.length);
if (coinIds.length == 1) {
if (coinType.indexOf("::sui::SUI") !== -1) {
const coinInput = tx.add(import_transactions.Commands.SplitCoins(tx.gas, [tx.pure.u64(expectedAmountAsBigInt)]));
return coinInput;
} else {
const coinInput = tx.add(import_transactions.Commands.SplitCoins(tx.object(coinIds[0]), [tx.pure.u64(expectedAmountAsBigInt)]));
return coinInput;
}
} else {
const coinIdToMergeIn = coinIds.shift();
if (coinIdToMergeIn) {
tx.add(import_transactions.Commands.MergeCoins(tx.object(coinIdToMergeIn), coinIds.map((id) => {
return tx.object(id);
})));
const coinInputSplet = tx.add(import_transactions.Commands.SplitCoins(tx.object(coinIdToMergeIn), [tx.pure.u64(expectedAmountAsBigInt)]));
return coinInputSplet;
}
}
throw new Error("should not happen");
}
async coinObjectsEnoughForAmount(owner, coinType, expectedAmount, addEmptyCoins = false) {
if (!this.suiClient) {
throw new Error("suiClient required");
}
const expectedAmountAsBigInt = BigInt(expectedAmount);
const coinIds = [];
const coins = [];
let result = null;
let cursor = null;
do {
result = await this.suiClient.getCoins({
owner,
coinType,
limit: 50,
cursor
});
coins.push(...result.data);
cursor = result.nextCursor;
} while (result.hasNextPage);
coins.sort((a, b) => {
return Number(b.balance) - Number(a.balance);
});
let totalAmount = BigInt(0);
for (const coin of coins) {
if (totalAmount <= expectedAmountAsBigInt) {
coinIds.push(coin.coinObjectId);
totalAmount = totalAmount + BigInt(coin.balance);
} else {
if (addEmptyCoins && BigInt(coin.balance) == 0n) {
coinIds.push(coin.coinObjectId);
}
}
}
if (totalAmount >= expectedAmountAsBigInt) {
return coinIds;
}
return null;
}
}
//# sourceMappingURL=SuiSqlBlockchain.js.map