UNPKG

@aeternity/aepp-sdk

Version:

SDK for the æternity blockchain

394 lines 12.6 kB
/* eslint-disable consistent-return */ /* eslint-disable default-case */ /* eslint-disable @typescript-eslint/no-use-before-define */ import { changeStatus, changeState, notify, emit, disconnect } from './internal.js'; import { unpackTx, buildTx } from '../tx/builder/index.js'; import { decode } from '../utils/encoder.js'; import { IllegalArgumentError, InsufficientBalanceError, ChannelConnectionError, UnexpectedChannelMessageError, ChannelError } from '../utils/errors.js'; import { Tag } from '../tx/builder/constants.js'; import { snakeToPascal } from '../utils/string.js'; export async function appendSignature(tx, signFn) { const { signatures, encodedTx } = unpackTx(tx, Tag.SignedTx); const payloadTx = buildTx(encodedTx); const result = await signFn(payloadTx); if (typeof result === 'string') { const { signatures: signatures2 } = unpackTx(result, Tag.SignedTx); return buildTx({ tag: Tag.SignedTx, signatures: signatures.concat(signatures2), encodedTx: decode(payloadTx) }); } return result; } export async function signAndNotify(channel, method, data, signFn) { let signedTx; if (data.tx != null) signedTx = await signFn(data.tx);else if (data.signed_tx != null) signedTx = await appendSignature(data.signed_tx, signFn);else throw new ChannelError("Can't find transaction in message"); const isError = typeof signedTx !== 'string'; const key = data.tx != null ? 'tx' : 'signed_tx'; notify(channel, method, isError ? { error: signedTx !== null && signedTx !== void 0 ? signedTx : 1 } : { [key]: signedTx }); return isError; } export function handleUnexpectedMessage(_channel, message, state) { state?.reject?.(Object.assign(new UnexpectedChannelMessageError(`Unexpected message received:\n\n${JSON.stringify(message)}`), { wsMessage: message })); return { handler: channelOpen }; } export function awaitingCompletion(channel, message, state, onSuccess) { if (onSuccess != null && message.method === 'channels.update') { return onSuccess(channel, message, state); } if (message.method === 'channels.conflict') { state.resolve({ accepted: false, errorCode: message.params.data.error_code, errorMessage: message.params.data.error_msg }); return { handler: channelOpen }; } if (message.method === 'channels.info') { if (message.params.data.event === 'aborted_update') { state.resolve({ accepted: false }); return { handler: channelOpen }; } } if (message.error != null) { const codes = message.error.data.map(d => d.code); if (codes.includes(1001)) { state.reject(new InsufficientBalanceError('Insufficient balance')); } else if (codes.includes(1002)) { state.reject(new IllegalArgumentError('Amount cannot be negative')); } else { state.reject(new ChannelConnectionError(message.error.message)); } return { handler: channelOpen }; } return handleUnexpectedMessage(channel, message, state); } export function awaitingConnection(channel, message) { if (message.method === 'channels.info') { const channelInfoStatus = message.params.data.event; let nextStatus = null; if (channelInfoStatus === 'channel_accept') nextStatus = 'accepted'; if (channelInfoStatus === 'funding_created') nextStatus = 'halfSigned'; if (nextStatus != null) { changeStatus(channel, nextStatus); return { handler: awaitingChannelCreateTx }; } if (message.params.data.event === 'channel_reestablished') { return { handler: awaitingOpenConfirmation }; } if (message.params.data.event === 'fsm_up') { channel._fsmId = message.params.data.fsm_id; return { handler: awaitingConnection }; } return { handler: awaitingConnection }; } if (message.method === 'channels.error') { emit(channel, 'error', new ChannelConnectionError(message?.payload?.message)); return { handler: channelClosed }; } } export async function awaitingReestablish(channel, message, state) { if (message.method === 'channels.info' && message.params.data.event === 'fsm_up') { channel._fsmId = message.params.data.fsm_id; return { handler: function awaitingChannelReestablished(_, message2, state2) { if (message2.method === 'channels.info' && message2.params.data.event === 'channel_reestablished') return { handler: awaitingOpenConfirmation }; return handleUnexpectedMessage(channel, message2, state2); } }; } return handleUnexpectedMessage(channel, message, state); } export async function awaitingReconnection(channel, message, state) { if (message.method === 'channels.info' && message.params.data.event === 'fsm_up') { channel._fsmId = message.params.data.fsm_id; const { signedTx } = await channel.state(); changeState(channel, signedTx == null ? '' : buildTx(signedTx)); return { handler: channelOpen }; } return handleUnexpectedMessage(channel, message, state); } export async function awaitingChannelCreateTx(channel, message) { const tag = channel._options.role === 'initiator' ? 'initiator_sign' : 'responder_sign'; if (message.method === `channels.sign.${tag}`) { await signAndNotify(channel, `channels.${tag}`, message.params.data, async tx => channel._options.sign(tag, tx)); return { handler: awaitingOnChainTx }; } } export function awaitingOnChainTx(channel, message) { function awaitingBlockInclusion(_, message2) { if (message2.method === 'channels.info') { switch (message2.params.data.event) { case 'funding_created': case 'own_funding_locked': return { handler: awaitingBlockInclusion }; case 'funding_locked': return { handler: awaitingOpenConfirmation }; } } if (message2.method === 'channels.on_chain_tx') { emit(channel, 'onChainTx', message2.params.data.tx, { info: message2.params.data.info, type: message2.params.data.type }); return { handler: awaitingBlockInclusion }; } } if (message.method === 'channels.on_chain_tx') { const { info } = message.params.data; const { role } = channel._options; if (info === 'funding_signed' && role === 'initiator' || info === 'funding_created' && role === 'responder') { return { handler: awaitingBlockInclusion }; } } if (message.method === 'channels.info' && message.params.data.event === 'funding_signed' && channel._options.role === 'initiator') { channel._channelId = message.params.channel_id; changeStatus(channel, 'signed'); return { handler: awaitingOnChainTx }; } } function awaitingOpenConfirmation(channel, message, state) { if (message.method === 'channels.info' && message.params.data.event === 'open') { channel._channelId = message.params.channel_id; return { handler: function awaitingChannelsUpdate(_, message2, state2) { if (message2.method === 'channels.update') { changeState(channel, message2.params.data.state); return { handler: channelOpen }; } return handleUnexpectedMessage(channel, message2, state2); } }; } return handleUnexpectedMessage(channel, message, state); } export async function channelOpen(channel, message, state) { switch (message.method) { case 'channels.info': switch (message.params.data.event) { case 'update': case 'withdraw_created': case 'deposit_created': return { handler: awaitingTxSignRequest }; case 'own_withdraw_locked': case 'withdraw_locked': case 'own_deposit_locked': case 'deposit_locked': case 'peer_disconnected': case 'channel_reestablished': case 'open': // TODO: Better handling of peer_disconnected event. // // We should enter intermediate state where offchain transactions // are blocked until channel is reestablished. emit(channel, snakeToPascal(message.params.data.event)); return { handler: channelOpen }; case 'fsm_up': channel._fsmId = message.params.data.fsm_id; return { handler: channelOpen }; case 'timeout': case 'close_mutual': return { handler: channelOpen }; case 'closing': changeStatus(channel, 'closing'); return { handler: channelOpen }; case 'closed_confirmed': changeStatus(channel, 'closed'); return { handler: channelClosed }; case 'died': changeStatus(channel, 'died'); return { handler: channelClosed }; case 'shutdown': return { handler: channelOpen }; } break; case 'channels.on_chain_tx': emit(channel, 'onChainTx', message.params.data.tx, { info: message.params.data.info, type: message.params.data.type }); return { handler: channelOpen }; case 'channels.leave': // TODO: emit event return { handler: channelOpen }; case 'channels.update': changeState(channel, message.params.data.state); return { handler: channelOpen }; case 'channels.sign.shutdown_sign_ack': return awaitingTxSignRequest(channel, message, state); } } channelOpen.enter = channel => { changeStatus(channel, 'open'); }; async function awaitingTxSignRequest(channel, message, state) { var _message$method$match; const [, tag] = (_message$method$match = message.method.match(/^channels\.sign\.([^.]+)$/)) !== null && _message$method$match !== void 0 ? _message$method$match : []; if (tag == null) return handleUnexpectedMessage(channel, message, state); const isError = await signAndNotify(channel, `channels.${tag}`, message.params.data, async tx => channel._options.sign(tag, tx, { updates: message.params.data.updates })); function awaitingUpdateConflict(_, message2) { if (message2.error != null) { return { handler: awaitingUpdateConflict, state }; } if (message2.method === 'channels.conflict') { return { handler: channelOpen }; } return handleUnexpectedMessage(channel, message2, state); } return isError ? { handler: awaitingUpdateConflict, state } : { handler: channelOpen }; } export async function awaitingShutdownTx(channel, message, state) { if (message.method !== 'channels.sign.shutdown_sign') { return handleUnexpectedMessage(channel, message, state); } await signAndNotify(channel, 'channels.shutdown_sign', message.params.data, async tx => state.sign(tx)); return { handler(_, message2) { if (message2.method !== 'channels.on_chain_tx') { return handleUnexpectedMessage(channel, message2, state); } // state.resolve(message.params.data.tx) return { handler: channelClosed, state }; }, state }; } export function awaitingLeave(channel, message, state) { if (message.method === 'channels.leave') { state.resolve({ channelId: message.params.channel_id, signedTx: message.params.data.state }); disconnect(channel); return { handler: channelClosed }; } if (message.method === 'channels.error') { state.reject(new ChannelConnectionError(message.data.message)); return { handler: channelOpen }; } return handleUnexpectedMessage(channel, message, state); } export function channelClosed(_channel, message, state) { if (state == null) return { handler: channelClosed }; if (message.params.data.event === 'closing') return { handler: channelClosed, state }; if (message.params.data.info === 'channel_closed') { state.closeTx = message.params.data.tx; return { handler: channelClosed, state }; } if (message.params.data.event === 'closed_confirmed') { state.resolve(state.closeTx); return { handler: channelClosed }; } return { handler: channelClosed, state }; } //# sourceMappingURL=handlers.js.map