@radixdlt/application
Version:
A JavaScript client library for interacting with the Radix Distributed Ledger.
265 lines • 12.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransactionIntentBuilder = exports.flatMapAddressesOf = exports.getUniqueAddresses = exports.isUnstakeTokensAction = exports.isStakeTokensAction = exports.isTransferTokensAction = exports.singleRecipientFromActions = void 0;
const actions_1 = require("../actions");
const account_1 = require("../../../account");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const actions_2 = require("../actions");
const neverthrow_1 = require("neverthrow");
const crypto_1 = require("../../../crypto");
const prelude_ts_1 = require("prelude-ts");
const primitives_1 = require("../../../primitives");
const util_1 = require("../../../util");
const singleRecipientFromActions = (mine, actions) => {
const others = (0, exports.flatMapAddressesOf)({ actions })
.map(a => a.publicKey)
.filter(a => !a.equals(mine));
if (others.length > 1) {
const errMsg = `Cannot encrypt/decrypt message for a transaction containing more than one recipient addresses.`;
util_1.log.error(errMsg);
throw new Error(errMsg);
}
const toSelf = others.length === 0;
if (toSelf) {
util_1.log.debug(`Encrypted message is to oneself.`);
}
return (0, neverthrow_1.ok)(toSelf ? mine : others[0]);
};
exports.singleRecipientFromActions = singleRecipientFromActions;
const ensureSingleRecipient = (input) => (0, util_1.toObservableFromResult)((0, exports.singleRecipientFromActions)(input.encryptingAccount.publicKey, input.intendedActionsFrom.intendedActions)).pipe((0, operators_1.map)(singleRecipientPublicKey => ({
encryptingAccount: input.encryptingAccount,
singleRecipientPublicKey: singleRecipientPublicKey,
})));
const mustHaveAtLeastOneAction = new Error('A transaction intent must contain at least one of the following actions: TransferToken, StakeTokens or UnstakeTokens');
const isTransferTokensAction = (something) => {
const inspection = something;
return (inspection.type === actions_1.ActionType.TOKEN_TRANSFER &&
!!inspection.to_account &&
!!inspection.from_account &&
(0, primitives_1.isAmount)(inspection.amount) &&
(0, account_1.isResourceIdentifier)(inspection.rri));
};
exports.isTransferTokensAction = isTransferTokensAction;
const isStakeTokensAction = (something) => {
const inspection = something;
return (inspection.type === actions_1.ActionType.STAKE_TOKENS &&
!!inspection.from_account &&
!!inspection.to_validator &&
(0, primitives_1.isAmount)(inspection.amount));
};
exports.isStakeTokensAction = isStakeTokensAction;
const isUnstakeTokensAction = (something) => {
const inspection = something;
return (inspection.type === actions_1.ActionType.UNSTAKE_TOKENS &&
!!inspection.from_validator &&
!!inspection.to_account &&
(0, primitives_1.isAmount)(inspection.amount));
};
exports.isUnstakeTokensAction = isUnstakeTokensAction;
const decodeApiAddress = (address) => {
const result = account_1.AccountAddress.fromUnsafe(address);
return result._unsafeUnwrap();
};
const getUniqueAddresses = (input) => {
var _a, _b;
const action = input.action;
const includeFrom = (_a = input.includeFrom) !== null && _a !== void 0 ? _a : true;
const includeTo = (_b = input.includeTo) !== null && _b !== void 0 ? _b : true;
if ((0, exports.isTransferTokensAction)(action)) {
const addresses = [];
if (includeTo) {
addresses.push(decodeApiAddress(action.to_account));
}
if (includeFrom) {
addresses.push(decodeApiAddress(action.from_account));
}
return addresses;
}
else if ((0, exports.isStakeTokensAction)(action)) {
const addresses = [];
if (includeFrom) {
addresses.push(decodeApiAddress(action.from_account));
}
return addresses;
}
else if ((0, exports.isUnstakeTokensAction)(action)) {
const addresses = [];
if (includeFrom) {
addresses.push(decodeApiAddress(action.to_account));
}
return addresses;
}
else {
return [];
}
};
exports.getUniqueAddresses = getUniqueAddresses;
const flatMapAddressesOf = (input) => {
const { actions, includeFrom, includeTo } = input;
const flatMapped = actions.reduce((acc, action) => {
const uniqueAddressOfAction = (0, exports.getUniqueAddresses)({
action,
includeFrom,
includeTo,
});
return acc.concat(...uniqueAddressOfAction);
}, []);
const set = new Set();
return flatMapped.filter(a => {
const str = a.toString();
const hasNt = !set.has(str);
set.add(str);
return hasNt;
});
};
exports.flatMapAddressesOf = flatMapAddressesOf;
const isTransactionIntentBuilderEncryptInput = (something) => {
const inspection = something;
return (inspection.encryptMessageIfAnyWithAccount !== undefined &&
(0, rxjs_1.isObservable)(inspection.encryptMessageIfAnyWithAccount) &&
(inspection.spendingSender !== undefined
? (0, rxjs_1.isObservable)(inspection.spendingSender)
: true));
};
const isTransactionIntentBuilderDoNotEncryptInput = (something) => {
if (isTransactionIntentBuilderEncryptInput(something)) {
return false;
}
const inspection = something;
return (inspection.spendingSender !== undefined &&
(0, rxjs_1.isObservable)(inspection.spendingSender));
};
const isTransactionIntentBuilderDoNotEncryptOption = (something) => {
const inspection = something;
return (inspection.skipEncryptionOfMessageIfAny !== undefined &&
isTransactionIntentBuilderDoNotEncryptInput(inspection.skipEncryptionOfMessageIfAny));
};
const create = () => {
const intermediateActions = [];
let maybePlaintextMsgToEncrypt = prelude_ts_1.Option.none();
const snapshotState = () => ({
actionInputs: intermediateActions,
message: maybePlaintextMsgToEncrypt.getOrUndefined(),
});
const snapshotBuilderState = () => ({
__state: snapshotState(),
});
const addAction = (input, type) => {
intermediateActions.push(Object.assign({ type }, input));
return Object.assign(Object.assign({}, methods), snapshotBuilderState());
};
const transferTokens = (input) => addAction(input, 'transfer');
const stakeTokens = (input) => addAction(input, 'stake');
const unstakeTokens = (input) => addAction(input, 'unstake');
const replaceAnyPreviousMessageWithNew = (newMessage) => {
maybePlaintextMsgToEncrypt = prelude_ts_1.Option.some(newMessage);
return Object.assign(Object.assign({}, methods), snapshotBuilderState());
};
const intendedActionsFromIntermediateActions = (from) => {
if (intermediateActions.length === 0)
return (0, neverthrow_1.err)(mustHaveAtLeastOneAction);
return (0, neverthrow_1.combine)(intermediateActions.map((i) => {
const intermediateActionType = i.type;
if (intermediateActionType === 'transfer') {
if ((0, actions_2.isTransferTokensInput)(i)) {
return actions_2.IntendedTransferTokens.create(i, from);
}
else {
throw new Error('Not transfer tokens input');
}
}
else if (intermediateActionType === 'stake') {
if ((0, actions_2.isStakeTokensInput)(i)) {
return actions_2.IntendedStakeTokens.create(i, from);
}
else {
throw new Error('Not stake tokens input');
}
}
else if (intermediateActionType === 'unstake') {
return actions_2.IntendedUnstakeTokens.create(i, from);
}
else {
return (0, neverthrow_1.err)(new Error('Incorrect implementation, forgot something...'));
}
})).map(intendedActions => ({ intendedActions, from }));
};
const syncBuildDoNotEncryptMessageIfAny = (from) => intendedActionsFromIntermediateActions(from).map(({ intendedActions }) => ({
actions: intendedActions,
message: maybePlaintextMsgToEncrypt
.map(msg => msg.plaintext
? crypto_1.Message.createPlaintext(msg.plaintext).bytes
: undefined)
.getOrUndefined(),
}));
const build = (options) => {
var _a;
if (isTransactionIntentBuilderDoNotEncryptOption(options)) {
if (maybePlaintextMsgToEncrypt.map(m => m.encrypt).getOrElse(false)) {
const errMsg = `Message in transaction specifies it should be encrypted, but input to TransactionIntentBuilder build method specifies that it (the builder) should not encrypt the message, and does not provide any account with which we can perform encryption.`;
console.error(errMsg);
util_1.log.error(errMsg);
return (0, rxjs_1.throwError)(new Error(errMsg));
}
return options.skipEncryptionOfMessageIfAny.spendingSender.pipe((0, operators_1.mergeMap)((from) => (0, util_1.toObservableFromResult)(syncBuildDoNotEncryptMessageIfAny(from))));
}
if (!isTransactionIntentBuilderEncryptInput(options)) {
throw new Error('Incorrect implementation');
}
const encryptingAccount$ = options.encryptMessageIfAnyWithAccount;
const spendingSender = (_a = options.spendingSender) !== null && _a !== void 0 ? _a : options.encryptMessageIfAnyWithAccount.pipe((0, operators_1.map)(account => account.address));
return spendingSender.pipe((0, operators_1.mergeMap)((from) => (0, util_1.toObservableFromResult)(intendedActionsFromIntermediateActions(from))), (0, operators_1.mergeMap)((intendedActionsFrom) => {
const transactionIntentWithoutEncryption = (plaintextMessage) => {
util_1.log.info(`Successfully built transaction. Actions: ${intendedActionsFrom.intendedActions
.map(action => action.type)
.toString()}`);
return (0, rxjs_1.of)({
actions: intendedActionsFrom.intendedActions,
message: plaintextMessage !== undefined
? crypto_1.MessageEncryption.encodePlaintext(plaintextMessage)
: undefined,
});
};
return maybePlaintextMsgToEncrypt.match({
Some: msgInTx => {
if (!msgInTx.encrypt) {
const errMsg = 'You are trying to encrypt a message which was specified not to be encrypted.';
console.error(errMsg);
util_1.log.error(errMsg);
return (0, rxjs_1.throwError)(new Error(errMsg));
}
return encryptingAccount$.pipe((0, operators_1.mergeMap)((encryptingAccount) => ensureSingleRecipient({
intendedActionsFrom,
encryptingAccount,
})), (0, operators_1.mergeMap)((actors) => actors.encryptingAccount.encrypt({
plaintext: msgInTx.plaintext,
publicKeyOfOtherParty: actors.singleRecipientPublicKey,
})), (0, operators_1.map)((encryptedMessage) => {
util_1.log.info(`Successfully built transaction with encrypted message. Actions: ${intendedActionsFrom.intendedActions
.map(action => action.type)
.toString()}`);
return {
actions: intendedActionsFrom.intendedActions,
message: encryptedMessage.combined(),
};
}));
},
None: () => transactionIntentWithoutEncryption(undefined),
});
}));
};
const methods = {
transferTokens,
stakeTokens,
unstakeTokens,
build,
message: replaceAnyPreviousMessageWithNew,
__syncBuildDoNotEncryptMessageIfAny: syncBuildDoNotEncryptMessageIfAny,
};
return Object.assign(Object.assign({}, snapshotBuilderState()), methods);
};
exports.TransactionIntentBuilder = {
create,
};
//# sourceMappingURL=transactionIntentBuilder.js.map