Merge pull request #2755 from ecency/sa/improve-hive-uri-support

[WIP] Improve hive uri QR support
This commit is contained in:
Feruz M 2023-09-28 07:01:47 +03:00 committed by GitHub
commit 522893056b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1006 additions and 93 deletions

View File

@ -16,10 +16,10 @@ import {
import { deepLinkParser } from '../../utils/deepLinkParser';
import RootNavigation from '../../navigation/rootNavigation';
import getWindowDimensions from '../../utils/getWindowDimensions';
import { isHiveUri } from '../../utils/hive-uri';
import { handleHiveUriOperation } from '../../providers/hive/dhive';
import { isHiveUri, getFormattedTx } from '../../utils/hive-uri';
import { handleHiveUriOperation, resolveTransaction } from '../../providers/hive/dhive';
import bugsnagInstance from '../../config/bugsnag';
import { get, isArray } from 'lodash';
import { get } from 'lodash';
import showLoginAlert from '../../utils/showLoginAlert';
import authType from '../../constants/authType';
import { delay } from '../../utils/editor';
@ -156,55 +156,70 @@ export const QRModal = ({}: QRModalProps) => {
}
const parsed = hiveuri.decode(uri);
// resolve the decoded tx and params to a signable tx
let { tx, signer } = hiveuri.resolveTransaction(parsed.tx, parsed.params, {
signers: currentAccount.name,
preferred_signer: currentAccount.name,
});
const operations = get(tx, 'operations', []);
if (!_checkOpsArray(operations)) {
Alert.alert(
intl.formatMessage({
id: 'qr.multi_array_ops_alert',
}),
intl.formatMessage({
id: 'qr.multi_array_ops_aler_desct',
}),
);
return;
}
dispatch(
showActionModal({
title: intl.formatMessage({
id: 'qr.confirmTransaction',
}),
bodyContent: _checkOpsArray(operations) ? _renderActionModalBody(operations[0]) : null,
buttons: [
{
text: intl.formatMessage({
id: 'qr.cancel',
const authoritiesMap = new Map();
authoritiesMap.set('active', currentAccount?.local?.activeKey ? true : false);
authoritiesMap.set('posting', currentAccount?.local?.postingKey ? true : false);
authoritiesMap.set('owner', currentAccount?.local?.ownerKey ? true : false);
authoritiesMap.set('memo', currentAccount?.local?.memoKey ? true : false);
getFormattedTx(parsed.tx, authoritiesMap)
.then(async (formattedTx) => {
// resolve the decoded tx and params to a signable tx
const tx = await resolveTransaction(formattedTx.tx, parsed.params, currentAccount.name);
const ops = get(tx, 'operations', []);
const op = ops[0];
dispatch(
showActionModal({
title: intl.formatMessage({
id: 'qr.confirmTransaction',
}),
onPress: () => {},
style: 'cancel',
},
{
text: intl.formatMessage({
id: 'qr.approve',
}),
onPress: () => {
handleHiveUriOperation(currentAccount, pinCode, uri)
.then(() => {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.successful' })));
})
.catch((err) => {
bugsnagInstance.notify(err);
dispatch(toastNotification(intl.formatMessage({ id: 'alert.key_warning' })));
});
bodyContent: _renderActionModalBody(op, formattedTx.opName),
buttons: [
{
text: intl.formatMessage({
id: 'qr.cancel',
}),
onPress: () => {},
style: 'cancel',
},
{
text: intl.formatMessage({
id: 'qr.approve',
}),
onPress: () => {
handleHiveUriOperation(currentAccount, pinCode, tx)
.then(() => {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.successful' })));
})
.catch((err) => {
bugsnagInstance.notify(err);
if (err) {
dispatch(toastNotification(intl.formatMessage({ id: err })));
} else {
dispatch(
toastNotification(intl.formatMessage({ id: 'qr.transaction_failed' })),
);
}
});
},
},
],
}),
);
})
.catch((errObj) => {
Alert.alert(
intl.formatMessage({ id: errObj.errorKey1 }, { key: errObj.authorityKeyType }),
intl.formatMessage(
{
id: errObj.errorKey2,
},
},
],
}),
);
{ key: errObj.authorityKeyType },
),
);
return;
});
};
const _handleDeepLink = async (url) => {
@ -221,11 +236,6 @@ export const QRModal = ({}: QRModalProps) => {
}
};
// check operation array is valid and is a single operation array
const _checkOpsArray = (ops) => {
return ops && isArray(ops) && ops.length === 1 && isArray(ops[0]) && ops[0].length === 2;
};
const _renderTransactionInfoRow = (item: any) => (
<View style={styles.transactionRow}>
<Text numberOfLines={1} style={styles.transactionItem1}>
@ -236,10 +246,10 @@ export const QRModal = ({}: QRModalProps) => {
</Text>
</View>
);
const _renderActionModalBody = (operations: any) => (
const _renderActionModalBody = (operations: any, opName: string) => (
<View style={styles.transactionBodyContainer}>
<View style={styles.transactionHeadingContainer}>
<Text style={styles.transactionHeading}>{operations[0]}</Text>
<Text style={styles.transactionHeading}>{opName}</Text>
</View>
<View style={styles.transactionItemsContainer}>
{Object.entries(operations[1]).map((item) => _renderTransactionInfoRow(item))}

View File

@ -1041,11 +1041,30 @@
"confirmTransaction": "Confirm transaction",
"approve": "Approve",
"cancel": "Cancel",
"multi_array_ops_alert": "Multiple operations array detected!",
"multi_array_ops_aler_desct": "Ecency does not support signing multiple operations, yet"
"multi_array_ops_alert": "Operation not supported by Ecency",
"multi_array_ops_aler_desct": "Ecency does not support signing multiple operations yet",
"invalid_op": "Invalid operation data",
"invalid_op_desc": "try contacting QR/link author",
"invalid_amount": "Invalid Amount",
"invalid_amount_desc": "Enter valid amount in proper format",
"transaction_failed": "Transaction Failed!",
"invalid_key": "{key} key is required to perform this transaction",
"invalid_key_desc": "kindly login with {key} key or master key to perform this transaction"
},
"history": {
"edit": "Edit History",
"version": "Version"
},
"chain-error": {
"min-root-comment": "You may only post once every five minutes.",
"identical-vote": "Your current vote on this content is identical to this vote.",
"insufficient-resource": "Insufficient Resource Credits.",
"delete-comment-with-vote": "Cannot delete a content with positive pending rewards.",
"comment-cashout": "Content after their payout cannot be deleted.",
"comment-children": "Cannot delete a content with replies.",
"paid-out-post-forbidden": "Voting for paid out content is not available.",
"missing-authority": "This operation requires Active private key or authority.",
"missing-owner-authority": "This operation requires Owner private key or authority.",
"insufficient_fund": "Insufficient Funds"
}
}

View File

@ -2082,59 +2082,77 @@ export const votingPower = (account) => {
};
/* eslint-enable */
export const resolveTransaction = async (parsedTx, parsedParams, signer) => {
const EXPIRE_TIME = 60 * 1000;
const props = await client.database.getDynamicGlobalProperties();
// resolve the decoded tx and params to a signable tx
const { tx } = hiveuri.resolveTransaction(parsedTx, parsedParams, {
/* eslint-disable no-bitwise */
ref_block_num: props.head_block_number & 0xffff,
ref_block_prefix: Buffer.from(props.head_block_id, 'hex').readUInt32LE(4),
expiration: new Date(Date.now() + client.broadcast.expireTime + EXPIRE_TIME)
.toISOString()
.slice(0, -5),
signers: [signer],
preferred_signer: signer,
});
tx.ref_block_num = parseInt(tx.ref_block_num + '', 10);
tx.ref_block_prefix = parseInt(tx.ref_block_prefix + '', 10);
return tx;
};
const handleChainError = (strErr: string) => {
if (/You may only post once every/.test(strErr)) {
return 'chain-error.min-root-comment';
} else if (/Your current vote on this comment is identical/.test(strErr)) {
return 'chain-error.identical-vote';
} else if (/Please wait to transact, or power up/.test(strErr)) {
return 'chain-error.insufficient-resource';
} else if (/Cannot delete a comment with net positive/.test(strErr)) {
return 'chain-error.delete-comment-with-vote';
} else if (/children == 0/.test(strErr)) {
return 'chain-error.comment-children';
} else if (/comment_cashout/.test(strErr)) {
return 'chain-error.comment-cashout';
} else if (/Votes evaluating for comment that is paid out is forbidden/.test(strErr)) {
return 'chain-error.paid-out-post-forbidden';
} else if (/Missing Active Authority/.test(strErr)) {
return 'chain-error.missing-authority';
} else if (/Missing Owner Authority/.test(strErr)) {
return 'chain-error.missing-owner-authority';
} else if (/does not have sufficient funds/.test(strErr)) {
return 'chain-error.insufficient_fund';
}
return null;
};
export const handleHiveUriOperation = async (
currentAccount: any,
pin: any,
hiveUri: string,
tx: any,
): Promise<TransactionConfirmation> => {
try {
const digitPinCode = getDigitPinCode(pin);
const key = getAnyPrivateKey(currentAccount.local, digitPinCode);
const privateKey = PrivateKey.fromString(key);
const { head_block_number, head_block_id, time } = await getDynamicGlobalProperties();
const ref_block_num = head_block_number & 0xffff;
const ref_block_prefix = Buffer.from(head_block_id, 'hex').readUInt32LE(4);
const expireTime = 60 * 1000;
const chainId = Buffer.from(
'beeab0de00000000000000000000000000000000000000000000000000000000',
'hex',
);
const expiration = new Date(new Date(`${time}Z`).getTime() + expireTime)
.toISOString()
.slice(0, -5);
const extensions = [];
const parsed = hiveuri.decode(hiveUri)
// resolve the decoded tx and params to a signable tx
let { tx, signer } = hiveuri.resolveTransaction(parsed.tx, parsed.params, {
expiration,
// accounts we are able to sign for
signers: currentAccount.name,
// selected signer if none is asked for by the params
preferred_signer: currentAccount.name,
});
//inject raw ref_block_num and ref_block_prefex to avoid string converstion by hiveuri.resolveTransaction
// e.g. from a get_dynamic_global_properties call
tx.ref_block_num = ref_block_num;
tx.ref_block_prefix = ref_block_prefix;
// console.log('tx : ', JSON.stringify(tx, null, 2));
// console.log('tx : ', tx);
const transaction = cryptoUtils.signTransaction(tx, privateKey, chainId);
const trxId = generateTrxId(transaction);
const resultHive = await client.broadcast.call('broadcast_transaction', [transaction]);
const result = Object.assign({ id: trxId }, resultHive);
// console.log('result : ', JSON.stringify(result, null, 2));
return result;
} catch (err) {
const errString = handleChainError(err.toString());
bugsnagInstance.notify(err, (event) => {
event.context = 'handle-hive-uri-operations';
event.setMetaData('hiveUri', hiveUri);
event.context = 'handle-hive-uri-operation';
event.setMetaData('tx', tx);
});
throw err;
return Promise.reject(errString);
}
};

View File

@ -1,4 +1,129 @@
import { get, isArray } from 'lodash';
const operationsData = require('./operations.json');
/**
* checks if uri entered is valid hive uri
* Accepts a string
* Returns boolean if uri starts with 'hive://'
* */
export const isHiveUri = (uri: string) => {
let trimUri = uri.trim();
return trimUri.startsWith('hive://');
};
// check operation array is valid and is a single operation array
const _checkOpsArray = (ops: any) => {
return ops && isArray(ops) && ops.length === 1 && isArray(ops[0]) && ops[0].length === 2;
};
const findParentKey = (obj, value, parentKey = null) => {
for (let key in obj) {
if (obj[key] === value) {
return parentKey;
} else if (typeof obj[key] === 'object') {
const foundKey = findParentKey(obj[key], value, key);
if (foundKey) {
return foundKey;
}
}
}
return null;
};
// get operation name and signer field from operation object
const getOperationProps = (opName: string) => {
const op = get(operationsData, opName, null);
if (op) {
const signerField = findParentKey(op, '__signer');
return {
opName: op.name,
opAuthority: op.authority || '',
signerField,
};
} else {
return null;
}
};
// validate and format amount field in operation to 3 decimal places
const _formatAmount = (amount: string) => {
const splitAmt = amount.split(' ');
if (
splitAmt.length === 2 &&
parseFloat(splitAmt[0]) &&
(splitAmt[1] === 'HIVE' || splitAmt[1] === 'HBD')
) {
return `${parseFloat(splitAmt[0]).toFixed(3)} ${splitAmt[1]}`;
} else {
return null;
}
};
/**
* Validates tx from parsed data from hive-uri, checks if operation length is not greater than one and __signer is present in operation
* Accepts tx object of parsed uri from decode method of hive-uri
* Returns promise with keys for showing errors, and operation name parsed from operations.json and in case of success returns formatted tx
*
* */
export const getFormattedTx = (tx: any, authoritiesMap: Map<string, boolean>) => {
let opName;
let errorObj = {
errorKey1: '',
errorKey2: '',
authorityKeyType: '',
};
const ops = get(tx, 'operations', []);
const isValidOp = _checkOpsArray(ops);
if (!isValidOp) {
errorObj.errorKey1 = 'qr.multi_array_ops_alert';
errorObj.errorKey2 = 'qr.multi_array_ops_aler_desct';
return Promise.reject(errorObj);
}
const op = ops[0]; // single operation
const operationName = op[0]; // operation name
let operationObj = op[1]; // operation object
if (!operationName) {
errorObj.errorKey1 = 'qr.invalid_op';
errorObj.errorKey2 = 'qr.invalid_op_desc';
return Promise.reject(errorObj);
}
const opProps = getOperationProps(operationName); // get operation props from operations.json file i-e signer field and operation name
errorObj.authorityKeyType = opProps?.opAuthority || ''; // set key type to validate object
if (!opProps) {
errorObj.errorKey1 = 'qr.invalid_op';
errorObj.errorKey2 = 'qr.invalid_op_desc';
return Promise.reject(errorObj);
}
if (authoritiesMap && !authoritiesMap.get(opProps.opAuthority)) {
errorObj.errorKey1 = 'qr.invalid_key';
errorObj.errorKey2 = 'qr.invalid_key_desc';
return Promise.reject(errorObj);
}
// if amount field present in operation, validate and check for proper formatting and format to 3 decimal places
if (operationObj.hasOwnProperty('amount')) {
const amount = _formatAmount(operationObj.amount);
operationObj.amount = amount;
if (!amount) {
errorObj.errorKey1 = 'qr.invalid_amount';
errorObj.errorKey2 = 'qr.invalid_amount_desc';
return Promise.reject(errorObj);
}
}
const opSignerValue = get(op[1], opProps.signerField, '');
// if signer field contains empty value, fill it with __signer
if (!opSignerValue) {
operationObj[opProps.signerField] = '__signer';
}
opName = opProps.opName;
tx = {
...tx,
operations: [[operationName, operationObj]],
};
// resolve with formatted tx and opName
return Promise.resolve({ tx: tx, opName: opName });
};

741
src/utils/operations.json Normal file
View File

@ -0,0 +1,741 @@
{
"transfer": {
"name": "Transfer",
"authority": "active",
"description": "Transfers asset from one account to another.",
"schema": {
"from": {
"type": "account",
"defaultValue": "__signer"
},
"to": {
"type": "account"
},
"amount": {
"type": "amount"
},
"memo": {
"type": "string",
"defaultValue": "",
"maxLength": 2048
}
}
},
"recurrent_transfer": {
"name": "Recurring Transfers",
"authority": "active",
"description": "Recurring transfers of asset from one account to another.",
"schema": {
"from": {
"type": "account",
"defaultValue": "__signer"
},
"to": {
"type": "account"
},
"amount": {
"type": "amount"
},
"memo": {
"type": "string",
"defaultValue": "",
"maxLength": 2048
},
"recurrence": {
"type": "int"
},
"executions": {
"type": "int"
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"delegate_vesting_shares": {
"name": "Delegate Hive Power",
"authority": "active",
"schema": {
"delegator": {
"type": "account",
"defaultValue": "__signer"
},
"delegatee": {
"type": "account"
},
"vesting_shares": {
"type": "amount"
}
}
},
"transfer_to_vesting": {
"name": "Power up",
"authority": "active",
"schema": {
"from": {
"type": "account",
"defaultValue": "__signer"
},
"to": {
"type": "account",
"defaultValue": "__signer"
},
"amount": {
"type": "amount"
}
}
},
"set_withdraw_vesting_route": {
"name": "Set withdraw vesting route",
"authority": "active",
"schema": {
"from_account": {
"type": "account",
"defaultValue": "__signer"
},
"to_account": {
"type": "account"
},
"percent": {
"type": "int"
},
"auto_vest": {
"type": "bool",
"defaultValue": false
}
}
},
"withdraw_vesting": {
"name": "Power down",
"authority": "active",
"schema": {
"account": {
"type": "account",
"defaultValue": "__signer"
},
"vesting_shares": {
"type": "amount"
}
}
},
"transfer_to_savings": {
"name": "Transfer to saving",
"authority": "active",
"schema": {
"from": {
"type": "account",
"defaultValue": "__signer"
},
"to": {
"type": "account",
"defaultValue": "__signer"
},
"amount": {
"type": "amount"
},
"memo": {
"type": "string",
"defaultValue": "",
"maxLength": 2048
}
}
},
"transfer_from_savings": {
"name": "Transfer from saving",
"authority": "active",
"schema": {
"from": {
"type": "account",
"defaultValue": "__signer"
},
"to": {
"type": "account",
"defaultValue": "__signer"
},
"amount": {
"type": "amount"
},
"memo": {
"type": "string",
"defaultValue": "",
"maxLength": 2048
},
"request_id": {
"type": "int"
}
}
},
"cancel_transfer_from_savings": {
"name": "Cancel transfer from saving",
"authority": "active",
"schema": {
"from": {
"type": "account",
"defaultValue": "__signer"
},
"request_id": {
"type": "int"
}
}
},
"convert": {
"name": "Convert",
"authority": "active",
"schema": {
"owner": {
"type": "account",
"defaultValue": "__signer"
},
"requestid": {
"type": "int"
},
"amount": {
"type": "amount"
}
}
},
"collateralized_convert": {
"name": "Collateralized Convert",
"authority": "active",
"schema": {
"owner": {
"type": "account",
"defaultValue": "__signer"
},
"requestid": {
"type": "int"
},
"amount": {
"type": "amount"
}
}
},
"account_witness_vote": {
"name": "Witness vote",
"authority": "active",
"schema": {
"account": {
"type": "account",
"defaultValue": "__signer"
},
"witness": {
"type": "account"
},
"approve": {
"type": "bool",
"defaultValue": true
}
}
},
"witness_update": {
"name": "Witness update",
"authority": "active",
"schema": {
"owner": {
"type": "account",
"defaultValue": "__signer"
},
"url": {
"type": "string",
"defaultValue": "https://ecency.com"
},
"block_signing_key": {
"type": "string",
"defaultValue": "STM1111111111111111111111111111111114T1Anm"
},
"props": {
"type": "object",
"defaultValue": {
"account_creation_fee": "1.000 HIVE",
"maximum_block_size": 131072,
"hbd_interest_rate": 2000
}
},
"fee": {
"type": "string",
"defaultValue": "0.000 HIVE"
}
}
},
"witness_set_properties": {
"name": "Witness set properties",
"authority": "active",
"schema": {
"owner": {
"type": "account",
"defaultValue": "__signer"
},
"props": {
"type": "object",
"defaultValue": {
"account_creation_fee": "1.000 HIVE",
"account_subsidy_budget": 10000,
"account_subsidy_decay": 330782,
"maximum_block_size": 65536,
"hbd_interest_rate": 2000,
"hbd_exchange_rate": {"base": "0.000 HBD", "quote": "0.000 HIVE"},
"url": "https://ecency.com",
"new_signing_key": "STM1111111111111111111111111111111114T1Anm"
}
}
}
},
"account_witness_proxy": {
"name": "Governance proxy",
"authority": "active",
"schema": {
"account": {
"type": "account",
"defaultValue": "__signer"
},
"proxy": {
"type": "account"
}
}
},
"claim_account": {
"name": "Claim account",
"authority": "active",
"schema": {
"creator": {
"type": "account",
"defaultValue": "__signer"
},
"fee": {
"type": "amount",
"defaultValue": "0.000 HIVE"
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"account_create": {
"name": "Create account",
"authority": "active",
"schema": {
"creator": {
"type": "account",
"defaultValue": "__signer"
},
"fee": {
"type": "amount",
"defaultValue": "3.000 HIVE"
},
"new_account_name": {
"type": "account"
},
"memo_key": {
"type": "string"
},
"json_metadata": {
"type": "string"
},
"owner": {
"type": "object"
},
"active": {
"type": "object"
},
"posting": {
"type": "object"
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"create_claimed_account": {
"name": "Create account with account credits",
"authority": "active",
"schema": {
"creator": {
"type": "account",
"defaultValue": "__signer"
},
"new_account_name": {
"type": "account"
},
"memo_key": {
"type": "string"
},
"json_metadata": {
"type": "string"
},
"owner": {
"type": "object"
},
"active": {
"type": "object"
},
"posting": {
"type": "object"
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"vote": {
"name": "Vote",
"authority": "posting",
"schema": {
"voter": {
"type": "account",
"defaultValue": "__signer"
},
"author": {
"type": "account"
},
"permlink": {
"type": "string"
},
"weight": {
"type": "int",
"defaultValue": 10000
}
}
},
"limit_order_create": {
"name": "Create limit order",
"authority": "active",
"schema": {
"owner": {
"type": "account",
"defaultValue": "__signer"
},
"orderid": {
"type": "int"
},
"amount_to_sell": {
"type": "amount"
},
"min_to_receive": {
"type": "amount"
},
"fill_or_kill": {
"type": "bool"
},
"expiration": {
"type": "time"
}
}
},
"limit_order_create2": {
"name": "Create limit order",
"authority": "active",
"schema": {
"owner": {
"type": "account",
"defaultValue": "__signer"
},
"orderid": {
"type": "int"
},
"amount_to_sell": {
"type": "amount"
},
"exchange_rate": {
"type": "object"
},
"fill_or_kill": {
"type": "bool"
},
"expiration": {
"type": "time"
}
}
},
"limit_order_cancel": {
"name": "Cancel limit order",
"authority": "active",
"schema": {
"owner": {
"type": "account",
"defaultValue": "__signer"
},
"orderid": {
"type": "int"
}
}
},
"claim_reward_balance": {
"name": "Redeem rewards",
"authority": "posting",
"schema": {
"account": {
"type": "account",
"defaultValue": "__signer"
},
"reward_hive": {
"type": "amount"
},
"reward_hbd": {
"type": "amount"
},
"reward_vests": {
"type": "amount"
}
}
},
"comment": {
"name": "Post or comment",
"authority": "posting",
"schema": {
"parent_author": {
"type": "account",
"defaultValue": ""
},
"parent_permlink": {
"type": "string"
},
"author": {
"type": "account",
"defaultValue": "__signer"
},
"permlink": {
"type": "string"
},
"title": {
"type": "string"
},
"body": {
"type": "string"
},
"json_metadata": {
"type": "string"
}
}
},
"comment_options": {
"name": "Post or comment options",
"authority": "posting",
"schema": {
"author": {
"type": "account",
"defaultValue": "__signer"
},
"permlink": {
"type": "string"
},
"allow_curation_rewards": {
"type": "bool",
"defaultValue": true
},
"allow_votes": {
"type": "bool",
"defaultValue": true
},
"max_accepted_payout": {
"type": "amount",
"defaultValue": "1000000.000 SBD"
},
"percent_hbd": {
"type": "int",
"defaultValue": 10000
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"custom_json": {
"name": "Custom operation",
"authority": "posting",
"schema": {
"required_auths": {
"type": "array",
"defaultValue": []
},
"required_posting_auths": {
"name": "posting auths",
"type": "array",
"defaultValue": ["__signer"]
},
"id": {
"type": "string"
},
"json": {
"type": "json"
}
}
},
"delete_comment": {
"name": "Delete comment",
"authority": "posting",
"schema": {
"author": {
"type": "account",
"defaultValue": "__signer"
},
"permlink": {
"type": "string"
}
}
},
"account_update": {
"name": "Update account (active)",
"authority": "active",
"schema": {
"account": {
"type": "account",
"defaultValue": "__signer"
},
"owner": {
"type": "object"
},
"active": {
"type": "object"
},
"posting": {
"type": "object"
},
"memo_key": {
"type": "string"
},
"json_metadata": {
"type": "json"
}
}
},
"account_update2": {
"name": "Update account (posting)",
"authority": "posting",
"schema": {
"account": {
"type": "account",
"defaultValue": "__signer"
},
"json_metadata": {
"type": "json",
"defaultValue": ""
},
"posting_json_metadata": {
"type": "json"
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"change_recovery_account": {
"name": "Change recovery account",
"authority": "owner",
"schema": {
"account_to_recover": {
"type": "account",
"defaultValue": "__signer"
},
"new_recovery_account": {
"type": "account"
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"create_proposal": {
"name": "Create proposal",
"authority": "active",
"schema": {
"creator": {
"type": "account",
"defaultValue": "__signer"
},
"receiver": {
"type": "account",
"defaultValue": "__signer"
},
"start_date": {
"type": "time"
},
"end_date": {
"type": "time"
},
"daily_pay": {
"type": "amount"
},
"subject": {
"type": "string"
},
"permlink": {
"type": "string"
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"remove_proposal": {
"name": "Remove proposal",
"authority": "active",
"schema": {
"proposal_owner": {
"type": "account",
"defaultValue": "__signer"
},
"proposal_ids": {
"type": "array"
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"update_proposal_votes": {
"name": "Update proposal votes",
"authority": "active",
"schema": {
"voter": {
"type": "account",
"defaultValue": "__signer"
},
"proposal_ids": {
"type": "array"
},
"approve": {
"type": "bool",
"defaultValue": true
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
},
"update_proposal": {
"name": "Update proposal",
"authority": "active",
"schema": {
"creator": {
"type": "account",
"defaultValue": "__signer"
},
"proposal_id": {
"type": "int"
},
"daily_pay": {
"type": "amount"
},
"subject": {
"type": "string"
},
"permlink": {
"type": "string"
},
"extensions": {
"type": "array",
"defaultValue": []
}
}
}
}