@mysten/sui
Version:
Sui TypeScript API(Work in Progress)
603 lines (602 loc) • 24.2 kB
JavaScript
var __typeError = (msg) => {
throw TypeError(msg);
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _serializationPlugins, _buildPlugins, _intentResolvers, _inputSection, _commandSection, _availableResults, _pendingPromises, _added, _data, _Transaction_instances, fork_fn, addCommand_fn, addInput_fn, normalizeTransactionArgument_fn, resolveArgument_fn, prepareBuild_fn, runPlugins_fn, waitForPendingTasks_fn, sortCommandsAndInputs_fn;
import { fromBase64, isSerializedBcs } from "@mysten/bcs";
import { is, parse } from "valibot";
import { normalizeSuiAddress } from "../utils/sui-types.js";
import { Commands } from "./Commands.js";
import { Argument, NormalizedCallArg, ObjectRef, TransactionExpiration } from "./data/internal.js";
import { serializeV1TransactionData } from "./data/v1.js";
import { SerializedTransactionDataV2 } from "./data/v2.js";
import { Inputs } from "./Inputs.js";
import { resolveTransactionData } from "./json-rpc-resolver.js";
import { createObjectMethods } from "./object.js";
import { createPure } from "./pure.js";
import { TransactionDataBuilder } from "./TransactionData.js";
import { getIdFromCallArg } from "./utils.js";
function createTransactionResult(index, length = Infinity) {
const baseResult = { $kind: "Result", Result: index };
const nestedResults = [];
const nestedResultFor = (resultIndex) => nestedResults[resultIndex] ?? (nestedResults[resultIndex] = {
$kind: "NestedResult",
NestedResult: [index, resultIndex]
});
return new Proxy(baseResult, {
set() {
throw new Error(
"The transaction result is a proxy, and does not support setting properties directly"
);
},
// TODO: Instead of making this return a concrete argument, we should ideally
// make it reference-based (so that this gets resolved at build-time), which
// allows re-ordering transactions.
get(target, property) {
if (property in target) {
return Reflect.get(target, property);
}
if (property === Symbol.iterator) {
return function* () {
let i = 0;
while (i < length) {
yield nestedResultFor(i);
i++;
}
};
}
if (typeof property === "symbol") return;
const resultIndex = parseInt(property, 10);
if (Number.isNaN(resultIndex) || resultIndex < 0) return;
return nestedResultFor(resultIndex);
}
});
}
const TRANSACTION_BRAND = Symbol.for("@mysten/transaction");
function isTransaction(obj) {
return !!obj && typeof obj === "object" && obj[TRANSACTION_BRAND] === true;
}
const modulePluginRegistry = {
buildPlugins: /* @__PURE__ */ new Map(),
serializationPlugins: /* @__PURE__ */ new Map()
};
const TRANSACTION_REGISTRY_KEY = Symbol.for("@mysten/transaction/registry");
function getGlobalPluginRegistry() {
try {
const target = globalThis;
if (!target[TRANSACTION_REGISTRY_KEY]) {
target[TRANSACTION_REGISTRY_KEY] = modulePluginRegistry;
}
return target[TRANSACTION_REGISTRY_KEY];
} catch (e) {
return modulePluginRegistry;
}
}
const _Transaction = class _Transaction {
constructor() {
__privateAdd(this, _Transaction_instances);
__privateAdd(this, _serializationPlugins);
__privateAdd(this, _buildPlugins);
__privateAdd(this, _intentResolvers, /* @__PURE__ */ new Map());
__privateAdd(this, _inputSection, []);
__privateAdd(this, _commandSection, []);
__privateAdd(this, _availableResults, /* @__PURE__ */ new Set());
__privateAdd(this, _pendingPromises, /* @__PURE__ */ new Set());
__privateAdd(this, _added, /* @__PURE__ */ new Map());
__privateAdd(this, _data);
/**
* Add a new object input to the transaction.
*/
this.object = createObjectMethods(
(value) => {
if (typeof value === "function") {
return this.object(this.add(value));
}
if (typeof value === "object" && is(Argument, value)) {
return value;
}
const id = getIdFromCallArg(value);
const inserted = __privateGet(this, _data).inputs.find((i) => id === getIdFromCallArg(i));
if (inserted?.Object?.SharedObject && typeof value === "object" && value.Object?.SharedObject) {
inserted.Object.SharedObject.mutable = inserted.Object.SharedObject.mutable || value.Object.SharedObject.mutable;
}
return inserted ? { $kind: "Input", Input: __privateGet(this, _data).inputs.indexOf(inserted), type: "object" } : __privateMethod(this, _Transaction_instances, addInput_fn).call(this, "object", typeof value === "string" ? {
$kind: "UnresolvedObject",
UnresolvedObject: { objectId: normalizeSuiAddress(value) }
} : value);
}
);
const globalPlugins = getGlobalPluginRegistry();
__privateSet(this, _data, new TransactionDataBuilder());
__privateSet(this, _buildPlugins, [...globalPlugins.buildPlugins.values()]);
__privateSet(this, _serializationPlugins, [...globalPlugins.serializationPlugins.values()]);
}
/**
* Converts from a serialize transaction kind (built with `build({ onlyTransactionKind: true })`) to a `Transaction` class.
* Supports either a byte array, or base64-encoded bytes.
*/
static fromKind(serialized) {
const tx = new _Transaction();
__privateSet(tx, _data, TransactionDataBuilder.fromKindBytes(
typeof serialized === "string" ? fromBase64(serialized) : serialized
));
__privateSet(tx, _inputSection, __privateGet(tx, _data).inputs);
__privateSet(tx, _commandSection, __privateGet(tx, _data).commands);
return tx;
}
/**
* Converts from a serialized transaction format to a `Transaction` class.
* There are two supported serialized formats:
* - A string returned from `Transaction#serialize`. The serialized format must be compatible, or it will throw an error.
* - A byte array (or base64-encoded bytes) containing BCS transaction data.
*/
static from(transaction) {
const newTransaction = new _Transaction();
if (isTransaction(transaction)) {
__privateSet(newTransaction, _data, new TransactionDataBuilder(transaction.getData()));
} else if (typeof transaction !== "string" || !transaction.startsWith("{")) {
__privateSet(newTransaction, _data, TransactionDataBuilder.fromBytes(
typeof transaction === "string" ? fromBase64(transaction) : transaction
));
} else {
__privateSet(newTransaction, _data, TransactionDataBuilder.restore(JSON.parse(transaction)));
}
__privateSet(newTransaction, _inputSection, __privateGet(newTransaction, _data).inputs);
__privateSet(newTransaction, _commandSection, __privateGet(newTransaction, _data).commands);
return newTransaction;
}
static registerGlobalSerializationPlugin(stepOrStep, step) {
getGlobalPluginRegistry().serializationPlugins.set(
stepOrStep,
step ?? stepOrStep
);
}
static unregisterGlobalSerializationPlugin(name) {
getGlobalPluginRegistry().serializationPlugins.delete(name);
}
static registerGlobalBuildPlugin(stepOrStep, step) {
getGlobalPluginRegistry().buildPlugins.set(
stepOrStep,
step ?? stepOrStep
);
}
static unregisterGlobalBuildPlugin(name) {
getGlobalPluginRegistry().buildPlugins.delete(name);
}
addSerializationPlugin(step) {
__privateGet(this, _serializationPlugins).push(step);
}
addBuildPlugin(step) {
__privateGet(this, _buildPlugins).push(step);
}
addIntentResolver(intent, resolver) {
if (__privateGet(this, _intentResolvers).has(intent) && __privateGet(this, _intentResolvers).get(intent) !== resolver) {
throw new Error(`Intent resolver for ${intent} already exists`);
}
__privateGet(this, _intentResolvers).set(intent, resolver);
}
setSender(sender) {
__privateGet(this, _data).sender = sender;
}
/**
* Sets the sender only if it has not already been set.
* This is useful for sponsored transaction flows where the sender may not be the same as the signer address.
*/
setSenderIfNotSet(sender) {
if (!__privateGet(this, _data).sender) {
__privateGet(this, _data).sender = sender;
}
}
setExpiration(expiration) {
__privateGet(this, _data).expiration = expiration ? parse(TransactionExpiration, expiration) : null;
}
setGasPrice(price) {
__privateGet(this, _data).gasConfig.price = String(price);
}
setGasBudget(budget) {
__privateGet(this, _data).gasConfig.budget = String(budget);
}
setGasBudgetIfNotSet(budget) {
if (__privateGet(this, _data).gasData.budget == null) {
__privateGet(this, _data).gasConfig.budget = String(budget);
}
}
setGasOwner(owner) {
__privateGet(this, _data).gasConfig.owner = owner;
}
setGasPayment(payments) {
__privateGet(this, _data).gasConfig.payment = payments.map((payment) => parse(ObjectRef, payment));
}
/** @deprecated Use `getData()` instead. */
get blockData() {
return serializeV1TransactionData(__privateGet(this, _data).snapshot());
}
/** Get a snapshot of the transaction data, in JSON form: */
getData() {
return __privateGet(this, _data).snapshot();
}
// Used to brand transaction classes so that they can be identified, even between multiple copies
// of the builder.
get [TRANSACTION_BRAND]() {
return true;
}
// Temporary workaround for the wallet interface accidentally serializing transactions via postMessage
get pure() {
Object.defineProperty(this, "pure", {
enumerable: false,
value: createPure((value) => {
if (isSerializedBcs(value)) {
return __privateMethod(this, _Transaction_instances, addInput_fn).call(this, "pure", {
$kind: "Pure",
Pure: {
bytes: value.toBase64()
}
});
}
return __privateMethod(this, _Transaction_instances, addInput_fn).call(this, "pure", is(NormalizedCallArg, value) ? parse(NormalizedCallArg, value) : value instanceof Uint8Array ? Inputs.Pure(value) : { $kind: "UnresolvedPure", UnresolvedPure: { value } });
})
});
return this.pure;
}
/** Returns an argument for the gas coin, to be used in a transaction. */
get gas() {
return { $kind: "GasCoin", GasCoin: true };
}
/**
* Add a new object input to the transaction using the fully-resolved object reference.
* If you only have an object ID, use `builder.object(id)` instead.
*/
objectRef(...args) {
return this.object(Inputs.ObjectRef(...args));
}
/**
* Add a new receiving input to the transaction using the fully-resolved object reference.
* If you only have an object ID, use `builder.object(id)` instead.
*/
receivingRef(...args) {
return this.object(Inputs.ReceivingRef(...args));
}
/**
* Add a new shared object input to the transaction using the fully-resolved shared object reference.
* If you only have an object ID, use `builder.object(id)` instead.
*/
sharedObjectRef(...args) {
return this.object(Inputs.SharedObjectRef(...args));
}
add(command) {
if (typeof command === "function") {
if (__privateGet(this, _added).has(command)) {
return __privateGet(this, _added).get(command);
}
const fork = __privateMethod(this, _Transaction_instances, fork_fn).call(this);
const result = command(fork);
if (!(result && typeof result === "object" && "then" in result)) {
__privateSet(this, _availableResults, __privateGet(fork, _availableResults));
__privateGet(this, _added).set(command, result);
return result;
}
const placeholder = __privateMethod(this, _Transaction_instances, addCommand_fn).call(this, {
$kind: "$Intent",
$Intent: {
name: "AsyncTransactionThunk",
inputs: {},
data: {
result: null
}
}
});
__privateGet(this, _pendingPromises).add(
Promise.resolve(result).then((result2) => {
placeholder.$Intent.data.result = result2;
})
);
const txResult = createTransactionResult(__privateGet(this, _data).commands.length - 1);
__privateGet(this, _added).set(command, txResult);
return txResult;
} else {
__privateMethod(this, _Transaction_instances, addCommand_fn).call(this, command);
}
return createTransactionResult(__privateGet(this, _data).commands.length - 1);
}
// Method shorthands:
splitCoins(coin, amounts) {
const command = Commands.SplitCoins(
typeof coin === "string" ? this.object(coin) : __privateMethod(this, _Transaction_instances, resolveArgument_fn).call(this, coin),
amounts.map(
(amount) => typeof amount === "number" || typeof amount === "bigint" || typeof amount === "string" ? this.pure.u64(amount) : __privateMethod(this, _Transaction_instances, normalizeTransactionArgument_fn).call(this, amount)
)
);
__privateMethod(this, _Transaction_instances, addCommand_fn).call(this, command);
return createTransactionResult(__privateGet(this, _data).commands.length - 1, amounts.length);
}
mergeCoins(destination, sources) {
return this.add(
Commands.MergeCoins(
this.object(destination),
sources.map((src) => this.object(src))
)
);
}
publish({ modules, dependencies }) {
return this.add(
Commands.Publish({
modules,
dependencies
})
);
}
upgrade({
modules,
dependencies,
package: packageId,
ticket
}) {
return this.add(
Commands.Upgrade({
modules,
dependencies,
package: packageId,
ticket: this.object(ticket)
})
);
}
moveCall({
arguments: args,
...input
}) {
return this.add(
Commands.MoveCall({
...input,
arguments: args?.map((arg) => __privateMethod(this, _Transaction_instances, normalizeTransactionArgument_fn).call(this, arg))
})
);
}
transferObjects(objects, address) {
return this.add(
Commands.TransferObjects(
objects.map((obj) => this.object(obj)),
typeof address === "string" ? this.pure.address(address) : __privateMethod(this, _Transaction_instances, normalizeTransactionArgument_fn).call(this, address)
)
);
}
makeMoveVec({
type,
elements
}) {
return this.add(
Commands.MakeMoveVec({
type,
elements: elements.map((obj) => this.object(obj))
})
);
}
/**
* @deprecated Use toJSON instead.
* For synchronous serialization, you can use `getData()`
* */
serialize() {
return JSON.stringify(serializeV1TransactionData(__privateGet(this, _data).snapshot()));
}
async toJSON(options = {}) {
await this.prepareForSerialization(options);
return JSON.stringify(
parse(SerializedTransactionDataV2, __privateGet(this, _data).snapshot()),
(_key, value) => typeof value === "bigint" ? value.toString() : value,
2
);
}
/** Build the transaction to BCS bytes, and sign it with the provided keypair. */
async sign(options) {
const { signer, ...buildOptions } = options;
const bytes = await this.build(buildOptions);
return signer.signTransaction(bytes);
}
/** Build the transaction to BCS bytes. */
async build(options = {}) {
await this.prepareForSerialization(options);
await __privateMethod(this, _Transaction_instances, prepareBuild_fn).call(this, options);
return __privateGet(this, _data).build({
onlyTransactionKind: options.onlyTransactionKind
});
}
/** Derive transaction digest */
async getDigest(options = {}) {
await __privateMethod(this, _Transaction_instances, prepareBuild_fn).call(this, options);
return __privateGet(this, _data).getDigest();
}
async prepareForSerialization(options) {
await __privateMethod(this, _Transaction_instances, waitForPendingTasks_fn).call(this);
__privateMethod(this, _Transaction_instances, sortCommandsAndInputs_fn).call(this);
const intents = /* @__PURE__ */ new Set();
for (const command of __privateGet(this, _data).commands) {
if (command.$Intent) {
intents.add(command.$Intent.name);
}
}
const steps = [...__privateGet(this, _serializationPlugins)];
for (const intent of intents) {
if (options.supportedIntents?.includes(intent)) {
continue;
}
if (!__privateGet(this, _intentResolvers).has(intent)) {
throw new Error(`Missing intent resolver for ${intent}`);
}
steps.push(__privateGet(this, _intentResolvers).get(intent));
}
await __privateMethod(this, _Transaction_instances, runPlugins_fn).call(this, steps, options);
}
};
_serializationPlugins = new WeakMap();
_buildPlugins = new WeakMap();
_intentResolvers = new WeakMap();
_inputSection = new WeakMap();
_commandSection = new WeakMap();
_availableResults = new WeakMap();
_pendingPromises = new WeakMap();
_added = new WeakMap();
_data = new WeakMap();
_Transaction_instances = new WeakSet();
fork_fn = function() {
const fork = new _Transaction();
__privateSet(fork, _data, __privateGet(this, _data));
__privateSet(fork, _serializationPlugins, __privateGet(this, _serializationPlugins));
__privateSet(fork, _buildPlugins, __privateGet(this, _buildPlugins));
__privateSet(fork, _intentResolvers, __privateGet(this, _intentResolvers));
__privateSet(fork, _pendingPromises, __privateGet(this, _pendingPromises));
__privateSet(fork, _availableResults, new Set(__privateGet(this, _availableResults)));
__privateSet(fork, _added, __privateGet(this, _added));
__privateGet(this, _inputSection).push(__privateGet(fork, _inputSection));
__privateGet(this, _commandSection).push(__privateGet(fork, _commandSection));
return fork;
};
addCommand_fn = function(command) {
const resultIndex = __privateGet(this, _data).commands.length;
__privateGet(this, _commandSection).push(command);
__privateGet(this, _availableResults).add(resultIndex);
__privateGet(this, _data).commands.push(command);
__privateGet(this, _data).mapCommandArguments(resultIndex, (arg) => {
if (arg.$kind === "Result" && !__privateGet(this, _availableResults).has(arg.Result)) {
throw new Error(
`Result { Result: ${arg.Result} } is not available to use the current transaction`
);
}
if (arg.$kind === "NestedResult" && !__privateGet(this, _availableResults).has(arg.NestedResult[0])) {
throw new Error(
`Result { NestedResult: [${arg.NestedResult[0]}, ${arg.NestedResult[1]}] } is not available to use the current transaction`
);
}
if (arg.$kind === "Input" && arg.Input >= __privateGet(this, _data).inputs.length) {
throw new Error(
`Input { Input: ${arg.Input} } references an input that does not exist in the current transaction`
);
}
return arg;
});
return command;
};
addInput_fn = function(type, input) {
__privateGet(this, _inputSection).push(input);
return __privateGet(this, _data).addInput(type, input);
};
normalizeTransactionArgument_fn = function(arg) {
if (isSerializedBcs(arg)) {
return this.pure(arg);
}
return __privateMethod(this, _Transaction_instances, resolveArgument_fn).call(this, arg);
};
resolveArgument_fn = function(arg) {
if (typeof arg === "function") {
const resolved = this.add(arg);
if (typeof resolved === "function") {
return __privateMethod(this, _Transaction_instances, resolveArgument_fn).call(this, resolved);
}
return parse(Argument, resolved);
}
return parse(Argument, arg);
};
prepareBuild_fn = async function(options) {
if (!options.onlyTransactionKind && !__privateGet(this, _data).sender) {
throw new Error("Missing transaction sender");
}
await __privateMethod(this, _Transaction_instances, runPlugins_fn).call(this, [...__privateGet(this, _buildPlugins), resolveTransactionData], options);
};
runPlugins_fn = async function(plugins, options) {
const createNext = (i) => {
if (i >= plugins.length) {
return () => {
};
}
const plugin = plugins[i];
return async () => {
const next = createNext(i + 1);
let calledNext = false;
let nextResolved = false;
await plugin(__privateGet(this, _data), options, async () => {
if (calledNext) {
throw new Error(`next() was call multiple times in TransactionPlugin ${i}`);
}
calledNext = true;
await next();
nextResolved = true;
});
if (!calledNext) {
throw new Error(`next() was not called in TransactionPlugin ${i}`);
}
if (!nextResolved) {
throw new Error(`next() was not awaited in TransactionPlugin ${i}`);
}
};
};
await createNext(0)();
__privateSet(this, _inputSection, __privateGet(this, _data).inputs);
__privateSet(this, _commandSection, __privateGet(this, _data).commands);
};
waitForPendingTasks_fn = async function() {
while (__privateGet(this, _pendingPromises).size > 0) {
const newPromise = Promise.all(__privateGet(this, _pendingPromises));
__privateGet(this, _pendingPromises).clear();
__privateGet(this, _pendingPromises).add(newPromise);
await newPromise;
__privateGet(this, _pendingPromises).delete(newPromise);
}
};
sortCommandsAndInputs_fn = function() {
const unorderedCommands = __privateGet(this, _data).commands;
const unorderedInputs = __privateGet(this, _data).inputs;
const orderedCommands = __privateGet(this, _commandSection).flat(Infinity);
const orderedInputs = __privateGet(this, _inputSection).flat(Infinity);
if (orderedCommands.length !== unorderedCommands.length) {
throw new Error("Unexpected number of commands found in transaction data");
}
if (orderedInputs.length !== unorderedInputs.length) {
throw new Error("Unexpected number of inputs found in transaction data");
}
const filteredCommands = orderedCommands.filter(
(cmd) => cmd.$Intent?.name !== "AsyncTransactionThunk"
);
__privateGet(this, _data).commands = filteredCommands;
__privateGet(this, _data).inputs = orderedInputs;
__privateSet(this, _commandSection, filteredCommands);
__privateSet(this, _inputSection, orderedInputs);
function getOriginalIndex(index) {
const command = unorderedCommands[index];
if (command.$Intent?.name === "AsyncTransactionThunk") {
const result = command.$Intent.data.result;
if (result == null) {
throw new Error("AsyncTransactionThunk has not been resolved");
}
return getOriginalIndex(result.Result);
}
const updated = filteredCommands.indexOf(command);
if (updated === -1) {
throw new Error("Unable to find original index for command");
}
return updated;
}
__privateGet(this, _data).mapArguments((arg) => {
if (arg.$kind === "Input") {
const updated = orderedInputs.indexOf(unorderedInputs[arg.Input]);
if (updated === -1) {
throw new Error("Input has not been resolved");
}
return { ...arg, Input: updated };
} else if (arg.$kind === "Result") {
const updated = getOriginalIndex(arg.Result);
return { ...arg, Result: updated };
} else if (arg.$kind === "NestedResult") {
const updated = getOriginalIndex(arg.NestedResult[0]);
return { ...arg, NestedResult: [updated, arg.NestedResult[1]] };
}
return arg;
});
};
let Transaction = _Transaction;
export {
Transaction,
isTransaction
};
//# sourceMappingURL=Transaction.js.map