@velas/account-agent
Version:
sdk
323 lines (260 loc) • 14.6 kB
JavaScript
import { Keccak } from 'sha3';
import { uniqueNamesGenerator, adjectives, animals } from 'unique-names-generator';
import { SolidInstruction, SignerType, RequestedOpt } from './instructions'
import ErrorHendler from './error';
import ScopesConstructor from './scopes';
import { Vaccount, OperationalStruct, Initialized } from './account-schema'
const errorHendler = new ErrorHendler('AccountConstructor');
function AccountConstructor(options = {}) {
if (!options.connection) throw new Error('connection option is required')
if (!options.account_contract) throw new Error('account_contract option is required')
if (!options.PublicKey) throw new Error('PublicKey module is required')
this.connection = options.connection;
this.PublicKey = options.PublicKey;
this.sc = new ScopesConstructor({ account_contract: options.account_contract });
};
AccountConstructor.prototype.accountEvmAddressToReadable = function(address) {
if (typeof address !== 'string') throw new Error('address should be string');
return uniqueNamesGenerator({
style: 'capital',
separator: ' ',
seed: parseInt(address.slice(-8), 16),
dictionaries: [adjectives, adjectives, animals],
});
};
AccountConstructor.prototype.accountPublicKeyToEVMAddress = function(accountPublicKey) {
accountPublicKey = typeof accountPublicKey === 'string' ? new this.PublicKey(accountPublicKey) : accountPublicKey;
const hash = new Keccak(256);
const accountPublicKeyHash = hash.update(accountPublicKey.toBuffer()).digest();
return "0x" + Buffer.concat([
Buffer.from([0xAC, 0xC0]),
accountPublicKeyHash.slice(14, 32)
]).toString("hex");
};
AccountConstructor.prototype.getStorageAddress = async function(storageType, accountPublicKey, version) {
const publicKey = await this.PublicKey.findProgramAddress([
accountPublicKey.toBuffer(),
Buffer.from(Buffer.concat([Buffer.from(storageType), Buffer.from(Uint16Array.of(version).buffer) ])),
], new this.PublicKey(this.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS));
return publicKey[0];
};
AccountConstructor.prototype.toVelasAccountScopes = function(scopes = []) {
scopes = scopes.reduce((result, item) => {
if (item.split(':')[0] === this.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS) {
result.push(item.split(':')[1]);
};
return result;
},[]);
return Uint8Array.of(...scopes);
};
AccountConstructor.prototype.getAccountPrograms = async function(accountData) {
const programsStorageCurrentIndex = accountData.storage.programs_current_index;
const programsStorageAddress = await this.getStorageAddress('programs', accountData.public_key, programsStorageCurrentIndex);
const programsStorage = await this.connection.getAccountInfo(programsStorageAddress);
const buff = programsStorage ? programsStorage.data : [];
return this.sc.chunkArray(buff, 33).reduce((acc, curr, i) => {
if (i === 0) acc = [];
const publickKey = new this.PublicKey(curr);
const publickKeyBase58 = publickKey.toBase58();
acc.push(publickKeyBase58);
return acc;
}, {});
};
AccountConstructor.prototype.getParsedOperationalStorage = async function(operationalStorageNonce, vaccountAddress) {
const operationalsStorageAddress = await this.getStorageAddress('operationals', vaccountAddress, operationalStorageNonce);
const operationalsStorageInfo = await this.connection.getAccountInfo(operationalsStorageAddress);
const operationals = [];
if (operationalsStorageInfo) {
const number_operational = operationalsStorageInfo.data.length / OperationalStruct.len(); // TO DO: check
for (let i = 0; i < number_operational; i++) {
const start = i * OperationalStruct.len();
operationals.push(OperationalStruct.decode(operationalsStorageInfo.data.slice(start, start + OperationalStruct.len())));
};
};
return operationals;
};
AccountConstructor.prototype.toWhiteList = async function(scopes = [], accountData) {
const programsStorageCurrentIndex = accountData.storage.programs_current_index;
const programsStorageAddress = await this.getStorageAddress('programs', accountData.public_key, programsStorageCurrentIndex)
const programsStorage = await this.connection.getAccountInfo(programsStorageAddress);
const programsStorageData = programsStorage ? programsStorage.data : [];
const programsStorageDataSplited = this.sc.chunkArray(programsStorageData, 33)
const programsInScopes = [];
scopes.filter(item => {
if (this.sc.PERMITTED_SCOPES.includes(item)) return false;
if (item.split(':')[0] === this.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS) return false;
return true;
}).forEach(item => {
programsInScopes.push(new this.PublicKey(item.split(':')[0]));
});
let whitelistProgramsIndexes = [];
let whitelistPrograms = []
let uniqueProgramsInScopes = [...new Set(programsInScopes)];
for (const [uniqueProgramIndex, program] of uniqueProgramsInScopes.entries()) {
let index = undefined;
for (const i in programsStorageDataSplited) {
const address = new this.PublicKey(programsStorageDataSplited[i]);
if (address.toBase58() === program.toBase58()) index = i;
};
if (index) {
whitelistProgramsIndexes.push(index);
} else {
whitelistProgramsIndexes.push(programsStorageDataSplited.length + uniqueProgramIndex);
whitelistPrograms.push({
pubkey: program,
current: await this.getStorageAddress('programs', accountData.public_key, programsStorageCurrentIndex),
//next: await this.getStorageAddress('programs', accountData.public_key, programsStorageCurrentIndex + whitelistPrograms.length + 1),
current_index: programsStorageCurrentIndex + whitelistPrograms.length,
next_index: programsStorageCurrentIndex + whitelistPrograms.length + 1
});
};
};
return {
scopes: this.toVelasAccountScopes(scopes),
whitelistPrograms,
whitelistProgramsIndexes,
};
};
AccountConstructor.prototype.removeOperationalData = async function(accountData, publicKeyOperationalToRemove) {
const external_programs_indices = this.sc.bitsToScopes(accountData.operational_keys[publicKeyOperationalToRemove].external_programs_indices, 256);
return {
storage: {
programs_current: await this.getStorageAddress('programs', accountData.public_key, accountData.storage.programs_current_index),
programPermissionsToRemove: external_programs_indices.map(item => {
return item;
})
},
};
};
AccountConstructor.prototype.executeTransactionData = async function(accountData) {
return {
storage: {
programs_current: await this.getStorageAddress('programs', accountData.public_key, accountData.storage.programs_current_index),
},
};
};
AccountConstructor.prototype.addOperationalData = async function(accountData, new_op_key_params, signerType) {
const isMasterKey = new_op_key_params.scopes.length ? 0 : 1;
const {
scopes,
whitelistPrograms,
whitelistProgramsIndexes,
} = await this.toWhiteList(new_op_key_params.scopes, accountData);
const operationalKeyData = {
signerType: signerType || SignerType.owner(),
requestedOption: new RequestedOpt({
agentType: Buffer.from(new_op_key_params.agentType).slice(0,32),
scopes,
whitelistProgramsIndexes,
whitelistTokensIndices: new Uint8Array,
isMasterKey,
})
};
if(new_op_key_params.extend){
const operationalToExtendIndex = Object.keys(accountData.operational_keys).indexOf(new_op_key_params.pubkey.toBase58())
operationalKeyData.operationalToExtendIndex = operationalToExtendIndex
}
let lamports = 0;
if (accountData.ephemeral ) {
lamports = lamports + await this.connection.getMinimumBalanceForRentExemption(135) // account storage
lamports = lamports + await this.connection.getMinimumBalanceForRentExemption(134) // operational storage
} else {
lamports = lamports + (await this.connection.getMinimumBalanceForRentExemption(134 + 134) - await this.connection.getMinimumBalanceForRentExemption(134));
};
if (whitelistPrograms.length && accountData.storage.programs_current_index === 1) { // programs storage
lamports = lamports + await this.connection.getMinimumBalanceForRentExemption(33 * whitelistPrograms.length);
} else if (whitelistPrograms.length && accountData.storage.programs_current_index !== 1) {
const cur_programs_len = accountData.storage.programs_current_index - 1;
const new_programs_len = cur_programs_len + whitelistPrograms.length;
const cur_lamports = await this.connection.getMinimumBalanceForRentExemption(33 * cur_programs_len);
const new_lamports = await this.connection.getMinimumBalanceForRentExemption(33 * new_programs_len);
const need_to_add = new_lamports - cur_lamports;
lamports = lamports + need_to_add;
};
// TO DO: add lamports for tokens
return {
storage: {
whitelistPrograms,
rent: lamports,
addOperationalEncoded: SolidInstruction.addOperational(operationalKeyData).encode(),
extendOperationalScopesEncoded: SolidInstruction.extendOperational(operationalKeyData).encode(),
addProgramEncoded: SolidInstruction.addProgram(operationalKeyData).encode(),
programs_current: await this.getStorageAddress('programs', accountData.public_key, accountData.storage.programs_current_index),
programs_next: await this.getStorageAddress('programs', accountData.public_key, accountData.storage.programs_current_index + whitelistPrograms.length),
tokens_current: await this.getStorageAddress('tokens', accountData.public_key, accountData.storage.tokens_current_index),
tokens_next: await this.getStorageAddress('tokens', accountData.public_key, accountData.storage.tokens_current_index),
},
};
};
AccountConstructor.prototype.getAccountData = async function(accountPublicKey, ephemeral = false, merge = false) {
let account = await this.connection.getAccountInfo(accountPublicKey);
if ( !account && !ephemeral ) return undefined;
const parsed = {
public_key: accountPublicKey,
account_key: accountPublicKey.toBase58(),
account_key_evm: this.accountPublicKeyToEVMAddress(accountPublicKey),
instruction: {
initializeEncoded: SolidInstruction.initialize().encode(),
addOwnerEncoded: SolidInstruction.addOwner().encode(),
replaceOwnerEncoded: SolidInstruction.replaceOwner().encode(),
},
storage: {
storage_current: await this.getStorageAddress('operationals', accountPublicKey, 1),
// storage_next: await this.getStorageAddress('operationals', accountPublicKey, 2),
programs_current_index: 1,
tokens_current_index: 1,
},
};
const accountOwner = account ? account.owner.toBase58() : this.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS;
if ((!account || accountOwner === this.sc.SYSTEM_PROGRAM_ADDRESS) && ephemeral ) {
parsed.ephemeral = true;
return parsed;
} else if(accountOwner === this.sc.SYSTEM_PROGRAM_ADDRESS && !ephemeral ) {
return undefined;
};
if (account) {
// Check if account was preinitialized
const usefulData = account.data.filter(i => i !== 0);
if (!usefulData.length) parsed.ephemeral = true;
const info = Vaccount.decode(account.data);
account = {
value: { data: { parsed: { info }}},
owner: account.owner,
};
account.value.data.parsed.info.owners = this.sc.chunkArray(account.value.data.parsed.info.owners, 32);
};
try {
parsed.storage = {
storage_current: await this.getStorageAddress('operationals', accountPublicKey, account.value.data.parsed.info.operational_storage_nonce),
// storage_next: await this.getStorageAddress('operationals', accountPublicKey, merge ? account.value.data.parsed.info.operational_storage_nonce - 1 : account.value.data.parsed.info.operational_storage_nonce + 1),
programs_current_index: account.value.data.parsed.info.programs_storage_nonce,
tokens_current_index: account.value.data.parsed.info.token_storage_nonce,
};
} catch (error) { errorHendler.error("storage processing error", error) };
const storage = await this.getParsedOperationalStorage(account.value.data.parsed.info.operational_storage_nonce, accountPublicKey );
try {
parsed.operational_keys = storage.reduce((acc, curr, index) => {
const agent_type = Array.from(curr.agent_type).splice(0, Array.from(curr.agent_type).indexOf(0))
const publickKey = new this.PublicKey(curr.pubkey.bytes);
curr.is_master_key = !!curr.is_master_key;
curr.index = index;
curr.pubkey = publickKey.toBase58();
curr.agent_type = String.fromCharCode.apply(String, agent_type);
curr.scopes = this.sc.bitsToScopes(curr.scopes);
curr.active = curr.state.initialize instanceof Initialized;
acc[curr.pubkey] = curr;
return acc;
}, {});
} catch (error) { errorHendler.error("op_keys storage processing error", error) };
try {
parsed.owner_keys = account.value.data.parsed.info.owners.reduce((acc, curr, i) => {
if (i === 0) acc = [];
const publickKey = new this.PublicKey(curr);
const publickKeyBase58 = publickKey.toBase58();
if (publickKeyBase58 !== this.sc.SYSTEM_PROGRAM_ADDRESS) acc.push(publickKeyBase58);
return acc;
}, {});
} catch (error) { errorHendler.error("owner_keys storage processing error", error) };
return parsed;
};
export default AccountConstructor;