accounts
Version:
Tempo Accounts SDK
428 lines (415 loc) • 21.9 kB
text/typescript
import { type DecodeErrorResultReturnType, type Hex, decodeErrorResult } from 'viem'
import { Abis } from 'viem/tempo'
import type { OneOf, UnionOmit } from '../internal/types.js'
type AllAbis = typeof Abis.all
type AbiErrorName = Extract<AllAbis[number], { type: 'error' }>['name']
/** Decoded execution error from a Tempo precompile revert. */
export type ExecutionError = OneOf<
| (DecodeErrorResultReturnType<AllAbis> & {
data: Hex
message: string
})
| { errorName: 'unknown'; message: string }
>
/** RPC-serialized execution error (bigints and numbers as hex). */
export type Rpc = UnionOmit<ExecutionError, 'args'>
/** Human-readable messages keyed by ABI error name. */
export const messages: Record<AbiErrorName, string> = {
AbdicationAlreadyScheduled: 'Capability {0} abdication is already scheduled.',
AccountAlreadyInitialized: 'Account already initialized.',
AccountNotAllowed: 'Account {0} is not allowed.',
ActiveLeaderRemoved: 'The active leader cannot be removed.',
ActiveSupplyWithoutBacking: 'Active supply has no backing.',
AddressAlreadyHasValidator: 'This address already has a validator.',
AddressNotReserved: 'Address is not reserved.',
AddressReserved: 'Address is reserved.',
AdminRelinquishmentFailed: 'Admin relinquishment failed.',
AlreadyInitialized: 'Already initialized.',
AmountExceedsDeposit: 'Amount exceeds deposit.',
AmountExceedsLimit: 'Amount exceeds limit.',
AmountNotIncreasing: 'Amount is not increasing.',
AmountOverflow: 'Amount overflow.',
AssetsNotArrived: 'Assets for request {0} have not arrived.',
BadFlow: 'Invalid execution flow.',
BaseAssetSweepBlocked: 'Base asset sweep is blocked by {0} unresolved claims.',
BelowMinimumOrderSize: 'Order size is below the minimum allowed ({0}).',
CallNotAllowed: 'This call is not allowed.',
CallbackDataTooLarge: 'Callback data too large.',
CallbackDidNotReturnToZone: 'Callback did not return to the zone.',
CallbackRejected: 'Callback rejected.',
CannotChangeWithinBlock: 'Cannot change within the same block.',
CannotSweepEngineBacking: 'Cannot sweep engine backing.',
CapabilityAbdicated: 'Capability {0} has been abdicated.',
CaptureAmountInvalid: 'Capture amount is invalid.',
ChannelAlreadyExists: 'Channel already exists.',
ChannelNotFound: 'Channel not found.',
ClaimNotClaimable: 'Claim {0} is not claimable.',
ClaimNotOpen: 'Claim {0} is not open.',
ClaimsExpired: 'Claims have expired.',
ClaimsNotExpired: 'Claims have not expired.',
CloseNotReady: 'Channel is not ready to close.',
ContractPaused: 'Contract is paused.',
CreditExceedsBalance:
'Credit for asset {0} exceeds the balance. Available: {1}, requested total: {2}.',
DepositBlockCapacityExceeded: 'Deposit block capacity of {0} exceeded.',
DepositOverflow: 'Deposit overflow.',
DepositTooSmall: 'Deposit too small.',
DepositsNotActive: 'Deposits are not active.',
DepositsNotPaused: 'Deposits are not paused.',
DepositsPaused: 'Deposits are paused.',
DistributorFeeUpdateNotPending: 'No distributor fee update is pending.',
DistributorFeeUpdateNotReady: 'Distributor fee update is not executable until {0}.',
DistributorTransferNotPending: 'No distributor transfer is pending.',
DivisionByZero: 'Division by zero.',
DuplicateAllowedAccount: 'Duplicate allowed account.',
DuplicateOwner: 'Duplicate owner.',
DuplicateRequest: 'Request {0} is duplicated.',
DuplicateZoneGateway: 'Duplicate zone gateway.',
EarnShareAlreadyExists: 'Earn share {0} already exists.',
EarnShareSupplyNotZero: 'Earn share supply is not zero.',
EarnShareSupplyOutOfBounds: 'Earn share supply {0} exceeds the maximum {1}.',
EarnVaultNotSet: 'Earn vault is not set.',
EmptyDeploymentId: 'Deployment ID cannot be empty.',
EmptyEarnShareMetadata: 'Empty earn share metadata.',
EmptyMetadata: 'Empty metadata.',
EmptyV1ValidatorSet: 'Validator set is empty.',
EncryptionKeyExpired: 'Encryption key {0}, activated at block {1}, expired at block {2}.',
EngineAlreadyBound: 'Earn vault {0} is already bound to an engine.',
EngineAlreadyExists: 'Engine {0} already exists.',
EngineAssetMismatch: 'Engine asset mismatch.',
EngineCapabilityUnsupported: 'Engine capability {0} is unsupported.',
EngineEarnVaultMismatch: 'Engine earn vault mismatch.',
EngineExitUnsupported: 'This engine does not support exits.',
EngineSharesTooLarge: 'Engine share amount {0} is too large.',
ExceedsMaxEarnShares: 'Maximum earn shares exceeded.',
ExcessiveConversionLoss:
'Conversion loss is excessive. Input shares: {0}, represented shares: {1}.',
ExcessiveMigrationClassificationError:
'Migration managed assets mismatch. Expected {0}, got {1}.',
ExpiringNonceHashNotSet: 'Expiring nonce hash is not set.',
ExpiringNonceReplay: 'Expiring nonce has already been used.',
ExpiringNonceSetFull: 'Expiring nonce set is full.',
ExpiryInPast: 'Expiry is in the past.',
FactoryCannotBeFinalOwner: 'The factory cannot be the final owner.',
FailedDeployment: 'Deployment failed.',
FeeValuationStale: 'Fee valuation is stale.',
FinalizedAssetMismatch: 'Finalized asset {0} does not match.',
GasFeeRateTooHigh: 'Gas fee rate too high.',
GasLimitTooHigh: 'Gas limit too high.',
GlobalFeeDisableUnavailable: 'Global fee disabling is unavailable.',
IdenticalAddresses: 'Addresses must be different.',
IdenticalTokens: 'Cannot swap a token for itself — input and output tokens must be different.',
Inactive: 'This contract is inactive.',
IncompatiblePolicyType: 'Incompatible policy type.',
IndexAlreadySet: 'Index already set.',
IngressAlreadyExists: 'Ingress "{0}" already exists.',
InitialEarnShareSupplyNotZero: 'Initial earn share supply must be zero.',
InsufficientAllowance: 'Insufficient allowance.',
InsufficientAssetsReceived: 'Insufficient assets received. Minimum: {0}, actual: {1}.',
InsufficientBalance: 'Insufficient balance. Required: {1}, available: {0}.',
InsufficientClaimableEarnShares: 'Insufficient claimable earn shares.',
InsufficientEngineShareSurplus:
'Insufficient engine share surplus. Available: {0}, requested: {1}.',
InsufficientFeeTokenBalance: 'Insufficient fee token balance.',
InsufficientFunding: 'Insufficient funding. Required: {0}, actual: {1}.',
InsufficientLiquidity: 'Not enough liquidity in the order book to fill this trade.',
InsufficientOutput:
'The output amount is below the slippage minimum — try increasing slippage tolerance.',
InsufficientReserves: 'Insufficient reserves.',
InvalidAccount: 'Invalid account.',
InvalidAdmin: 'Invalid admin.',
InvalidAmount: 'Invalid amount.',
InvalidArrayLengths: 'Invalid array lengths.',
InvalidAsset: 'Invalid asset. Expected {0}, got {1}.',
InvalidAuthority: 'Invalid authority.',
InvalidBaseToken: 'This token is not a valid base token for the requested pair.',
InvalidBlockNumber: 'Invalid block number.',
InvalidBouncebackRecipient: 'Invalid bounceback recipient.',
InvalidCallScope: 'Invalid call scope.',
InvalidCallbackTarget: 'Invalid callback target.',
InvalidCiphertextLength: 'Invalid ciphertext length. Expected {1}, got {0}.',
InvalidClaimAddress: 'Invalid claim address.',
InvalidClosedLoopConfig: 'Invalid closed loop config.',
InvalidConfig: 'Invalid config.',
InvalidCurrency: 'Invalid currency.',
InvalidCurrentTxHash: 'Invalid current transaction hash.',
InvalidDepositTransition: 'Invalid deposit transition.',
InvalidDistributorConfig: 'Invalid distributor config.',
InvalidDistributorConfiguration: 'Invalid distributor configuration.',
InvalidDistributorFeeConfiguration: 'Invalid distributor fee configuration.',
InvalidEarnDecimals: 'Invalid earn decimals.',
InvalidEarnFeesImplementation: 'Invalid earn fees implementation.',
InvalidEarnVault: 'Invalid earn vault.',
InvalidEarnVaultImplementation: 'Invalid earn vault implementation.',
InvalidEncryptedSenderCount: 'Invalid encrypted sender count. Expected {1}, got {0}.',
InvalidEncryptedSenderLength: 'Invalid encrypted sender length. Expected {1}, got {0}.',
InvalidEncryptionKeyIndex: 'Encryption key index {0} is invalid.',
InvalidEngine: 'Invalid engine.',
InvalidEngineShareScale: 'Invalid engine share scale.',
InvalidEphemeralPubkey: 'Invalid ephemeral pubkey.',
InvalidExpiringNonceExpiry: 'Invalid expiring nonce expiry.',
InvalidFallbackRecipient: 'Invalid fallback recipient.',
InvalidFeeClaimReceiver: 'Invalid fee claim receiver.',
InvalidFeeConfiguration: 'Invalid fee configuration.',
InvalidFlipTick: 'The flip-order price tick is invalid.',
InvalidFormat: 'Invalid format.',
InvalidKeyAuthorizationWitness: 'Invalid key authorization witness.',
InvalidKeyId: 'Invalid key ID.',
InvalidLeader: 'Invalid leader.',
InvalidLogoURI: 'Invalid logo URI.',
InvalidMasterAddress: 'Invalid master address.',
InvalidMaxRateAge: 'Invalid maximum rate age.',
InvalidMigrationIndex: 'Invalid migration index.',
InvalidMode: 'Invalid mode.',
InvalidMultiproof: 'Invalid multiproof.',
InvalidNonceKey: 'Invalid nonce key.',
InvalidOwner: 'Invalid owner.',
InvalidOwnerOrder: 'Invalid owner order.',
InvalidPayee: 'Invalid payee.',
InvalidPayload: 'Invalid payload.',
InvalidPolicyType: 'Invalid policy type.',
InvalidPrivateAsset: 'Invalid private asset.',
InvalidProof: 'Invalid proof.',
InvalidProofOfPossession: 'Invalid proof of possession.',
InvalidPublicKey: 'Invalid public key.',
InvalidQuorumCertificate: 'Invalid quorum certificate.',
InvalidQuoteToken: 'Invalid quote token.',
InvalidRateTimestamp: 'Rate timestamp {0} is invalid at current time {1}.',
InvalidReceipt: 'Invalid receipt.',
InvalidReceivePolicyType: 'Invalid receive policy type.',
InvalidRecipient: 'Invalid recipient.',
InvalidRecoveryAuthority: 'Invalid recovery authority.',
InvalidReturnToken: 'Invalid return token.',
InvalidRevealTo: 'Invalid reveal to.',
InvalidRoutePair: 'Invalid route pair.',
InvalidSequencerSet: 'Invalid sequencer set.',
InvalidSignature: 'Invalid signature.',
InvalidSignatureFormat: 'Invalid signature format.',
InvalidSignatureType: 'Invalid signature type.',
InvalidSourcePortal: 'Invalid source portal.',
InvalidSpendingLimit: 'Invalid spending limit.',
InvalidSupplyCap: 'Invalid supply cap.',
InvalidSwapCalculation: 'Invalid swap calculation.',
InvalidTempoBlockNumber: 'Invalid tempo block number.',
InvalidThreshold: 'Invalid threshold.',
InvalidTick: 'The price tick is invalid.',
InvalidToken: 'This token is not supported on the exchange.',
InvalidTrackedRequest: 'Tracked request {0} is invalid.',
InvalidTransactionLimit: 'Invalid transaction limit.',
InvalidTransferPolicy: 'Transfer policy {0} is invalid.',
InvalidTransferPolicyId: 'Invalid transfer policy.',
InvalidValidatorAddress: 'Invalid validator address.',
InvalidVedaPeriphery: 'Account {0} is not a valid Veda periphery.',
InvalidVedaPeripheryWiring:
'Invalid Veda periphery wiring for component {0}. Expected {1}, got {2}.',
InvalidVedaRateBounds: 'Invalid Veda rate bounds. Minimum: {0}, maximum: {1}.',
InvalidVenueShareDecimals: 'Invalid venue share decimals.',
InvalidWeight: 'Invalid weight.',
InvalidWithdrawalCount: 'Invalid withdrawal count. Expected {1}, got {0}.',
IssuerGrantFailed: 'Issuer grant failed.',
KeyAlreadyExists: 'Key already exists.',
KeyAlreadyRevoked: 'Key has already been revoked.',
KeyAuthorizationWitnessAlreadyBurned: 'Key authorization witness has already been burned.',
KeyExpired: 'Key has expired.',
KeyNotFound: 'Key not found.',
LeaderAlreadyUpdatedThisBlock: 'The leader has already been updated this block.',
LegacyAuthorizeKeySelectorChanged: 'Legacy authorize key selector changed to {0}.',
LogoURITooLong: 'Logo URI is too long.',
MasterIdCollision: 'Master ID collision with {0}.',
MaxInputExceeded:
'The required input exceeds the slippage maximum — try increasing slippage tolerance.',
MaxManagedAssetsExceeded: 'Maximum managed assets {0} exceeded by actual amount {1}.',
MerkleProofInvalidMultiproof: 'Invalid Merkle multiproof.',
MigrationNotComplete: 'Migration is not complete.',
MinimumAssetsNotMet: 'Minimum assets not met. Minimum: {0}, actual: {1}.',
MinimumEarnSharesNotMet: 'Minimum earn shares not met. Minimum: {0}, actual: {1}.',
MinimumEngineSharesNotMet: 'Minimum engine shares not met. Minimum: {0}, actual: {1}.',
MustDelegateCall: 'This function must be called with delegatecall.',
NoEarnShares: 'No earn shares were received.',
NoEncryptionKeyAtBlock: 'No encryption key exists at block {0}.',
NoEncryptionKeySet: 'No encryption key set.',
NoOptedInSupply: 'No opted-in supply.',
NoPublishedVedaPeriphery: 'No Veda periphery has been published.',
NoRecoverableClaims: 'No recoverable claims were found.',
NoVenueSharesReceived: 'No venue shares were received.',
NonceOverflow: 'Nonce overflow.',
NotAdmin: 'Not admin.',
NotAuthorizedForwarder: 'Caller {0} is not an authorized forwarder.',
NotDistributor: 'Not distributor.',
NotEarnVault: 'Not earn vault.',
NotEmergencyGuardianOrOperator: 'Caller is not the emergency guardian or operator.',
NotEngine: 'Not engine.',
NotFactory: 'Not factory.',
NotHostPort: '"{1}" is not a valid host:port for {0}.',
NotInitialized: 'Not initialized.',
NotIp: '"{0}" is not a valid IP address.',
NotIpPort: '"{1}" is not a valid IP:port for {0}.',
NotMultisigAccount: 'Not multisig account.',
NotOperator: 'Not operator.',
NotOwner: 'Not owner.',
NotPauseAuthority: 'Not pause authority.',
NotPayeeOrOperator: 'Not payee or operator.',
NotPayer: 'Not payer.',
NotPendingAdmin: 'Not pending admin.',
NotPendingDistributor: 'Not pending distributor.',
NotRequesterOrJanitor: 'Caller is not the requester or janitor.',
NotSelf: 'Not self.',
NotSequencer: 'Not sequencer.',
NotZoneMessenger: 'Not zone messenger.',
NothingOwed: 'Nothing is owed.',
OnlyDirectCall: 'Only direct calls are allowed.',
OnlySequencer: 'Only the sequencer may call this function.',
OnlyZoneInbox: 'Only the zone inbox may call this function.',
OperatorMigrationDisabled: 'Operator migration disabled.',
OrderDoesNotExist: 'No order exists with the given ID.',
OrderNotStale: 'This order is not yet eligible for stale-cleanup.',
OwnableInvalidOwner: 'Owner {0} is invalid.',
OwnableUnauthorizedAccount: 'Account {0} is not authorized.',
OwnershipRenunciationDisabled: 'Ownership renunciation disabled.',
PairAlreadyExists: 'A trading pair for these tokens has already been created.',
PairDoesNotExist: 'No trading pair exists for these tokens.',
Paused: 'This contract is paused.',
PausedLiabilityIncrease: 'Cannot increase liabilities while paused.',
PendingRedeemsOpen: 'Pending redemptions are still open.',
PermitExpired: 'Permit has expired.',
PolicyForbids: 'Forbidden by policy.',
PolicyNotFound: 'Policy not found.',
PolicyNotSimple: 'Policy is not a simple policy.',
PortalIsPaused: 'Portal is paused.',
ProofOfWorkFailed: 'Proof of work failed.',
ProtectedAddress: 'Address is protected.',
ProtocolNonceNotSupported: 'Protocol nonce is not supported.',
PublicKeyAlreadyExists: 'Public key already exists.',
QueueTrackingDisabled: 'Tracking is disabled for queue {0}.',
QueueUnavailable: 'Queue unavailable.',
RateChangedWithinTransaction: 'Rate changed within the transaction. Expected {0}, got {1}.',
ReentrancyGuardReentrantCall: 'Reentrant call blocked.',
ReentrantCall: 'Reentrant call blocked.',
ReentrantWithdrawal: 'Reentrant withdrawal blocked.',
RegistryEngineMismatch: 'Registry engine mismatch. Expected {0}, got {1}.',
RequestNotOpen: 'Request {0} is not open.',
ResidualBacking: 'Residual backing remains.',
ResidualBalance: 'Residual balance remains.',
RewardRateLimitExceeded: 'Normalized reward rate {0} exceeds the baseline rate {1}.',
RootTotalDecreased: 'Root total decreased from {0} to {1}.',
RootTotalExceeded: 'Requested amount {0} exceeds the remaining entitlement {1}.',
SameEngine: 'Same engine.',
SameTransactionUpdateNotAllowed: 'Same transaction update not allowed.',
SequencerConfigurationUnchanged: 'Sequencer configuration unchanged.',
SignatureTypeMismatch: 'Signature type mismatch. Expected {0}, got {1}.',
SolveUnderfunded: 'Solve for asset {0} is underfunded. Delivered: {1}, required: {2}.',
SpendingLimitExceeded: 'Spending limit exceeded.',
StaleAccountantRate: 'Accountant rate updated at {0} is stale at time {1}; maximum age is {2}.',
StaleLeadershipEpoch: 'Stale leadership epoch. Expected {0}, got {1}.',
StaleRootVersion: 'Stale root version. Expected {0}, got {1}.',
StaticCallNotAllowed: 'Static call not allowed.',
SupplyCapExceeded: 'Supply cap exceeded.',
SweepExceedsAvailableBalance:
'Sweep for token {0} exceeds the balance. Available: {1}, requested: {2}.',
TickOutOfBounds: 'Price tick {0} is outside the allowed range.',
TokenAlreadyEnabled: 'Token already enabled.',
TokenAlreadyExists: 'Token {0} already exists.',
TokenCallFailed: 'Token call failed.',
TokenCallFalse: 'Token call returned false.',
TokenEnablementBlockCapacityExceeded: 'Token enablement block capacity of {0} exceeded.',
TokenMetadataTooLong: 'Token metadata too long.',
TokenNotEnabled: 'Token not enabled.',
TokenTransferPolicyNotSet: 'Token transfer policy not set.',
TooManyOwners: 'Too many owners.',
TooManyWithdrawalsThisBlock: 'Too many withdrawals this block.',
TransferFailed: 'Transfer failed.',
TransferPolicyMismatch: 'Transfer policy mismatch. Expected {0}, got {1}.',
Unauthorized: 'Unauthorized.',
UnauthorizedCaller: 'Unauthorized caller.',
UnauthorizedClaimer: 'Unauthorized claimer.',
UnexpectedAssetsReceived: 'Unexpected assets received. Expected {0}, got {1}.',
UnexpectedEarnShares: 'Unexpected earn shares. Reported: {0}, measured: {1}.',
UnexpectedRetainedAssets: 'Unexpected assets retained. Balance before: {0}, after: {1}.',
UnexpectedVenueSharesBurned: 'Unexpected venue shares burned. Expected {0}, got {1}.',
UnexpectedVenueSharesQueued: 'Unexpected venue shares queued. Expected {0}, got {1}.',
Uninitialized: 'Uninitialized.',
UnsupportedAccountantFreshness: 'Accountant {0} does not support freshness checks.',
UnsupportedTransferPolicy: 'Transfer policy {0} has unsupported type {1}.',
ValidatorAlreadyDeactivated: 'Validator is already deactivated.',
ValidatorAlreadyExists: 'Validator already exists.',
ValidatorNotFound: 'Validator not found.',
VedaRateOutOfBounds: 'Veda rate {0} is outside the allowed range of {1} to {2}.',
VirtualAddressNotAllowed: 'Virtual address is not allowed.',
VirtualAddressUnregistered: 'Virtual address is not registered.',
WrongInputToken: 'Incorrect input token.',
WrongSourceZone: 'Incorrect source zone.',
ZeroAddress: 'Address cannot be zero.',
ZeroAmount: 'Amount cannot be zero.',
ZeroAmountWithdrawal: 'Withdrawal amount cannot be zero.',
ZeroCampaignId: 'Campaign ID cannot be zero.',
ZeroDeadline: 'Deadline cannot be zero.',
ZeroDeposit: 'Deposit cannot be zero.',
ZeroMinimumAssets: 'Minimum assets cannot be zero.',
ZeroMinimumEarnShares: 'Minimum earn shares cannot be zero.',
ZeroMinimumEngineShares: 'Minimum engine shares cannot be zero.',
ZeroPublicKey: 'Public key cannot be zero.',
ZeroRoot: 'Root cannot be zero.',
ZeroStatementHash: 'Statement hash cannot be zero.',
}
/** Interpolate `{0}`, `{1}`, … placeholders with args. */
function interpolate(template: string, args?: readonly unknown[]): string {
if (!args) return template
return template.replace(/\{(\d+)\}/g, (_, i) => {
const v = args[Number(i)]
return v === undefined ? `{${i}}` : String(v)
})
}
/** Parse a viem error into a structured execution error. */
export function parse(error: Error): ExecutionError {
const raw =
(error as { details?: string }).details ??
(error as { shortMessage?: string }).shortMessage ??
error.message
const data = extractRevertData(error)
if (data) {
try {
const decoded = decodeErrorResult({ abi: Abis.all, data })
const template = messages[decoded.errorName as AbiErrorName]
return {
...decoded,
data,
message: template
? interpolate(template, decoded.args as readonly unknown[])
: raw.replace(/^execution reverted:\s*/i, ''),
} as never
} catch {}
}
// Fallback: extract error name from human-readable revert message.
const nameMatch = /:\s*(\w+)\(\w+/.exec(raw)
const errorName = nameMatch?.[1]
if (errorName && errorName in messages)
return { errorName: 'unknown', message: messages[errorName as AbiErrorName]! }
return {
errorName: 'unknown',
message: raw.replace(/^execution reverted:\s*/i, ''),
}
}
/** Serializes an ExecutionError for RPC transport (bigints/numbers → hex). */
export function serialize(preimage: ExecutionError): Rpc {
if (preimage.errorName === 'unknown') return { errorName: 'unknown', message: preimage.message }
return {
errorName: preimage.errorName,
abiItem: preimage.abiItem,
message: preimage.message,
data: preimage.data,
} as never
}
function extractRevertData(error: unknown): Hex | null {
if (!error || typeof error !== 'object') return null
const e = error as Record<string, unknown>
if (typeof e.data === 'string' && e.data.startsWith('0x')) return e.data as Hex
if (e.cause) return extractRevertData(e.cause)
if (e.error) return extractRevertData(e.error)
if (typeof e.walk === 'function') {
const inner = (e as { walk: (fn: (e: unknown) => boolean) => unknown }).walk(
(e) => typeof (e as Record<string, unknown>).data === 'string',
)
if (inner) return extractRevertData(inner)
}
return null
}