@0xobelisk/rooch-client
Version:
Tookit for interacting with rooch move framework
474 lines (428 loc) • 13 kB
text/typescript
import {
getRoochNodeUrl,
Args,
TypeTag,
AnnotatedFunctionResultView,
Secp256k1Keypair,
ExecuteTransactionResponseView,
ModuleABIView,
BalanceInfoView,
Transaction,
GetStatesParams,
ObjectStateView,
ListStatesParams,
PaginatedStateKVViews,
GetEventsByEventHandleParams,
PaginatedEventViews,
address,
TypeArgs,
} from '@roochnetwork/rooch-sdk';
import { RoochAccountManager } from './libs/roochAccountManager';
import { RoochInteractor } from './libs/roochInteractor';
import { RoochContractFactory } from './libs/roochContractFactory';
import {
DubheParams,
DerivePathParams,
ContractQuery,
ContractTx,
MapModuleFuncQuery,
MapModuleFuncTx,
MoveModuleFuncType,
} from './types';
export function isUndefined(value?: unknown): value is undefined {
return value === undefined;
}
export function withMeta<T extends { meta: MoveModuleFuncType }>(
meta: MoveModuleFuncType,
creator: Omit<T, 'meta'>
): T {
(creator as T).meta = meta;
return creator as T;
}
function createQuery(
meta: MoveModuleFuncType,
fn: (
params?: Args[],
typeArguments?: TypeTag[]
) => Promise<AnnotatedFunctionResultView>
): ContractQuery {
return withMeta(
meta,
async ({
params,
typeArguments,
}: {
params?: Args[];
typeArguments?: TypeTag[];
} = {}): Promise<AnnotatedFunctionResultView> => {
const result = await fn(params, typeArguments);
return result;
}
);
}
function createTx(
meta: MoveModuleFuncType,
fn: (
tx: Transaction,
signer?: Secp256k1Keypair,
params?: Args[],
typeArguments?: TypeTag[],
isRaw?: boolean
) => Promise<ExecuteTransactionResponseView>
): ContractTx {
return withMeta(
meta,
async ({
tx,
signer,
params,
typeArguments,
isRaw,
}: {
tx: Transaction;
signer?: Secp256k1Keypair;
params?: Args[];
typeArguments?: TypeTag[];
isRaw?: boolean;
}): Promise<ExecuteTransactionResponseView> => {
// const result = await fn(signer, params, typeArguments, isRaw);
const result = await fn(tx, signer, params, typeArguments, isRaw);
return result;
}
);
}
/**
* @class Dubhe
* @description This class is used to aggregate the tools that used to interact with SUI network.
*/
export class Dubhe {
public accountManager: RoochAccountManager;
public roochInteractor: RoochInteractor;
public contractFactory: RoochContractFactory;
public packageId: string | undefined;
public metadata: ModuleABIView[] | undefined;
readonly #query: MapModuleFuncQuery = {};
readonly #tx: MapModuleFuncTx = {};
/**
* Support the following ways to init the DubheClient:
* 1. mnemonics
* 2. secretKey (base64 or hex)
* If none of them is provided, will generate a random mnemonics with 24 words.
*
* @param mnemonics, 12 or 24 mnemonics words, separated by space
* @param secretKey, base64 or hex string, when mnemonics is provided, secretKey will be ignored
* @param networkType, 'testnet' | 'mainnet' | 'devnet' | 'localnet', default is 'devnet'
* @param fullnodeUrl, the fullnode url, default is the preconfig fullnode url for the given network type
* @param packageId
*/
constructor({
mnemonics,
secretKey,
networkType,
fullnodeUrls,
packageId,
metadata,
}: DubheParams = {}) {
// Init the account manager
this.accountManager = new RoochAccountManager({ mnemonics, secretKey });
// Init the rpc provider
fullnodeUrls = fullnodeUrls || [getRoochNodeUrl(networkType ?? 'mainnet')];
this.roochInteractor = new RoochInteractor(fullnodeUrls);
this.packageId = packageId;
if (metadata !== undefined) {
this.metadata = metadata as ModuleABIView[];
Object.values(metadata as ModuleABIView[]).forEach((metadataRes) => {
let contractAddress = metadataRes.address;
let moduleName = metadataRes.name;
Object.values(metadataRes.functions).forEach((value) => {
const meta: MoveModuleFuncType = {
contractAddress,
moduleName,
funcName: value.name,
isEntry: value.is_entry,
typeParams: value.type_params,
params: value.params,
return: value.return,
};
// if (value.is_view) {
if (isUndefined(this.#query[moduleName])) {
this.#query[moduleName] = {};
}
if (isUndefined(this.#query[moduleName][value.name])) {
this.#query[moduleName][value.name] = createQuery(
meta,
(p, type_p) => this.#read(meta, p, type_p)
);
}
// }
// if (value.is_entry) {
if (isUndefined(this.#tx[moduleName])) {
this.#tx[moduleName] = {};
}
if (isUndefined(this.#tx[moduleName][value.name])) {
this.#tx[moduleName][value.name] = createTx(
meta,
(tx, s, p, type_p, isRaw) =>
this.#exec(meta, tx, s, p, type_p, isRaw)
);
}
// }
});
});
}
this.contractFactory = new RoochContractFactory({
packageId,
metadata,
});
}
public get query(): MapModuleFuncQuery {
return this.#query;
}
public get tx(): MapModuleFuncTx {
return this.#tx;
}
#exec = async (
meta: MoveModuleFuncType,
tx: Transaction,
signer?: Secp256k1Keypair,
params?: Args[],
typeArguments?: TypeTag[],
isRaw?: boolean
) => {
if (isRaw === true) {
return tx.callFunction({
target: `${this.contractFactory.packageId}::${meta.moduleName}::${meta.funcName}`,
args: params,
typeArgs: typeArguments,
});
}
tx.callFunction({
target: `${this.contractFactory.packageId}::${meta.moduleName}::${meta.funcName}`,
args: params,
typeArgs: typeArguments,
});
return await this.signAndExecuteTransaction(tx, signer);
};
#read = async (
meta: MoveModuleFuncType,
params?: Args[],
typeArguments?: TypeTag[]
) => {
return this.roochInteractor.executeViewFunction(
meta.contractAddress,
meta.moduleName,
meta.funcName,
typeArguments,
params
);
};
/**
* if derivePathParams is not provided or mnemonics is empty, it will return the keypair.
* else:
* it will generate signer from the mnemonic with the given derivePathParams.
* @param derivePathParams, such as { accountIndex: 2, isExternal: false, addressIndex: 10 }, comply with the BIP44 standard
*/
getKeypair(derivePathParams?: DerivePathParams) {
return this.accountManager.getKeyPair(derivePathParams);
}
/**
* if derivePathParams is not provided or mnemonics is empty, it will return the currentSigner.
* else:
* it will generate signer from the mnemonic with the given derivePathParams.
* @param derivePathParams, such as { accountIndex: 2, isExternal: false, addressIndex: 10 }, comply with the BIP44 standard
*/
getSigner(derivePathParams?: DerivePathParams) {
const keyPair = this.accountManager.getKeyPair(derivePathParams);
return keyPair;
}
/**
* @description Switch the current account with the given derivePathParams
* @param derivePathParams, such as { accountIndex: 2, isExternal: false, addressIndex: 10 }, comply with the BIP44 standard
*/
switchAccount(derivePathParams: DerivePathParams) {
this.accountManager.switchAccount(derivePathParams);
}
/**
* @description Get the address of the account for the given derivePathParams
* @param derivePathParams, such as { accountIndex: 2, isExternal: false, addressIndex: 10 }, comply with the BIP44 standard
*/
getBech32Address(derivePathParams?: DerivePathParams) {
return this.accountManager.getAddress(derivePathParams).toBech32Address();
}
getRoochAddress(derivePathParams?: DerivePathParams) {
return this.accountManager.getAddress(derivePathParams);
}
getHexAddress(derivePathParams?: DerivePathParams) {
return this.accountManager.getAddress(derivePathParams).toHexAddress();
}
getBitcoinAddress(derivePathParams?: DerivePathParams) {
return this.accountManager.getBitcoinAddress(derivePathParams);
}
currentAddress() {
return this.accountManager.currentAddress;
}
client() {
return this.roochInteractor.currentClient;
}
getPackageId() {
return this.contractFactory.packageId;
}
getMetadata(): ModuleABIView[] | undefined {
return this.contractFactory.metadata;
}
getNetwork() {
return this.roochInteractor.network;
}
getFullNodeUrl() {
return this.roochInteractor.currentFullNode;
}
async getRpcApiVersion(): Promise<string | undefined> {
return this.roochInteractor.getRpcApiVersion();
}
async getChainId(): Promise<bigint> {
return this.roochInteractor.getChainId();
}
async getBalance(
accountAddress?: string,
coinType?: string,
outputOnly?: boolean
): Promise<BalanceInfoView | string> {
try {
if (accountAddress === undefined) {
accountAddress = this.getBech32Address();
}
if (coinType === undefined) {
coinType = '0x3::gas_coin::RGas';
}
const resource = await this.roochInteractor.getBalance(
accountAddress,
coinType
);
return outputOnly ? resource : resource.balance;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
throw new Error(
`Failed to get ${coinType} balance for address ${accountAddress}: ${errorMessage}`
);
}
}
async signAndExecuteTransaction(
transaction: Transaction,
signer?: Secp256k1Keypair,
derivePathParams?: DerivePathParams
) {
if (signer === undefined) {
signer = this.getSigner(derivePathParams);
}
return this.roochInteractor.signAndExecuteTransaction(transaction, signer, {
withOutput: true,
});
}
async signAndSendTransaction(
tx: Transaction,
derivePathParams?: DerivePathParams
) {
const sender = this.getSigner(derivePathParams);
return this.signAndExecuteTransaction(tx, sender);
}
async createSession(
sessionArgs: {
appName: string;
appUrl: string;
scopes: string[];
},
signer?: Secp256k1Keypair,
derivePathParams?: DerivePathParams
) {
if (signer === undefined) {
signer = this.getSigner(derivePathParams);
}
return this.roochInteractor.createSession(sessionArgs, signer);
}
async moveCall(callParams: {
target: string;
params?: Args[];
typeArguments?: TypeTag[];
signer?: Secp256k1Keypair;
derivePathParams?: DerivePathParams;
}) {
const {
target,
params = [],
typeArguments = [],
signer,
derivePathParams,
} = callParams;
const effectiveSigner = signer ?? this.getSigner(derivePathParams);
console.log('effectiveSigner', effectiveSigner.getRoochAddress().toStr());
const tx = new Transaction();
tx.callFunction({
target,
args: params,
typeArgs: typeArguments,
});
return this.signAndExecuteTransaction(
tx,
effectiveSigner,
derivePathParams
);
}
async publishPackage(callParams: {
packageBytes: Uint8Array;
signer?: Secp256k1Keypair;
derivePathParams?: DerivePathParams;
}) {
const { packageBytes, signer, derivePathParams } = callParams;
return this.moveCall({
target: `0x2::module_store::publish_package_entry`,
params: [Args.vec('u8', Array.from(packageBytes))],
signer,
derivePathParams,
});
}
async getStates(params: GetStatesParams): Promise<ObjectStateView[]> {
return this.roochInteractor.getStates(params);
}
async listStates(params: ListStatesParams): Promise<PaginatedStateKVViews> {
return this.roochInteractor.listStates(params);
}
async getEvents(
input: GetEventsByEventHandleParams
): Promise<PaginatedEventViews> {
return this.roochInteractor.getEvents(input);
}
async transfer(
input: {
recipient: address;
amount: number | bigint;
coinType: TypeArgs;
},
signer?: Secp256k1Keypair,
derivePathParams?: DerivePathParams
): Promise<ExecuteTransactionResponseView> {
if (signer === undefined) {
signer = this.getSigner(derivePathParams);
}
return this.roochInteractor.transfer({
...input,
signer,
});
}
async transferObject(
input: {
recipient: address;
objectId: string;
objectType: TypeArgs;
},
signer?: Secp256k1Keypair,
derivePathParams?: DerivePathParams
): Promise<ExecuteTransactionResponseView> {
if (signer === undefined) {
signer = this.getSigner(derivePathParams);
}
return this.roochInteractor.transferObject({
...input,
signer,
});
}
}