@radixdlt/application
Version:
A JavaScript client library for interacting with the Radix Distributed Ledger.
617 lines (577 loc) • 18.4 kB
text/typescript
import {
TokenInfoEndpoint,
NativeTokenInfoEndpoint,
AccountBalancesEndpoint,
BuildTransactionEndpoint,
FinalizeTransactionEndpoint,
TransactionEndpoint,
Decoded,
StakePositionsEndpoint,
UnstakePositionsEndpoint,
AccountTransactionsEndpoint,
ValidatorEndpoint,
ValidatorsEndpoint,
GatewayEndpoint,
RecentTransactionEndpoint,
} from './_types'
import {
AccountStakeEntry,
AccountUnstakeEntry,
Action,
BurnTokens,
CreateTokenDefinition,
MintTokens,
ReturnOfAPICall,
StakeTokens,
TokenAmount,
TransferTokens,
UnstakeTokens,
Validator as ValidatorRaw,
} from '@radixdlt/networking'
import { Result } from 'neverthrow'
import {
ResourceIdentifier,
ResourceIdentifierT,
ValidatorAddress,
ValidatorAddressT,
AccountAddress,
AccountAddressT,
} from '@radixdlt/account'
import { Amount, AmountT, Network } from '@radixdlt/primitives'
import {
ActionType,
ExecutedAction,
ExecutedBurnTokensAction,
ExecutedCreateTokenDefinitionAction,
ExecutedMintTokensAction,
ExecutedStakeTokensAction,
ExecutedTransferTokensAction,
ExecutedUnstakeTokensAction,
SimpleTransactionHistory,
TransactionIdentifier,
TransactionIdentifierT,
TransactionStatus as TransactionStatusEnum,
} from '../..'
import { ok, combine } from 'neverthrow'
const transformTokenAmount = (amount: TokenAmount) => [
Amount.fromUnsafe(amount.value),
ResourceIdentifier.fromUnsafe(amount.token_identifier.rri),
]
export const handleGatewayResponse = (
json: ReturnOfAPICall<'gatewayPost'>,
): Result<GatewayEndpoint.DecodedResponse, Error[]> =>
ok({
// @ts-ignore
network: json.data.network_identifier.network as Network,
}).mapErr(e => [e] as Error[])
export const handleTokenInfoResponse = (
json: ReturnOfAPICall<'tokenPost'>,
): Result<TokenInfoEndpoint.DecodedResponse, Error[]> =>
combine([
ResourceIdentifier.fromUnsafe(json.data.token.token_identifier.rri),
Amount.fromUnsafe(json.data.token.token_properties.granularity),
Amount.fromUnsafe(json.data.token.token_supply.value),
])
.map(values => ({
name: json.data.token.token_properties.name ?? '',
rri: values[0] as ResourceIdentifierT,
symbol: json.data.token.token_properties.symbol,
description: json.data.token.token_properties.description,
granularity: values[1] as AmountT,
isSupplyMutable: json.data.token.token_properties.is_supply_mutable,
currentSupply: values[2] as AmountT,
tokenInfoURL: json.data.token.token_properties.url
? new URL(json.data.token.token_properties.url)
: undefined,
iconURL: json.data.token.token_properties.icon_url
? new URL(json.data.token.token_properties.icon_url)
: undefined,
}))
.mapErr(e => [e])
export const handleNativeTokenResponse = (
json: ReturnOfAPICall<'tokenNativePost'>,
): Result<NativeTokenInfoEndpoint.DecodedResponse, Error[]> =>
combine([
ResourceIdentifier.fromUnsafe(json.data.token.token_identifier.rri),
Amount.fromUnsafe(json.data.token.token_properties.granularity),
Amount.fromUnsafe(json.data.token.token_supply.value),
])
.map(values => ({
name: json.data.token.token_properties.name ?? '',
rri: values[0] as ResourceIdentifierT,
symbol: json.data.token.token_properties.symbol,
description: json.data.token.token_properties.description,
granularity: values[1] as AmountT,
isSupplyMutable: json.data.token.token_properties.is_supply_mutable,
currentSupply: values[2] as AmountT,
tokenInfoURL: json.data.token.token_properties.url
? new URL(json.data.token.token_properties.url)
: undefined,
iconURL: json.data.token.token_properties.icon_url
? new URL(json.data.token.token_properties.icon_url)
: undefined,
}))
.mapErr(e => [e])
const transformStakeEntry = (stake: AccountStakeEntry) =>
combine([
ValidatorAddress.fromUnsafe(stake.validator_identifier.address),
Amount.fromUnsafe(stake.delegated_stake.value),
]).map(value => ({
validator: value[0] as ValidatorAddressT,
amount: value[1] as AmountT,
}))
const transformUnstakeEntry = (unstake: AccountUnstakeEntry) =>
combine([
ValidatorAddress.fromUnsafe(unstake.validator_identifier.address),
Amount.fromUnsafe(unstake.unstaking_amount.value),
ok<number, Error>(unstake.epochs_until_unlocked),
]).map(value => ({
validator: value[0] as ValidatorAddressT,
amount: value[1] as AmountT,
epochsUntil: value[2] as number,
}))
export const handleStakePositionsResponse = (
json: ReturnOfAPICall<'accountStakesPost'>,
): Result<StakePositionsEndpoint.DecodedResponse, Error[]> =>
combine(json.data.stakes.map(transformStakeEntry))
.andThen(stakes =>
combine(
json.data.pending_stakes.map(transformStakeEntry),
).map(pendingStakes => ({ stakes, pendingStakes })),
)
.mapErr(e => [e])
export const handleUnstakePositionsResponse = (
json: ReturnOfAPICall<'accountUnstakesPost'>,
): Result<UnstakePositionsEndpoint.DecodedResponse, Error[]> => {
return combine(json.data.pending_unstakes.map(transformUnstakeEntry))
.map(pendingUnstakes =>
combine(json.data.unstakes.map(transformUnstakeEntry)).map(
unstakes => ({
pendingUnstakes,
unstakes,
}),
),
)
.andThen(res => res)
.mapErr(e => [e])
}
export const handleAccountTransactionsResponse = (
json: ReturnOfAPICall<'accountTransactionsPost'>,
): Result<AccountTransactionsEndpoint.DecodedResponse, Error[]> =>
combine(json.data.transactions.map(handleTx)).map(
(transactions): SimpleTransactionHistory => ({
cursor: json.data.next_cursor as string,
// @ts-ignore
transactions,
}),
)
export const handleRecentTransactionResponse = (
json: ReturnOfAPICall<'transactionRecentPost'>,
): Result<RecentTransactionEndpoint.DecodedResponse, Error[]> =>
combine(json.data.transactions.map(handleTx)).map(
(transactions): SimpleTransactionHistory => ({
cursor: json.data.next_cursor as string,
// @ts-ignore
transactions,
}),
)
// export const handleAccountTransactionsResponse = (
// json: ReturnOfAPICall<'accountTransactionsPost'>,
// ) =>
// JSONDecoding.withDecoders(
// transactionIdentifierDecoder('hash'),
// dateDecoder('timestamp'),
// ...tokenDecoders,
// )
// .create<
// AccountTransactionsEndpoint.Response,
// AccountTransactionsEndpoint.DecodedResponse
// >()(json)
// .andThen(decoded =>
// hasRequiredProps('accountTransactions', decoded, [
// 'ledger_state',
// 'total_count',
// 'transactions',
// ]),
// )
/*
export const handleDerivetoken_identifierResponse = (
json: ReturnOfAPICall<'tokenDerivePost'>,
) =>
JSONDecoding.withDecoders(RRIDecoder('rri'))
.create<
DeriveTokenIdentifierEndpoint.Response,
DeriveTokenIdentifierEndpoint.DecodedResponse
>()(json)
.andThen(decoded =>
hasRequiredProps('deriveTokenIdentifier', decoded, [
'token_identifier',
]),
)
*/
const transformUrl = (url: string) => {
try {
return new URL(url)
} catch (error) {
return undefined
}
}
const transformValidator = (validator: ValidatorRaw) =>
combine([
ValidatorAddress.fromUnsafe(validator.validator_identifier.address),
AccountAddress.fromUnsafe(
validator.properties.owner_account_identifier.address,
),
Amount.fromUnsafe(validator.stake.value),
Amount.fromUnsafe(validator.info.owner_stake.value),
]).map(
(values): ValidatorEndpoint.DecodedResponse => ({
address: values[0] as ValidatorAddressT,
ownerAddress: values[1] as AccountAddressT,
name: validator.properties.name,
infoURL: transformUrl(validator.properties.url),
totalDelegatedStake: values[2] as AmountT,
ownerDelegation: values[3] as AmountT,
validatorFee: validator.properties.validator_fee_percentage,
registered: validator.properties.registered,
isExternalStakeAccepted:
validator.properties.external_stake_accepted,
uptimePercentage: validator.info.uptime.uptime_percentage,
proposalsMissed: validator.info.uptime.proposals_missed,
proposalsCompleted: validator.info.uptime.proposals_completed,
}),
)
export const handleAccountBalancesResponse = (
json: ReturnOfAPICall<'accountBalancesPost'>,
): Result<AccountBalancesEndpoint.DecodedResponse, Error[]> => {
const liquidBalancesResults = combine(
json.data.account_balances.liquid_balances.map(balance =>
combine([
Amount.fromUnsafe(balance.value),
ResourceIdentifier.fromUnsafe(balance.token_identifier.rri),
]).map(values => ({
value: values[0] as AmountT,
token_identifier: {
rri: values[1] as ResourceIdentifierT,
},
})),
),
)
return combine([
liquidBalancesResults.map(balances => ({ balances })),
ResourceIdentifier.fromUnsafe(
json.data.account_balances.staked_and_unstaking_balance
.token_identifier.rri,
),
Amount.fromUnsafe(
json.data.account_balances.staked_and_unstaking_balance.value,
),
])
.map(values => ({
ledger_state: {
...json.data.ledger_state,
timestamp: new Date(json.data.ledger_state.timestamp),
},
account_balances: {
// @ts-ignore
liquid_balances: values[0].balances as Decoded.TokenAmount[],
staked_and_unstaking_balance: {
token_identifier: {
rri: (values[1] as unknown) as ResourceIdentifierT,
},
value: (values[2] as unknown) as AmountT,
},
},
}))
.mapErr(e => [e])
}
export const handleValidatorResponse = (
json: ReturnOfAPICall<'validatorPost'>,
): Result<ValidatorEndpoint.DecodedResponse, Error[]> =>
transformValidator(json.data.validator).mapErr(e => [e])
export const handleValidatorsResponse = (
json: ReturnOfAPICall<'validatorsPost'>,
): Result<ValidatorsEndpoint.DecodedResponse, Error[]> =>
combine(json.data.validators.map(transformValidator))
.map(validators => ({ validators }))
.mapErr(e => [e])
/*
export const handleStakePositionsResponse = (
json: ReturnOfAPICall<'accountStakesPost'>,
) => combine([
]).mapErr(e => [e])
json.stakes.map(stake => combine([
ValidatorAddress.fromUnsafe(stake.validatorIdentifier.address),
Amount.fromUnsafe(stake.delegatedStake.value)
]).map(values => ({
validator: values[0] as ValidatorAddressT,
amount: values[1] as AmountT
})
export const handleUnstakePositionsResponse = (
json: ReturnOfAPICall<'accountUnstakesPost'>,
) =>
JSONDecoding.withDecoders(
RRIDecoder('rri'),
amountDecoder('value'),
validatorAddressDecoder('address'),
dateDecoder('timestamp'),
)
.create<
UnstakePositionsEndpoint.Response,
UnstakePositionsEndpoint.DecodedResponse
>()(json)
.andThen(decoded =>
hasRequiredProps('unstakePositions', decoded, [
'ledger_state',
'unstakes',
]),
)
export const handleAccountTransactionsResponse = (
json: ReturnOfAPICall<'accountTransactionsPost'>,
) =>
JSONDecoding.withDecoders(
transactionIdentifierDecoder('hash'),
dateDecoder('timestamp'),
...tokenDecoders,
)
.create<
AccountTransactionsEndpoint.Response,
AccountTransactionsEndpoint.DecodedResponse
>()(json)
.andThen(decoded =>
hasRequiredProps('accountTransactions', decoded, [
'ledger_state',
'total_count',
'transactions',
]),
)
export const handleValidatorResponse = (
json: ReturnOfAPICall<'validatorPost'>,
) =>
JSONDecoding.withDecoders(...validatorDecoders, dateDecoder('timestamp'))
.create<
ValidatorEndpoint.Response,
ValidatorEndpoint.DecodedResponse
>()(json)
.andThen(decoded =>
hasRequiredProps('validator', decoded, [
'ledger_state',
'validator',
]),
)
export const handleValidatorsResponse = (
json: ReturnOfAPICall<'validatorsPost'>,
) =>
JSONDecoding.withDecoders(...validatorDecoders, dateDecoder('timestamp'))
.create<
ValidatorsEndpoint.Response,
ValidatorsEndpoint.DecodedResponse
>()(json)
.andThen(decoded =>
hasRequiredProps('validators', decoded, [
'ledger_state',
'validators',
]),
)
export const handleTransactionRulesResponse = (
json: ReturnOfAPICall<'transactionRulesPost'>,
) =>
JSONDecoding.withDecoders(
amountDecoder('value'),
RRIDecoder('rri'),
dateDecoder('timestamp'),
)
.create<
TransactionRulesEndpoint.Response,
TransactionRulesEndpoint.DecodedResponse
>()(json)
.andThen(decoded =>
hasRequiredProps('transactionRules', decoded, [
'ledger_state',
'transaction_rules',
]),
)
*/
export const handleBuildTransactionResponse = (
json: ReturnOfAPICall<'transactionBuildPost'>,
): Result<BuildTransactionEndpoint.DecodedResponse, Error[]> =>
Amount.fromUnsafe(json.data.transaction_build.fee.value)
.map(amount => ({
transaction: {
blob: json.data.transaction_build.unsigned_transaction,
hashOfBlobToSign: json.data.transaction_build.payload_to_sign,
},
fee: amount,
}))
.mapErr(e => [e])
export const handleFinalizeTransactionResponse = (
json: ReturnOfAPICall<'transactionFinalizePost'>,
): Result<FinalizeTransactionEndpoint.DecodedResponse, Error[]> =>
TransactionIdentifier.create(json.data.transaction_identifier.hash)
.map(txID => ({
blob: json.data.signed_transaction,
txID,
}))
.mapErr(e => [e] as Error[])
export const handleSubmitTransactionResponse = (
json: ReturnOfAPICall<'transactionSubmitPost'>,
) =>
TransactionIdentifier.create(json.data.transaction_identifier.hash)
.map(txID => ({
txID,
}))
.mapErr(e => [e])
export const handleTransactionResponse = (
json: ReturnOfAPICall<'transactionStatusPost'>,
): Result<TransactionEndpoint.DecodedResponse, Error[]> =>
handleTx(json.data.transaction)
const handleTx = (
transaction: ReturnOfAPICall<'transactionStatusPost'>['data']['transaction'],
) => {
const transformAction = (action: Action): Result<ExecutedAction, Error> => {
const transformTransferTokenAction = (action: TransferTokens) =>
combine([
...(action.amount ? transformTokenAmount(action.amount) : []),
]).map(
(actionValue): ExecutedTransferTokensAction => ({
type: ActionType.TOKEN_TRANSFER,
to_account: action.to_account.address,
from_account: action.from_account.address,
amount: actionValue[0] as AmountT,
rri: actionValue[1] as ResourceIdentifierT,
}),
)
const transformStakeTokenAction = (
type: ActionType.STAKE_TOKENS,
action: StakeTokens,
) =>
combine([...transformTokenAmount(action.amount)]).map(
(
actionValue,
): ExecutedStakeTokensAction | ExecutedUnstakeTokensAction => ({
type,
amount: actionValue[0] as AmountT,
rri: actionValue[1] as ResourceIdentifierT,
to_validator: action.to_validator.address,
from_account: action.from_account.address,
}),
)
const transformUnstakeTokenAction = (
type: ActionType.UNSTAKE_TOKENS,
action: UnstakeTokens,
) =>
combine([
Amount.fromUnsafe(action.unstake_percentage ?? 0),
...(action.amount ? transformTokenAmount(action.amount) : []),
]).map((actionValue):
| ExecutedStakeTokensAction
| ExecutedUnstakeTokensAction => ({
type,
from_validator: action.from_validator.address,
to_account: action.to_account.address,
unstake_percentage: actionValue[0] as AmountT,
amount: actionValue[1] as AmountT,
rri: actionValue[2] as ResourceIdentifierT,
}))
const transformMintTokenAction = (
type: ActionType.MINT_TOKENS,
action: MintTokens,
) =>
combine(transformTokenAmount(action.amount)).map(
(actionValue): ExecutedMintTokensAction => ({
type: ActionType.MINT_TOKENS,
to_account: action.to_account.address,
amount: actionValue[0] as AmountT,
rri: actionValue[1] as ResourceIdentifierT,
}),
)
const transformBurnTokenAction = (
type: ActionType.BURN_TOKENS,
action: BurnTokens,
) =>
combine(transformTokenAmount(action.amount)).map(
(actionValue): ExecutedBurnTokensAction => ({
type: ActionType.BURN_TOKENS,
from_account: action.from_account.address,
amount: actionValue[0] as AmountT,
rri: actionValue[1] as ResourceIdentifierT,
}),
)
const transformCreateTokenDefinitionAction = (
type: ActionType.CREATE_TOKEN_DEFINITION,
action: CreateTokenDefinition,
) =>
combine(transformTokenAmount(action.token_supply)).map(
(actionValue): ExecutedCreateTokenDefinitionAction => ({
type: ActionType.CREATE_TOKEN_DEFINITION,
amount: actionValue[0] as AmountT,
rri: actionValue[1] as ResourceIdentifierT,
owner: action.token_properties.owner?.address,
to_account: action.to_account?.address,
name: action.token_properties.name,
description: action.token_properties.description,
icon_url: action.token_properties.icon_url,
url: action.token_properties.url,
symbol: action.token_properties.symbol,
granularity: action.token_properties.granularity,
is_supply_mutable:
action.token_properties.is_supply_mutable,
}),
)
switch (action.type) {
case 'TransferTokens':
return transformTransferTokenAction(action as TransferTokens)
case 'StakeTokens':
return transformStakeTokenAction(
ActionType.STAKE_TOKENS,
action as StakeTokens,
)
case 'UnstakeTokens':
return transformUnstakeTokenAction(
ActionType.UNSTAKE_TOKENS,
action as UnstakeTokens,
)
case 'MintTokens':
return transformMintTokenAction(
ActionType.MINT_TOKENS,
action as MintTokens,
)
case 'BurnTokens':
return transformBurnTokenAction(
ActionType.BURN_TOKENS,
action as BurnTokens,
)
case 'CreateTokenDefinition':
return transformCreateTokenDefinitionAction(
ActionType.CREATE_TOKEN_DEFINITION,
action as CreateTokenDefinition,
)
default:
return ok({ ...action, type: ActionType.OTHER })
}
}
return combine([
TransactionIdentifier.create(transaction.transaction_identifier.hash),
ok(
transaction.transaction_status.confirmed_time
? new Date(transaction.transaction_status.confirmed_time)
: null,
),
Amount.fromUnsafe(transaction.fee_paid.value),
ok(transaction.metadata.message ?? ''),
combine(transaction.actions.map(transformAction)).map(actions => ({
actions,
})),
ok(transaction.transaction_status.status),
])
.map(value => ({
txID: value[0] as TransactionIdentifierT,
sentAt: value[1] as Date,
fee: value[2] as AmountT,
message: value[3] as string,
// @ts-ignore
actions: value[4].actions as ExecutedAction[],
status: value[5] as TransactionStatusEnum,
}))
.mapErr(e => [e] as Error[])
}