@yubing744/rooch-sdk
Version:
285 lines (284 loc) • 7.97 kB
JavaScript
import { DEFAULT_MAX_GAS_AMOUNT } from "../constants";
import { PrivateKeyAuth } from "../auth";
import { BcsSerializer } from "../types/bcs";
import {
RoochTransaction,
RoochTransactionData,
AccountAddress as BCSAccountAddress,
Authenticator
} from "../generated/runtime/rooch_types/mod";
import {
encodeArg,
encodeFunctionCall,
addressToListTuple,
uint8Array2SeqNumber,
addressToSeqNumber,
encodeStructTypeTag
} from "../utils";
import { Ed25519Keypair } from "../utils/keypairs";
const SCOPE_LENGTH = 3;
const SCOPE_MODULE_ADDRESSS = 0;
const SCOPE_MODULE_NAMES = 1;
const SCOPE_FUNCTION_NAMES = 2;
class Account {
constructor(client, address, authorizer) {
this.client = client;
this.address = address;
this.authorizer = authorizer;
}
async makeAuth(tsData) {
const payload = (() => {
const se = new BcsSerializer();
tsData.serialize(se);
return se.getBytes();
})();
return this.authorizer.auth(payload);
}
parseStateToSessionKey(data) {
const result = new Array();
for (const state of data.data) {
const moveValue = state?.state.decoded_value;
if (moveValue) {
const val = moveValue.value;
result.push({
authentication_key: val.authentication_key,
scopes: this.parseScopes(val.scopes),
create_time: parseInt(val.create_time),
last_active_time: parseInt(val.last_active_time),
max_inactive_interval: parseInt(val.max_inactive_interval)
});
}
}
return result;
}
parseScopes(data) {
const result = new Array();
for (const scope of data) {
result.push(`${scope.module_name}::${scope.module_address}::${scope.function_name}`);
}
return result;
}
async getSequenceNumber() {
const resp = await this.client.executeViewFunction({
funcId: "0x3::account::sequence_number",
tyArgs: [],
args: [
{
type: "Address",
value: this.address
}
]
});
if (resp && resp.return_values) {
return resp.return_values[0].decoded_value;
}
return 0;
}
/**
* Get account address
*/
getAddress() {
return this.address;
}
/**
* Run move function by current account
*
* @param funcId FunctionId the function like '0x3::empty::empty'
* @param tyArgs Generic parameter list
* @param args parameter list
* @param opts Call option
*/
async runFunction(funcId, tyArgs, args, opts) {
const number = await this.getSequenceNumber();
const bcsArgs = args.map((arg) => encodeArg(arg));
const scriptFunction = encodeFunctionCall(funcId, tyArgs, bcsArgs);
const txData = new RoochTransactionData(
new BCSAccountAddress(addressToListTuple(this.address)),
BigInt(number),
BigInt(this.client.getChainId()),
BigInt(opts.maxGasAmount ?? DEFAULT_MAX_GAS_AMOUNT),
scriptFunction
);
const authResult = await this.makeAuth(txData);
const auth = new Authenticator(
BigInt(authResult.scheme),
uint8Array2SeqNumber(authResult.payload)
);
const ts = new RoochTransaction(txData, auth);
const payload = (() => {
const se = new BcsSerializer();
ts.serialize(se);
return se.getBytes();
})();
return this.client.sendRawTransaction(payload);
}
async createSessionAccount(scope, maxInactiveInterval, opts) {
const kp = Ed25519Keypair.generate();
await this.registerSessionKey(
kp.getPublicKey().toRoochAddress(),
scope,
maxInactiveInterval,
opts
);
const auth = new PrivateKeyAuth(kp);
return new Account(this.client, this.address, auth);
}
async registerSessionKey(authKey, scopes, maxInactiveInterval, opts) {
const [scopeModuleAddresss, scopeModuleNames, scopeFunctionNames] = scopes.map((scope) => {
const parts = scope.split("::");
if (parts.length !== SCOPE_LENGTH) {
throw new Error("invalid scope");
}
const scopeModuleAddress = parts[SCOPE_MODULE_NAMES];
const scopeModuleName = parts[SCOPE_MODULE_ADDRESSS];
const scopeFunctionName = parts[SCOPE_FUNCTION_NAMES];
return [scopeModuleAddress, scopeModuleName, scopeFunctionName];
}).reduce(
(acc, val) => {
acc[0].push(val[SCOPE_MODULE_NAMES]);
acc[1].push(val[SCOPE_MODULE_ADDRESSS]);
acc[2].push(val[SCOPE_FUNCTION_NAMES]);
return acc;
},
[[], [], []]
);
await this.runFunction(
"0x3::session_key::create_session_key_with_multi_scope_entry",
[],
[
{
type: { Vector: "U8" },
value: addressToSeqNumber(authKey)
},
{
type: { Vector: "Address" },
value: scopeModuleAddresss
},
{
type: { Vector: "Ascii" },
value: scopeModuleNames
},
{
type: { Vector: "Ascii" },
value: scopeFunctionNames
},
{
type: "U64",
value: BigInt(maxInactiveInterval)
}
],
opts || {
maxGasAmount: 1e8
}
);
}
/**
* Remove session key
*
* @param authKey
* @param opts
*/
async removeSessionKey(authKey, opts) {
return await this.runFunction(
"0x3::session_key::remove_session_key_entry",
[],
[
{
type: { Vector: "U8" },
value: addressToSeqNumber(authKey)
}
],
opts || {
maxGasAmount: 1e8
}
);
}
/**
* Query account's sessionKey
*
* @param cursor The page cursor
* @param limit The page limit
*/
async querySessionKeys(cursor, limit) {
const accessPath = `/resource/${this.address}/0x3::session_key::SessionKeys`;
const state = await this.client.getStates(accessPath);
if (state) {
const stateView = state;
const tableId = stateView[0].value;
const accessPath2 = `/table/${tableId}`;
const pageView = await this.client.listStates({
accessPath: accessPath2,
cursor,
limit
});
return {
data: this.parseStateToSessionKey(pageView),
nextCursor: pageView.next_cursor,
hasNextPage: pageView.has_next_page
};
}
throw new Error("not found state");
}
/**
* Check session key whether expired
*
* @param authKey the auth key
*/
async isSessionKeyExpired(authKey) {
const result = await this.client.executeViewFunction({
funcId: "0x3::session_key::is_expired_session_key",
tyArgs: [],
args: [
{
type: "Address",
value: this.address
},
{
type: { Vector: "U8" },
value: addressToSeqNumber(authKey)
}
]
});
if (result && result.vm_status !== "Executed") {
throw new Error("view 0x3::session_key::is_expired_session_key fail");
}
return result.return_values[0].decoded_value;
}
async gasCoinBalance() {
const result = await this.client.executeViewFunction({
funcId: "0x3::gas_coin::balance",
tyArgs: [],
args: [
{
type: "Address",
value: this.getAddress()
}
]
});
if (result && result.vm_status !== "Executed") {
throw new Error("view 0x3::gas_coin::balance fail");
}
return BigInt(result.return_values[0].decoded_value);
}
async coinBalance(coinType) {
const structType = encodeStructTypeTag(coinType);
const result = await this.client.executeViewFunction({
funcId: "0x3::account_coin_store::balance",
tyArgs: [structType],
args: [
{
type: "Address",
value: this.getAddress()
}
]
});
if (result && result.vm_status !== "Executed") {
throw new Error("view 0x3::account_coin_store::balance fail");
}
return BigInt(result.return_values[0].decoded_value);
}
}
export {
Account
};
//# sourceMappingURL=account.js.map