@velas/account-agent
Version: 
sdk
844 lines (713 loc) • 39.2 kB
JavaScript
import bs58 from 'bs58';
import AccountConstructor from '../helper/account';
import assert             from '../helper/assert';
import ErrorHendler       from '../helper/error';
import ScopesConstructor  from '../helper/scopes';
import { SolidInstruction, SignerType } from '../helper/instructions'
const errorHendler = new ErrorHendler('Client');
/**
 * Creates a new API Client to blockchain
 * @constructor
 * @param {Object} options
 */
function Client(options, keyStorage) {
    this.keyStorage               = keyStorage;
    this.connection               = new options.client_provider.Connection(options.client_host, 'singleGossip');
    this.broadcast                = options.broadcastTransactionHendler;
    this.web3                     = options.client_provider;
    this.account_contract         = options.client_account_contract;
    this.transactions_sponsor_pub_key = options.transactions_sponsor_pub_key;
    this.sc                       = new ScopesConstructor({ account_contract: options.client_account_contract });
    this.accountDataConstructor   = new AccountConstructor({
        connection:       this.connection,
        account_contract: options.client_account_contract,
        PublicKey:        this.web3.PublicKey,
    });
};
Client.prototype.signAndBroadcastWithKey = async function({
    account,
    op_key,
    message,
    csrf_token,
    host,
}) {
    assert.check(account,    { type: 'string', message: 'account option is required' });
    assert.check(op_key,     { type: 'string', message: 'op_key option is required' });
    assert.check(message,    { type: 'object', message: 'message option is required' });
    assert.check(csrf_token, { type: 'string', message: 'csrf_token option is required' });
    assert.check(host,       { type: 'string', message: 'host option is required' });
    const transaction = this.web3.Transaction.populate(this.web3.Message.from(message), []);
    const signature   = await this.keyStorage.signWithKey(op_key, message);
    const signatures  = [[op_key, bs58.encode(signature)]];
    const { error, transactions_signatures } = await this.broadcast(host, {
        transactions: [{
            account,
            message: bs58.encode(transaction.serializeMessage()),
            signatures,
        }],
        csrf_token
    });
    if (error) throw new Error(error);
    return transactions_signatures[0];
};
Client.prototype.signAndSendWithKey = async function({
    account,
    op_key,
    message,
}) {
    assert.check(account, { type: 'string', message: 'account option is required' });
    assert.check(op_key,  { type: 'string', message: 'op_key option is required' });
    assert.check(message, { type: 'object', message: 'message option is required' });
    const transaction = this.web3.Transaction.populate(this.web3.Message.from(message), []);
    const signature   = await this.keyStorage.signWithKey(op_key, message);
    transaction.feePayer = new this.web3.PublicKey(op_key);
    transaction.addSignature(new this.web3.PublicKey(op_key), signature);
    if (!transaction.verifySignatures()) throw new Error(`Signature verification failed! Signer must be only one and it must be current session key: ${op_key}.`);
    return await this.web3.sendAndConfirmRawTransaction(
        this.connection,
        transaction.serialize(),
        {
          commitment: 'single',
          skipPreflight: true,
        },
    );
};
Client.prototype.findAccountAddressWithPublicKey = async function(publicKey, return_base58 = true) {
    const ownerPublicKey = typeof publicKey === 'string' ? new this.web3.PublicKey(publicKey) : publicKey;
    const vaccount = await this.web3.PublicKey.findProgramAddress(
        [ ownerPublicKey.toBuffer().slice(0, 32),
          Buffer.from("vaccount"),
        ],
        new this.web3.PublicKey(this.account_contract),
    );
    if (return_base58) return vaccount[0].toBase58();
    return vaccount[0];
};
Client.prototype.getAccountPrograms = function(accountData) {
    return this.accountDataConstructor.getAccountPrograms(accountData);
};
Client.prototype.getAccountData = function(base58PublicKey) {
    const vaccountPublicKey = new this.web3.PublicKey(base58PublicKey);
    return this.accountDataConstructor.getAccountData(vaccountPublicKey);
};
Client.prototype.getClientData = async function(client_id) {
    try {
        if (!client_id) return undefined;
        const publickKey = new this.web3.PublicKey(client_id);
        const account    = await this.connection.getParsedAccountInfo(publickKey);
        if (!account.value || account.value.data.program !== 'velas-relying-party') return undefined;
        return {
            client_name:   account.value.data.parsed.related_program_data.name,
            client_uri:    account.value.data.parsed.related_program_data.domain_name,
            redirect_uris: account.value.data.parsed.related_program_data.redirect_uri
        }
    } catch (_) {
        return undefined;
    };
};
Client.prototype.getBalance = function(base58PublicKey) {
    const publickKey = new this.web3.PublicKey(base58PublicKey);
    return this.connection.getBalance(publickKey);
};
Client.prototype.transactionStatus = function(signature) {
    return this.connection.getSignatureStatus(signature);
};
Client.prototype.sendMessage = async function({
    transaction_name,
    params,
    transactions_sponsor_api_host,
    csrf_token,
    cb,
}) {
    try {
        assert.check(params,                        { type: 'object',   message: 'params option is required' });
        assert.check(transactions_sponsor_api_host, { type: 'string',   message: 'transactions_sponsor_api_host option is required' });
        assert.check(csrf_token,                    { type: 'string',   message: 'csrf_token option is required' });
        assert.check(transaction_name,              { type: 'string',   message: 'transaction_name option is required' });
        assert.check(cb,                            { type: 'function', message: 'cb option is required' });
        if (![
            'proxy',
            'transfer',
            'initializeTransaction',
            'addOwnerTransaction',
            'replaceOwnerTransaction',
            'addOperationalAddressTransaction',
            'removeOperationalAddressTransaction',
            'extendOperationalScopesTransaction',
        ].includes(transaction_name)) throw new Error(`unsupported transaction_name param`);
        var expression = /^(http:\/\/|https:\/\/)+(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-:]*[A-Za-z0-9])$/;
        var regex = new RegExp(expression);
    
        if (!transactions_sponsor_api_host.match(regex)) throw new Error('wrong host address for transactions_sponsor_api_host param');
    } catch(e) {
        return cb({
            error: 'send_message_invalid_params',
            description: e.message || e,
            description: `SendMessage error: ${e.message || e}`,
        });
    };
    let message_data, transaction_id;
    try {
        message_data = await this[transaction_name](params);
    } catch(e) {
        return cb({
            error: 'send_message_generation_data_error',
            description: `Preparing a transaction error: ${e.message || e}`,
        });
    };
    try {
        const { error, transactions_signatures } = await this.broadcast(transactions_sponsor_api_host, {
            ...message_data,
            csrf_token
        });
        if (error) {
            return cb({
                error: 'send_message_transaction_failed',
                description: `Broadcast transaction error: ${error}`,
            });
        };
        if (!transactions_signatures) throw new Error('no transaction transactions_signatures from broadcast host');
        
        transaction_id = transactions_signatures.pop();
    } catch(e) {
        return cb({
            error: 'send_message_broadcast_host_error',
            description: `Broadcast host error: ${e.message || e}`,
        });
    };
    let i = 17;
    await ( async () => {
        while (i > 0) {
            try {
                const status = await this.transactionStatus(transaction_id);
                if (status && status.value && status.value.confirmationStatus === 'finalized') {
                    return cb(null, {
                        transaction_id,
                        account: message_data.account,
                    });
                };
                i--;
                if (status && status.value && status.value.err) {
                    throw new Error( `Transaction failed: ${status.value.err}. ${transaction_id}`);
                };
                if (i === 0) throw new Error(`Transaction wasn't completed within this time. ${transaction_id}`); 
            } catch(e) {
                return cb({
                    error: 'send_message_invalid_transaction_status',
                    description: `Transaction status error: ${e.message || e}. ${transaction_id}`,
                });
            };
            await new Promise(resolve => setTimeout(resolve, 2000));
        };
    })();
};
Client.prototype.proxy = function(params) {
    return params;
};
Client.prototype.transactionParamsValidator = async function(params = {}, { required }) {
    for (const req of required) {
        if (!params[req]) errorHendler.error(`${req} option is required`, null);
    };
    delete params.vaccount;
    if (params.account) {
        try {
            params.vaccount = typeof params.account === 'string' ? new this.web3.PublicKey(bs58.decode(params.account)) : params.account;
            
            if (!params.vaccount instanceof this.web3.PublicKey) throw new Error('account should be an instance of PublicKey');
            params.accountData = await this.accountDataConstructor.getAccountData(params.vaccount);
            
            delete params.account;
        } catch (error) {
            errorHendler.error(`incorrect param: account`, error);
        };
    };
    // SECRET -> secretOwnerOrOperationalToSignTx -> ownerOrOperationalToSignTx 
    if (params.secretOwnerOrOperationalToSignTx) {
        try {
            params.secretOwnerOrOperationalToSignTx = typeof params.secretOwnerOrOperationalToSignTx === 'string' ? bs58.decode(params.secretOwnerOrOperationalToSignTx) : params.secretOwnerOrOperationalToSignTx;
            params.ownerOrOperationalToSignTx = { account: new this.web3.Account(params.secretOwnerOrOperationalToSignTx) };
            params.ownerOrOperationalToSignTx.pubkey = params.ownerOrOperationalToSignTx.account.publicKey;
            delete params.secretOwnerOrOperationalToSignTx;
        } catch (error) {
            errorHendler.error(`incorrect param: secretOwnerOrOperationalToSignTx`, error);
        };
        if (!params.vaccount) errorHendler.error(`vaccount not found to process secretOwnerOrOperationalToSignTx`);
        if (!params.accountData || 
            !params.accountData.owner_keys) errorHendler.error(`accountData not found to process secretOwnerOrOperationalToSignTx`);
        const base58PublicKey  = params.ownerOrOperationalToSignTx.account.publicKey.toBase58();
        const isOperationalKey = params.accountData.operational_keys[base58PublicKey];
        const isOwnerKey       = params.accountData.owner_keys.includes(base58PublicKey);
        if (!isOperationalKey && !isOwnerKey) errorHendler.error(`incorrect param: secretOwnerOrOperationalToSignTx. ${base58PublicKey} not found.`);
        params.ownerOrOperationalToSignTx.type             = isOperationalKey ? 'operational' : 'owner';
        params.ownerOrOperationalToSignTx.operationalIndex = isOperationalKey ? isOperationalKey.index : null;
        params.ownerOrOperationalToSignTx.signerType       = isOwnerKey ? SignerType.owner() : SignerType.operational(isOperationalKey.index);
    };
    if (params.secretOperationalOrOwner) { // TO DO: refactring (rename secretOperationalOrOwner)
        try {
            params.secretOperationalOrOwner   = typeof params.secretOperationalOrOwner === 'string' ? bs58.decode(params.secretOperationalOrOwner) : params.secretOperationalOrOwner;
            params.ownerOrOprationalAccount   = new this.web3.Account(params.secretOperationalOrOwner);
            params.ownerOrOprationalPubKey    = params.ownerOrOprationalAccount.publicKey
            
            delete params.secretOperationalOrOwner;
        } catch (error) {
            errorHendler.error(`incorrect param: secret`, error);
        };
    };
    // PUBKEY publicKeyOperationalToRemove -> operationalToRemove
    if (params.publicKeyOperationalToRemove) {
        try {
            params.operationalToRemove = {
                pubkey: typeof params.publicKeyOperationalToRemove === 'string' ? new this.web3.PublicKey(bs58.decode(params.publicKeyOperationalToRemove)) : params.publicKeyOperationalToRemove,
            };
            delete params.publicKeyOperationalToRemove;
        } catch (error) {
            errorHendler.error(`incorrect param: publicKeyOperationalToRemove`, error);
        };
        if (!params.vaccount)    errorHendler.error(`incorrect param: publicKeyOperationalToRemove (vaccount not found)`);
        if (!params.accountData) errorHendler.error(`account ${params.vaccount.toBase58()} not found`);
        const base58PublicKey  = params.operationalToRemove.pubkey.toBase58();
        const isOperationalKey = params.accountData.operational_keys[base58PublicKey];
        const isOwnerKey       = params.accountData.owner_keys.includes(base58PublicKey);
        if (!isOperationalKey && !isOwnerKey) errorHendler.error(`incorrect param: publicKeyOperationalToRemove (vaccount does't have key: ${base58PublicKey})`);
        params.operationalToRemove.operationalIndex = isOperationalKey ? isOperationalKey.index : null
        params.operationalToRemove.signerType = isOwnerKey ? SignerType.owner() : SignerType.operational(isOperationalKey.index);
    };    
    // PUBKEY ownerOrOprationalPubKey -> ownerOrOprational
    if (params.ownerOrOprationalPubKey) {
        try {
            params.ownerOrOprational = {
                pubkey: typeof params.ownerOrOprationalPubKey === 'string' ? new this.web3.PublicKey(bs58.decode(params.ownerOrOprationalPubKey)) : params.ownerOrOprationalPubKey,
            };
            delete params.ownerOrOprationalPubKey;
        } catch (error) {
            errorHendler.error(`incorrect param: ownerOrOprationalPubKey`, error);
        };
        if (!params.vaccount)    errorHendler.error(`incorrect param: ownerOrOprationalPubKey (vaccount not found)`);
        if (!params.accountData) errorHendler.error(`account ${params.vaccount.toBase58()} not found`);
        const key = params.ownerOrOprational.pubkey.toBase58();
        const isOperationalKey = params.accountData.operational_keys[key];
        const isOwnerKey = params.accountData.owner_keys.includes(key);
        if (!isOperationalKey && !isOwnerKey) errorHendler.error(`incorrect param: ownerOrOprationalPubKey (vaccount does't have key: ${key})`);
        params.ownerOrOprational.operationalIndex = isOperationalKey ? isOperationalKey.index : null
        params.ownerOrOprational.signerType = isOwnerKey ? SignerType.owner() : SignerType.operational(isOperationalKey.index);
    };
    // ====== 
    if (params.secret) { // TO DO: refactring (rename secret)
        try {
            params.secret   = typeof params.secret === 'string' ? bs58.decode(params.secret) : params.secret;
            params.owner    = new this.web3.Account(params.secret);
            delete params.secret;
        } catch (error) {
            errorHendler.error(`incorrect param: secret`, error);
        };
        if (!params.vaccount){
            if (!params.account) {
                params.vaccount = await this.findAccountAddressWithPublicKey(params.owner.publicKey, false);
            } else {
                try {
                    params.vaccount =  new this.web3.PublicKey(params.account);
                } catch (error) {
                    errorHendler.error(`incorrect param: secret`, error);
                };
            };
        }
    };
    if (params.secret_new) { // TO DO: refactring (rename secret_new)
        try {
            params.secret_new   = typeof params.secret_new === 'string' ? bs58.decode(params.secret_new) : params.secret_new;
            params.owner_new    = new this.web3.Account(params.secret_new);
            delete params.secret_new;
        } catch (error) {
            errorHendler.error(`incorrect param: secret`, error);
        };
    };
    if (params.op_key) { // TO DO: refactring (rename op_key)
        try {
            params.op_key                = typeof params.op_key === 'string' ? bs58.decode(params.op_key) : params.op_key;
            params.operationalPublicKey  = new this.web3.PublicKey(params.op_key);
            delete params.op_key;
        } catch (error) {
            errorHendler.error(`incorrect param: op_key`, error);
        };
    };
    if (params.transactions_sponsor_pub_key) {
        try {
            params.transactions_sponsor_pub_key = typeof params.transactions_sponsor_pub_key === 'string' ? bs58.decode(params.transactions_sponsor_pub_key) : params.transactions_sponsor_pub_key;
            params.payerPublicKey           = new this.web3.PublicKey(params.transactions_sponsor_pub_key);
            delete params.transactions_sponsor_pub_key;
        } catch (error) {
            errorHendler.error(`incorrect param: transactions_sponsor_pub_key`, error);
        };
    } else {
        params.payerPublicKey = new this.web3.PublicKey(this.transactions_sponsor_pub_key || this.sc.SYSTEM_PROGRAM_ADDRESS);
    };
    if (params.agent_type) { // TO DO: refactring (rename agent_type)
        if (typeof params.agent_type !== 'string') errorHendler.error(`incorrect param: agent_type`, null);
        params.agentType = params.agent_type;
        delete params.agent_type;
    } else {
        params.agentType = 'Incognito';
    };
    if (params.fromAccountPubKey) { // TO DO: refactoring (remove params.vaccount)
        try {
            params.vaccount = typeof params.fromAccountPubKey === 'string' ? new this.web3.PublicKey(bs58.decode(params.fromAccountPubKey)) : params.fromAccountPubKey;
            
            if (!params.vaccount instanceof this.web3.PublicKey) errorHendler.error(`incorrect param: fromAccountPubKey`, 'not instance of PublicKey');
            delete params.fromAccountPubKey;
        } catch (error) {
            errorHendler.error(`incorrect param: fromAccountPubKey`, error);
        };
    };
    if (params.toPubKey) {
        try {
            params.toPubKey = typeof params.toPubKey === 'string' ? new this.web3.PublicKey(bs58.decode(params.toPubKey)) : params.toPubKey;
        } catch (error) {
            errorHendler.error(`incorrect param: toPubKey`, error);
        };
    };
        
    // TO DO: scopes validation;
    // TO DO: lamports validation;
    return params;
};
Client.prototype.prepareTransaction = async function({
    feePayer,
    vaccount,
    instructions,
    owSigner,
    opSigner,
}) {
    const { blockhash: recentBlockhash } = await this.connection.getRecentBlockhash();
    const transaction = new this.web3.Transaction({ recentBlockhash, feePayer }).add(...instructions);
    if (owSigner.length) transaction.sign(...owSigner);
          
    const ow_signatures = [];
    for(const ow of owSigner) {
        const owPubKey = ow.publicKey.toBase58();
        for(const item of transaction.signatures) {
            const signPubKey = item.publicKey.toBase58();
            if ((owPubKey === signPubKey) && item.signature) {
                console.log("prepareTransaction: found owner signature for - ", owPubKey, bs58.encode(item.signature))
                ow_signatures.push([signPubKey, bs58.encode(item.signature)])
            }
        };    
    };
    const signatures = [...ow_signatures];
    if (opSigner) {
        const op_signature = await this.keyStorage.signWithKey(opSigner.toBase58(), transaction.serializeMessage());
        signatures.push([opSigner.toBase58(), bs58.encode(op_signature)]);
    };
    return {
        account: vaccount.toBase58(),
        message: bs58.encode(transaction.serializeMessage()),
        signatures,
    };
};
Client.prototype.initializeTransaction = async function(params) {
    const {
        owner,
        operationalPublicKey,
        payerPublicKey,
        vaccount,
        agentType,
        scopes,
    } = await this.transactionParamsValidator(params, { required: ['secret', 'op_key'] });
   
    const accountData = await this.accountDataConstructor.getAccountData(vaccount, true); console.log("accountDat1a", accountData);
    if (!accountData.ephimeric) throw new Error('Account ' + vaccount.toBase58() + ' already exists for specified key');
    const addOperationalData = await this.accountDataConstructor.addOperationalData(accountData, {
        pubkey:    operationalPublicKey,
        agentType: agentType,
        scopes:    scopes,
    });
    const transfer = this.web3.SystemProgram.transfer({
        fromPubkey: payerPublicKey,
        toPubkey:   vaccount,
        lamports:   addOperationalData.storage.rent
    });
    const init = new this.web3.TransactionInstruction({
        programId: new this.web3.PublicKey(this.account_contract),
        keys: [
            { pubkey: vaccount,                                                isSigner: false, isWritable: true },
            { pubkey: owner.publicKey,                                         isSigner: true,  isWritable: false },
            { pubkey: new this.web3.PublicKey(this.sc.SYSVAR_RENT_ADDRESS),    isSigner: false, isWritable: false },
            { pubkey: new this.web3.PublicKey(this.sc.SYSTEM_PROGRAM_ADDRESS), isSigner: false, isWritable: false },
        ],
        data: accountData.instruction.initializeEncoded,
    });
    let addPrograms = [];
    for (const programObject of addOperationalData.storage.whitelistPrograms) {
        addPrograms.push(new this.web3.TransactionInstruction({
            programId: new this.web3.PublicKey(this.account_contract),
            keys: [
                { pubkey: vaccount,                                                isSigner: false, isWritable: true },
                { pubkey: accountData.storage.storage_current,                     isSigner: false, isWritable: true },
                { pubkey: programObject.pubkey,                                    isSigner: false, isWritable: false },
                { pubkey: programObject.current,                                   isSigner: false, isWritable: true },
                { pubkey: programObject.next,                                      isSigner: false, isWritable: true },
                { pubkey: new this.web3.PublicKey(this.sc.SYSVAR_RENT_ADDRESS),    isSigner: false, isWritable: false },
                { pubkey: new this.web3.PublicKey(this.sc.SYSTEM_PROGRAM_ADDRESS), isSigner: false, isWritable: false },
                { pubkey: owner.publicKey,                                         isSigner: true,  isWritable: false },
            ],
            data: addOperationalData.storage.addProgramEncoded,
        }))
    };
    const addOperationalAddressInstruction = new this.web3.TransactionInstruction({
        programId: new this.web3.PublicKey(this.account_contract),
        keys: [
            { pubkey: vaccount,                                                isSigner: false, isWritable: true },
            { pubkey: addOperationalData.storage.tokens_next,                  isSigner: false, isWritable: true },
            { pubkey: addOperationalData.storage.programs_next,                isSigner: false, isWritable: true },
            { pubkey: accountData.storage.storage_current,                     isSigner: false, isWritable: true },
            { pubkey: accountData.storage.storage_next,                        isSigner: false, isWritable: true },
            { pubkey: operationalPublicKey,                                    isSigner: false,  isWritable: false },
            { pubkey: new this.web3.PublicKey(this.sc.SYSVAR_RENT_ADDRESS),    isSigner: false, isWritable: false },
            { pubkey: new this.web3.PublicKey(this.sc.SYSTEM_PROGRAM_ADDRESS), isSigner: false, isWritable: false },
            { pubkey: owner.publicKey,                                         isSigner: true,  isWritable: false }, 
        ],
        data: addOperationalData.storage.addOperationalEncoded,
    });
    return {
        transactions: [await this.prepareTransaction({
            feePayer:     payerPublicKey,
            vaccount:     vaccount,
            instructions: [transfer, init, ...addPrograms, addOperationalAddressInstruction],
            owSigner:     [owner],
            //opSigner:   operationalPublicKey,
        })]
    };
};
Client.prototype.addOperationalAddressTransaction = async function(params) {
    const {
        operationalPublicKey,
        payerPublicKey,
        vaccount,
        agentType,
        scopes,
        ownerOrOprational,
        ownerOrOprationalAccount,
    } = await this.transactionParamsValidator(params, { required: ['secretOperationalOrOwner', 'op_key', 'account' ] });
    const accountData = await this.accountDataConstructor.getAccountData(vaccount);
    if (!accountData) throw new Error("Specified account not found");
    // if (!accountData.owner_keys.includes(owner.publicKey.toBase58()) && !accountData.operational_keys[owner.publicKey.toBase58()]) throw new Error("You dont have rights to specified account"); // TODO: write new validation;
    const addOperationalData = await this.accountDataConstructor.addOperationalData(accountData, {
        pubkey:    operationalPublicKey,
        agentType: agentType,
        scopes:    scopes,
    }, ownerOrOprational);
    let instructions = [];
    if (addOperationalData.storage.rent > 0) {
        instructions.push(this.web3.SystemProgram.transfer({
            fromPubkey: payerPublicKey,
            toPubkey:   vaccount,
            lamports:   addOperationalData.storage.rent
        }));
    };
    for (const programObject of addOperationalData.storage.whitelistPrograms) {
        instructions.push(new this.web3.TransactionInstruction({
            programId: new this.web3.PublicKey(this.account_contract),
            keys: [
                { pubkey: vaccount,                                                isSigner: false, isWritable: true },
                { pubkey: accountData.storage.storage_current,                     isSigner: false, isWritable: true },
                { pubkey: programObject.pubkey,                                    isSigner: false, isWritable: false },
                { pubkey: programObject.current,                                   isSigner: false, isWritable: true },
                { pubkey: programObject.next,                                      isSigner: false, isWritable: true },
                { pubkey: new this.web3.PublicKey(this.sc.SYSVAR_RENT_ADDRESS),    isSigner: false, isWritable: false },
                { pubkey: new this.web3.PublicKey(this.sc.SYSTEM_PROGRAM_ADDRESS), isSigner: false, isWritable: false },
                { pubkey: ownerOrOprationalAccount.publicKey,                                         isSigner: true,  isWritable: false },
            ],
            data: addOperationalData.storage.addProgramEncoded,
        }))
    };
    const addOperationalAddressInstruction = new this.web3.TransactionInstruction({
        programId: new this.web3.PublicKey(this.account_contract),
        keys: [
            { pubkey: vaccount,                                                isSigner: false, isWritable: true },
            { pubkey: addOperationalData.storage.tokens_next,                  isSigner: false, isWritable: true },
            { pubkey: addOperationalData.storage.programs_next,                isSigner: false, isWritable: true },
            { pubkey: accountData.storage.storage_current,                     isSigner: false, isWritable: true },
            { pubkey: accountData.storage.storage_next,                        isSigner: false, isWritable: true },
            { pubkey: operationalPublicKey,                                    isSigner: false, isWritable: false },
            { pubkey: new this.web3.PublicKey(this.sc.SYSVAR_RENT_ADDRESS),    isSigner: false, isWritable: false },
            { pubkey: new this.web3.PublicKey(this.sc.SYSTEM_PROGRAM_ADDRESS), isSigner: false, isWritable: false },
            { pubkey: ownerOrOprationalAccount.publicKey,                      isSigner: true,  isWritable: false }, 
        ],
        data: addOperationalData.storage.addOperationalEncoded,
    });
    return {
        transactions: [await this.prepareTransaction({
            feePayer:     payerPublicKey,
            vaccount:     vaccount,
            instructions: [...instructions, addOperationalAddressInstruction],
            owSigner:     [ownerOrOprationalAccount],
            // opSigner:     operationalPublicKey,
        })]
    };
};
Client.prototype.transfer = async function(params) {
    const {
        vaccount,
        toPubKey,
        lamports,
        ownerOrOprational,
        accountData,
    } = await this.transactionParamsValidator(params, { required: [
        'fromAccountPubKey',
        'toPubKey',
        'lamports',
        'ownerOrOprationalPubKey'
    ]});
    const transfer = new this.web3.TransactionInstruction({
        programId: new this.web3.PublicKey(this.account_contract),
        keys: [
            { pubkey: vaccount,                                                isSigner: false, isWritable: true },
            { pubkey: accountData.storage.storage_current,                     isSigner: false, isWritable: true },
            { pubkey: toPubKey,                                                isSigner: false, isWritable: true },
            { pubkey: new this.web3.PublicKey(this.sc.SYSVAR_RENT_ADDRESS),    isSigner: false, isWritable: false },
            { pubkey: new this.web3.PublicKey(this.sc.SYSTEM_PROGRAM_ADDRESS), isSigner: false, isWritable: false },
            { pubkey: ownerOrOprational.pubkey,                                isSigner: true,  isWritable: false }, 
        ],
        data: SolidInstruction.transfer({ 
            amount:     lamports,
            signerType: ownerOrOprational.signerType
        }).encode(),
    });
    return new this.web3.Transaction().add(transfer);
};
Client.prototype.replaceOwnerTransaction = async function(params) {
    const {
        owner,
        owner_new,
        payerPublicKey,
        vaccount,
    } = await this.transactionParamsValidator(params, { required: ['secret', 'secret_new', 'account'] });
    const accountData = await this.accountDataConstructor.getAccountData(vaccount);
    if (!accountData) throw new Error("Specified account not found");
    if (!accountData.owner_keys.includes(owner.publicKey.toBase58())) throw new Error("You dont have rights to specified account");
    const replaceOwnerInstruction = new this.web3.TransactionInstruction({
        programId: new this.web3.PublicKey(this.account_contract),
        keys: [
            { pubkey: vaccount,            isSigner: false, isWritable: true },
            { pubkey: owner.publicKey,     isSigner: false, isWritable: false },
            { pubkey: owner_new.publicKey, isSigner: true,  isWritable: false },
            { pubkey: owner.publicKey,     isSigner: true,  isWritable: false },
        ],
        data: accountData.instruction.replaceOwnerEncoded,
    });
    return this.prepareTransaction({
        feePayer:     payerPublicKey,
        vaccount:     vaccount,
        instructions: [replaceOwnerInstruction],
        owSigner:     [owner, owner_new],
    });
};
Client.prototype.removeOperationalAddressTransaction = async function(params) {
    const {
        payerPublicKey,
        vaccount,
        ownerOrOperationalToSignTx,
        operationalToRemove,
        accountData,
    } = await this.transactionParamsValidator(params, { required: ['account', 'secretOwnerOrOperationalToSignTx', 'publicKeyOperationalToRemove'] });
    if (!accountData) throw new Error("Specified account not found");
    
    const removeOperationalData = await this.accountDataConstructor.removeOperationalData(accountData, operationalToRemove.pubkey);
    let removePrograms = [];
    for (const permissionToRemoveIndex of removeOperationalData.storage.programPermissionsToRemove) {    
        removePrograms.push(new this.web3.TransactionInstruction({
            programId: new this.web3.PublicKey(this.account_contract),
            keys: [
                { pubkey: vaccount,                                       isSigner: false, isWritable: true },
                { pubkey: accountData.storage.storage_current,            isSigner: false, isWritable: true },
                { pubkey: removeOperationalData.storage.programs_current, isSigner: false, isWritable: true },
                { pubkey: ownerOrOperationalToSignTx.pubkey,              isSigner: true,  isWritable: false }, // !!!
            ],
            data: SolidInstruction.removeProgramPermission({
                operational2RemoveIndex: operationalToRemove.operationalIndex,
                signerType:              ownerOrOperationalToSignTx.signerType,
                program2RemoveIndex:     permissionToRemoveIndex,
            }).encode(),
        }));
    };
    const removeOperationalAddressInstruction = new this.web3.TransactionInstruction({
        programId: new this.web3.PublicKey(this.account_contract),
        keys: [
            { pubkey: vaccount,                                                   isSigner: false, isWritable: true },
            { pubkey: accountData.storage.storage_current,                        isSigner: false, isWritable: true },
            { pubkey: accountData.storage.storage_next,                           isSigner: false, isWritable: true },
            { pubkey: operationalToRemove.pubkey,                                 isSigner: false, isWritable: false },
            { pubkey: new this.web3.PublicKey(this.sc.SYSVAR_RENT_ADDRESS),       isSigner: false, isWritable: false },
            { pubkey: new this.web3.PublicKey(this.sc.SYSTEM_PROGRAM_ADDRESS),    isSigner: false, isWritable: false },
            { pubkey: ownerOrOperationalToSignTx.pubkey,                          isSigner: true,  isWritable: false }, // !!!
        ],
        data: SolidInstruction.removeOperational({ 
            operational2RemoveIndex: operationalToRemove.operationalIndex,
            signerType:              ownerOrOperationalToSignTx.signerType,
        }).encode(),
    });
    const owSigner = ownerOrOperationalToSignTx.type === 'owner' ? [ownerOrOperationalToSignTx.account] : [];
    const opSigner = ownerOrOperationalToSignTx.type === 'owner' ? null : ownerOrOperationalToSignTx.pubkey;
    const transactions = await Promise.all(removePrograms.map(async (instruction) => {
        return await this.prepareTransaction({
            feePayer:     payerPublicKey,
            vaccount:     vaccount,
            instructions: [instruction],
            owSigner,
            opSigner,
        })
    }));
    const removeTransaction = await this.prepareTransaction({
        feePayer:     payerPublicKey,
        vaccount:     vaccount,
        instructions: [removeOperationalAddressInstruction],
        owSigner,
        opSigner,
    })
    transactions.push(removeTransaction);
    return { transactions };
};
Client.prototype.extendOperationalScopesTransaction = async function(params) {
    const {
        operationalPublicKey,
        payerPublicKey,
        vaccount,
        ownerOrOprational,
        agentType,
        scopes,
        ownerOrOprationalAccount,
    } = await this.transactionParamsValidator(params, { required: ['secretOperationalOrOwner', 'account', 'op_key', 'scopes'] });
    const accountData = await this.accountDataConstructor.getAccountData(vaccount);
    if (!accountData) throw new Error("Specified account not found");
    const addOperationalData = await this.accountDataConstructor.addOperationalData(accountData, {
        pubkey:    operationalPublicKey,
        agentType: agentType,
        scopes:    scopes,
        extend:    true
    }, ownerOrOprational);
    let instructions = [];
    for (const programObject of addOperationalData.storage.whitelistPrograms) {
        instructions.push(new this.web3.TransactionInstruction({
            programId: new this.web3.PublicKey(this.account_contract),
            keys: [
                { pubkey: vaccount,                                                isSigner: false, isWritable: true },
                { pubkey: accountData.storage.storage_current,                     isSigner: false, isWritable: true },
                { pubkey: programObject.pubkey,                                    isSigner: false, isWritable: false },
                { pubkey: programObject.current,                                   isSigner: false, isWritable: true },
                { pubkey: programObject.next,                                      isSigner: false, isWritable: true },
                { pubkey: new this.web3.PublicKey(this.sc.SYSVAR_RENT_ADDRESS),    isSigner: false, isWritable: false },
                { pubkey: new this.web3.PublicKey(this.sc.SYSTEM_PROGRAM_ADDRESS), isSigner: false, isWritable: false },
                { pubkey: ownerOrOprationalAccount.publicKey,                                         isSigner: true,  isWritable: false },
            ],
            data: addOperationalData.storage.addProgramEncoded,
        }))
    };
    const extendOperationalScopesInstruction = new this.web3.TransactionInstruction({
        programId: new this.web3.PublicKey(this.account_contract),
        keys: [
            { pubkey: vaccount,                                                   isSigner: false, isWritable: true },
            { pubkey: addOperationalData.storage.tokens_current,                  isSigner: false, isWritable: true },
            { pubkey: addOperationalData.storage.programs_next,                isSigner: false, isWritable: true },
            { pubkey: accountData.storage.storage_current,                        isSigner: false, isWritable: true },
            { pubkey: new this.web3.PublicKey(this.sc.SYSTEM_PROGRAM_ADDRESS),    isSigner: false, isWritable: false },
            { pubkey: ownerOrOprationalAccount.publicKey,                         isSigner: true,  isWritable: false },
        ],
        data: addOperationalData.storage.extendOperationalScopesEncoded,
    });
    return this.prepareTransaction({
        feePayer:     payerPublicKey,
        vaccount:     vaccount,
        instructions: [...instructions, extendOperationalScopesInstruction],
        owSigner:     [ownerOrOprationalAccount],
    });
};
export default Client;