@bsv/wallet-toolbox
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
691 lines (655 loc) • 25 kB
text/typescript
import { Utils } from '@bsv/sdk'
import {
asArray,
asString,
convertProofToMerklePath,
randomBytesBase64,
sdk,
verifyHexString,
verifyId,
verifyInteger,
verifyOne,
verifyOptionalHexString,
verifyTruthy
} from '../../index.all'
import {
outputColumnsWithoutLockingScript,
TableCertificate,
TableCertificateField,
TableCertificateX,
TableCommission,
TableMonitorEvent,
TableOutput,
TableOutputBasket,
TableOutputTag,
TableOutputTagMap,
TableProvenTx,
TableProvenTxReq,
TableSettings,
TableSyncState,
TableTransaction,
TableTxLabel,
TableTxLabelMap,
TableUser,
transactionColumnsWithoutRawTx
} from '../index.all'
import { Knex } from 'knex'
import { isHexString } from '../../sdk'
import { StorageReader, StorageReaderOptions } from '../StorageReader'
export interface StorageMySQLDojoReaderOptions extends StorageReaderOptions {
chain: sdk.Chain
/**
* Knex database interface initialized with valid connection configuration.
*/
knex: Knex
}
export class StorageMySQLDojoReader extends StorageReader implements sdk.WalletStorageSyncReader {
knex: Knex
constructor(options: StorageMySQLDojoReaderOptions) {
super(options)
if (!options.knex) throw new sdk.WERR_INVALID_PARAMETER('options.knex', `valid`)
this.knex = options.knex
}
override async destroy(): Promise<void> {
await this.knex?.destroy()
}
override async transaction<T>(scope: (trx: sdk.TrxToken) => Promise<T>, trx?: sdk.TrxToken): Promise<T> {
if (trx) return await scope(trx)
return await this.knex.transaction<T>(async knextrx => {
const trx = knextrx as sdk.TrxToken
return await scope(trx)
})
}
toDb(trx?: sdk.TrxToken) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const db = !trx ? this.knex : <Knex.Transaction<any, any[]>>trx
this.whenLastAccess = new Date()
return db
}
override async readSettings(trx?: sdk.TrxToken): Promise<TableSettings> {
const d = verifyOne(await this.toDb(trx)('settings'))
const r: TableSettings = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
storageIdentityKey: verifyHexString(d.dojoIdentityKey),
storageName: d.dojoName || `${this.chain} Legacy Import`,
chain: this.chain,
dbtype: 'MySQL',
maxOutputScript: 256
}
if (r.storageName.startsWith('staging') && this.chain !== 'test')
throw new sdk.WERR_INVALID_PARAMETER('chain', `in aggreement with storage chain ${r.storageName}`)
this._settings = r
return r
}
setupQuery<T extends object>(table: string, args: sdk.FindPartialSincePagedArgs<T>): Knex.QueryBuilder {
let q = this.toDb(args.trx)<T>(table)
if (args.partial && Object.keys(args.partial).length > 0) q.where(args.partial)
if (args.since) q.where('updated_at', '>=', this.validateDateForWhere(args.since))
if (args.paged) {
q.limit(args.paged.limit)
q.offset(args.paged.offset || 0)
}
return q
}
findOutputBasketsQuery(args: sdk.FindOutputBasketsArgs): Knex.QueryBuilder {
return this.setupQuery('output_baskets', args)
}
async findOutputBaskets(args: sdk.FindOutputBasketsArgs): Promise<TableOutputBasket[]> {
const q = this.findOutputBasketsQuery(args)
const ds = await q
const rs: TableOutputBasket[] = []
for (const d of ds) {
const r: TableOutputBasket = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
basketId: verifyInteger(d.basketId),
userId: verifyInteger(d.userId),
name: verifyTruthy(d.name).trim().toLowerCase(),
numberOfDesiredUTXOs: verifyInteger(d.numberOfDesiredUTXOs),
minimumDesiredUTXOValue: verifyInteger(d.minimumDesiredUTXOValue),
isDeleted: !!d.isDeleted
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['isDeleted'])
}
findTxLabelsQuery(args: sdk.FindTxLabelsArgs): Knex.QueryBuilder {
return this.setupQuery('tx_labels', args)
}
async findTxLabels(args: sdk.FindTxLabelsArgs): Promise<TableTxLabel[]> {
const q = this.findTxLabelsQuery(args)
const ds = await q
const rs: TableTxLabel[] = []
for (const d of ds) {
const r: TableTxLabel = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
txLabelId: verifyInteger(d.txLabelId),
userId: verifyInteger(d.userId),
label: verifyTruthy(d.label).trim().toLowerCase(),
isDeleted: !!d.isDeleted
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['isDeleted'])
}
findOutputTagsQuery(args: sdk.FindOutputTagsArgs): Knex.QueryBuilder {
return this.setupQuery('output_tags', args)
}
async findOutputTags(args: sdk.FindOutputTagsArgs): Promise<TableOutputTag[]> {
const q = this.findOutputTagsQuery(args)
const ds = await q
const rs: TableOutputTag[] = []
for (const d of ds) {
const r: TableOutputTag = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
outputTagId: verifyInteger(d.outputTagId),
userId: verifyInteger(d.userId),
tag: verifyTruthy(d.tag).trim().toLowerCase(),
isDeleted: !!d.isDeleted
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['isDeleted'])
}
findTransactionsQuery(args: sdk.FindTransactionsArgs, count?: boolean): Knex.QueryBuilder {
if (args.partial.rawTx)
throw new sdk.WERR_INVALID_PARAMETER(
'args.partial.rawTx',
`undefined. Transactions may not be found by rawTx value.`
)
if (args.partial.inputBEEF)
throw new sdk.WERR_INVALID_PARAMETER(
'args.partial.inputBEEF',
`undefined. Transactions may not be found by inputBEEF value.`
)
const q = this.setupQuery('transactions', args)
if (args.status && args.status.length > 0) q.whereIn('status', args.status)
if (args.noRawTx && !count) {
const columns = transactionColumnsWithoutRawTx.map(c => `transactions.${c}`)
q.select(columns)
}
return q
}
async findTransactions(args: sdk.FindTransactionsArgs): Promise<TableTransaction[]> {
const q = this.findTransactionsQuery(args)
const ds = await q
const rs: TableTransaction[] = []
for (const d of ds) {
const r: TableTransaction = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
transactionId: verifyInteger(d.transactionId),
userId: verifyInteger(d.userId),
status: verifyTruthy(convertTxStatus(d.status)),
reference: forceToBase64(d.referenceNumber),
isOutgoing: !!d.isOutgoing,
satoshis: verifyInteger(d.amount),
description: verifyTruthy(d.note || '12345'),
provenTxId: verifyOptionalInteger(d.provenTxId),
version: verifyOptionalInteger(d.version),
lockTime: verifyOptionalInteger(d.lockTime),
txid: nullToUndefined(d.txid),
inputBEEF: d.beef ? Array.from(d.beef) : undefined,
rawTx: d.rawTransaction ? Array.from(d.rawTransaction) : undefined
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['isOutgoing'])
}
findCommissionsQuery(args: sdk.FindCommissionsArgs): Knex.QueryBuilder {
if (args.partial.lockingScript)
throw new sdk.WERR_INVALID_PARAMETER(
'args.partial.lockingScript',
`undefined. Commissions may not be found by lockingScript value.`
)
return this.setupQuery('commissions', args)
}
async findCommissions(args: sdk.FindCommissionsArgs): Promise<TableCommission[]> {
const q = this.findCommissionsQuery(args)
const ds = await q
const rs: TableCommission[] = []
for (const d of ds) {
const r: TableCommission = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
commissionId: verifyInteger(d.commissionId),
userId: verifyInteger(d.userId),
transactionId: verifyInteger(d.transactionId),
satoshis: verifyInteger(d.satoshis),
keyOffset: verifyTruthy(d.keyOffset).trim(),
isRedeemed: !!d.isRedeemed,
lockingScript: Array.from(verifyTruthy(d.outputScript))
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['isRedeemed'])
}
limitString(s: string, maxLen: number): string {
if (s.length > maxLen) s = s.slice(0, maxLen)
return s
}
findOutputsQuery(args: sdk.FindOutputsArgs, count?: boolean): Knex.QueryBuilder {
if (args.partial.lockingScript)
throw new sdk.WERR_INVALID_PARAMETER(
'args.partial.lockingScript',
`undefined. Outputs may not be found by lockingScript value.`
)
const q = this.setupQuery('outputs', args)
if (args.noScript && !count) {
const columns = outputColumnsWithoutLockingScript.map(c => `outputs.${c}`)
q.select(columns)
}
return q
}
async findOutputs(args: sdk.FindOutputsArgs): Promise<TableOutput[]> {
const q = this.findOutputsQuery(args)
const ds = await q
const rs: TableOutput[] = []
for (const d of ds) {
const r: TableOutput = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
outputId: verifyInteger(d.outputId),
userId: verifyInteger(d.userId),
transactionId: verifyInteger(d.transactionId),
basketId: verifyOptionalInteger(d.basketId),
spendable: !!d.spendable,
change: d.providedBy !== 'you' && d.purpose === 'change',
outputDescription: (d.description || '').trim(),
vout: verifyInteger(typeof d.vout !== 'number' ? 9999 : d.vout),
satoshis: verifyInteger(d.amount),
providedBy: verifyTruthy(d.providedBy || 'you')
.trim()
.toLowerCase()
.replace('dojo', 'storage'),
purpose: (d.purpose || '').trim().toLowerCase(),
type: verifyTruthy(d.type).trim(),
txid: nullToUndefined(d.txid),
senderIdentityKey: verifyOptionalHexString(d.senderIdentityKey),
derivationPrefix: nullToUndefined(d.derivationPrefix),
derivationSuffix: nullToUndefined(d.derivationSuffix),
customInstructions: nullToUndefined(d.customInstruction),
spentBy: verifyOptionalInteger(d.spentBy),
sequenceNumber: undefined,
spendingDescription: nullToUndefined(d.spendingDescription),
scriptLength: verifyOptionalInteger(d.scriptLength),
scriptOffset: verifyOptionalInteger(d.scriptOffset),
lockingScript: d.outputScript ? Array.from(d.outputScript) : undefined
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['spendable', 'change'])
}
findCertificatesQuery(args: sdk.FindCertificatesArgs): Knex.QueryBuilder {
const q = this.setupQuery('certificates', args)
if (args.certifiers && args.certifiers.length > 0) q.whereIn('certifier', args.certifiers)
if (args.types && args.types.length > 0) q.whereIn('type', args.types)
return q
}
async findCertificates(args: sdk.FindCertificatesArgs): Promise<TableCertificateX[]> {
const q = this.findCertificatesQuery(args)
const ds = await q
const rs: TableCertificate[] = []
for (const d of ds) {
const r: TableCertificate = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
certificateId: verifyInteger(d.certificateId),
userId: verifyInteger(d.userId),
type: verifyTruthy(d.type).trim(), // base64
serialNumber: verifyTruthy(d.serialNumber).trim(), // base64
certifier: verifyHexString(d.certifier),
subject: verifyHexString(d.subject),
revocationOutpoint: verifyTruthy(d.revocationOutpoint).trim().toLowerCase(),
signature: verifyHexString(d.signature),
verifier: verifyOptionalHexString(d.validationKey),
isDeleted: !!d.isDeleted
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['isDeleted'])
}
findCertificateFieldsQuery(args: sdk.FindCertificateFieldsArgs): Knex.QueryBuilder {
return this.setupQuery('certificate_fields', args)
}
async findCertificateFields(args: sdk.FindCertificateFieldsArgs): Promise<TableCertificateField[]> {
const q = this.findCertificateFieldsQuery(args)
const ds = await q
const rs: TableCertificateField[] = []
for (const d of ds) {
const r: TableCertificateField = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
userId: verifyInteger(d.userId),
certificateId: verifyInteger(d.certificateId),
fieldName: verifyTruthy(d.fieldName).trim().toLowerCase(),
fieldValue: verifyTruthy(d.fieldValue).trim(), // base64
masterKey: verifyTruthy(d.masterKey).trim() // base64
}
rs.push(r)
}
return this.validateEntities(rs)
}
override async findSyncStates(args: sdk.FindSyncStatesArgs): Promise<TableSyncState[]> {
const q = this.setupQuery('sync_state', args)
const ds = await q
const rs: TableSyncState[] = []
for (const d of ds) {
const r: TableSyncState = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
syncStateId: verifyInteger(d.syncStateId),
userId: verifyInteger(d.userId),
storageIdentityKey: verifyHexString(d.storageIdentityKey),
storageName: verifyTruthy(d.storageName || 'legacy importer')
.trim()
.toLowerCase(),
status: convertSyncStatus(d.status),
init: !!d.init,
refNum: verifyTruthy(d.refNum),
syncMap: verifyTruthy(d.syncMap),
when: d.when ? this.validateDate(d.when) : undefined,
satoshis: verifyOptionalInteger(d.total),
errorLocal: nullToUndefined(d.errorLocal),
errorOther: nullToUndefined(d.errorOther)
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['init'])
}
override async findUsers(args: sdk.FindUsersArgs): Promise<TableUser[]> {
const q = this.setupQuery('users', args)
const ds = await q
const rs: TableUser[] = []
for (const d of ds) {
const r: TableUser = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
userId: verifyId(d.userId),
identityKey: verifyTruthy(d.identityKey),
activeStorage: this.getSettings().storageIdentityKey
}
rs.push(r)
}
return this.validateEntities(rs)
}
getProvenTxsForUserQuery(args: sdk.FindForUserSincePagedArgs): Knex.QueryBuilder {
const k = this.toDb(args.trx)
let q = k('proven_txs').where(function () {
this.whereExists(
k
.select('*')
.from('transactions')
.whereRaw(`proven_txs.provenTxId = transactions.provenTxId and transactions.userId = ${args.userId}`)
)
})
if (args.paged) {
q = q.limit(args.paged.limit)
q = q.offset(args.paged.offset || 0)
}
if (args.since) q = q.where('updated_at', '>=', args.since)
return q
}
async getProvenTxsForUser(args: sdk.FindForUserSincePagedArgs): Promise<TableProvenTx[]> {
const q = this.getProvenTxsForUserQuery(args)
const ds = await q
const rs: TableProvenTx[] = []
for (const d of ds) {
const mp = convertProofToMerklePath(d.txid, {
index: d.index,
nodes: deserializeTscMerkleProofNodes(d.nodes),
height: d.height
})
const r: TableProvenTx = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
provenTxId: verifyInteger(d.provenTxId),
txid: verifyHexString(d.txid),
height: verifyInteger(d.height),
index: verifyInteger(d.index),
merklePath: mp.toBinary(),
rawTx: Array.from(verifyTruthy(d.rawTx)),
blockHash: verifyHexString(asString(verifyTruthy(d.blockHash))),
merkleRoot: verifyHexString(asString(verifyTruthy(d.merkleRoot)))
}
rs.push(r)
}
return this.validateEntities(rs)
}
getProvenTxReqsForUserQuery(args: sdk.FindForUserSincePagedArgs): Knex.QueryBuilder {
const k = this.toDb(args.trx)
let q = k('proven_tx_reqs').where(function () {
this.whereExists(
k
.select('*')
.from('transactions')
.whereRaw(`proven_tx_reqs.txid = transactions.txid and transactions.userId = ${args.userId}`)
)
})
if (args.paged) {
q = q.limit(args.paged.limit)
q = q.offset(args.paged.offset || 0)
}
if (args.since) q = q.where('updated_at', '>=', args.since)
return q
}
async getProvenTxReqsForUser(args: sdk.FindForUserSincePagedArgs): Promise<TableProvenTxReq[]> {
const q = this.getProvenTxReqsForUserQuery(args)
const ds = await q
const rs: TableProvenTxReq[] = []
for (const d of ds) {
const r: TableProvenTxReq = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
provenTxReqId: verifyInteger(d.provenTxReqId),
provenTxId: verifyOptionalInteger(d.provenTxId),
txid: verifyTruthy(d.txid),
rawTx: Array.from(verifyTruthy(d.rawTx)),
status: verifyTruthy(convertReqStatus(d.status)),
attempts: verifyInteger(d.attempts),
notified: !!d.notified,
history: verifyTruthy(d.history),
notify: verifyTruthy(d.notify),
inputBEEF: d.beef ? Array.from(d.beef) : undefined
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['notified'])
}
getTxLabelMapsForUserQuery(args: sdk.FindForUserSincePagedArgs): Knex.QueryBuilder {
const k = this.toDb(args.trx)
let q = k('tx_labels_map').whereExists(
k
.select('*')
.from('tx_labels')
.whereRaw(`tx_labels.txLabelId = tx_labels_map.txLabelId and tx_labels.userId = ${args.userId}`)
)
if (args.since) q = q.where('updated_at', '>=', this.validateDateForWhere(args.since))
if (args.paged) {
q = q.limit(args.paged.limit)
q = q.offset(args.paged.offset || 0)
}
return q
}
async getTxLabelMapsForUser(args: sdk.FindForUserSincePagedArgs): Promise<TableTxLabelMap[]> {
const q = this.getTxLabelMapsForUserQuery(args)
const ds = await q
const rs: TableTxLabelMap[] = []
for (const d of ds) {
const r: TableTxLabelMap = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
txLabelId: verifyInteger(d.txLabelId),
transactionId: verifyInteger(d.transactionId),
isDeleted: !!d.isDeleted
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['isDeleted'])
}
getOutputTagMapsForUserQuery(args: sdk.FindForUserSincePagedArgs): Knex.QueryBuilder {
const k = this.toDb(args.trx)
let q = k('output_tags_map').whereExists(
k
.select('*')
.from('output_tags')
.whereRaw(`output_tags.outputTagId = output_tags_map.outputTagId and output_tags.userId = ${args.userId}`)
)
if (args.since) q = q.where('updated_at', '>=', this.validateDateForWhere(args.since))
if (args.paged) {
q = q.limit(args.paged.limit)
q = q.offset(args.paged.offset || 0)
}
return q
}
async getOutputTagMapsForUser(args: sdk.FindForUserSincePagedArgs): Promise<TableOutputTagMap[]> {
const q = this.getOutputTagMapsForUserQuery(args)
const ds = await q
const rs: TableOutputTagMap[] = []
for (const d of ds) {
const r: TableOutputTagMap = {
created_at: verifyTruthy(d.created_at),
updated_at: verifyTruthy(d.updated_at),
outputId: verifyInteger(d.outputId),
outputTagId: verifyInteger(d.outputTagId),
isDeleted: !!d.isDeleted
}
rs.push(r)
}
return this.validateEntities(rs, undefined, ['isDeleted'])
}
override countCertificateFields(args: sdk.FindCertificateFieldsArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override countCertificates(args: sdk.FindCertificatesArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override countCommissions(args: sdk.FindCommissionsArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override countOutputBaskets(args: sdk.FindOutputBasketsArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override countOutputs(args: sdk.FindOutputsArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override countOutputTags(args: sdk.FindOutputTagsArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override countSyncStates(args: sdk.FindSyncStatesArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override countTransactions(args: sdk.FindTransactionsArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override countTxLabels(args: sdk.FindTxLabelsArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override countUsers(args: sdk.FindUsersArgs): Promise<number> {
throw new Error('Method not implemented.')
}
override findMonitorEvents(args: sdk.FindMonitorEventsArgs): Promise<TableMonitorEvent[]> {
throw new Error('Method not implemented.')
}
override countMonitorEvents(args: sdk.FindMonitorEventsArgs): Promise<number> {
throw new Error('Method not implemented.')
}
/**
* Helper to force uniform behavior across database engines.
* Use to process all individual records with time stamps retreived from database.
*/
validateEntity<T extends sdk.EntityTimeStamp>(entity: T, dateFields?: string[], booleanFields?: string[]): T {
entity.created_at = this.validateDate(entity.created_at)
entity.updated_at = this.validateDate(entity.updated_at)
if (dateFields) {
for (const df of dateFields) {
if (entity[df]) entity[df] = this.validateDate(entity[df])
}
}
if (booleanFields) {
for (const df of booleanFields) {
if (entity[df] !== undefined) entity[df] = !!entity[df]
}
}
for (const key of Object.keys(entity)) {
const val = entity[key]
if (val === null) {
entity[key] = undefined
} else if (Buffer.isBuffer(val)) {
entity[key] = Array.from(val)
}
}
return entity
}
/**
* Helper to force uniform behavior across database engines.
* Use to process all arrays of records with time stamps retreived from database.
* @returns input `entities` array with contained values validated.
*/
validateEntities<T extends sdk.EntityTimeStamp>(entities: T[], dateFields?: string[], booleanFields?: string[]): T[] {
for (let i = 0; i < entities.length; i++) {
entities[i] = this.validateEntity(entities[i], dateFields, booleanFields)
}
return entities
}
}
function deserializeTscMerkleProofNodes(nodes: Buffer): string[] {
if (!Buffer.isBuffer(nodes)) throw new sdk.WERR_INTERNAL('Buffer or string expected.')
const buffer = nodes
const ns: string[] = []
for (let offset = 0; offset < buffer.length; ) {
const flag = buffer[offset++]
if (flag === 1) ns.push('*')
else if (flag === 0) {
ns.push(asString(buffer.subarray(offset, offset + 32)))
offset += 32
} else {
throw new sdk.WERR_BAD_REQUEST(`node type byte ${flag} is not supported here.`)
}
}
return ns
}
type DojoProvenTxReqStatusApi =
| 'sending'
| 'unsent'
| 'nosend'
| 'unknown'
| 'nonfinal'
| 'unprocessed'
| 'unmined'
| 'callback'
| 'unconfirmed'
| 'completed'
| 'invalid'
| 'doubleSpend'
function convertReqStatus(status: DojoProvenTxReqStatusApi): sdk.ProvenTxReqStatus {
return status
}
type DojoTransactionStatusApi = 'completed' | 'failed' | 'unprocessed' | 'sending' | 'unproven' | 'unsigned' | 'nosend'
//type TransactionStatus =
// 'completed' | 'failed' | 'unprocessed' | 'sending' | 'unproven' | 'unsigned' | 'nosend'
function convertTxStatus(status: DojoTransactionStatusApi): sdk.TransactionStatus {
return status
}
function nullToUndefined<T>(v: T): T | undefined {
if (v === null) return undefined
if (typeof v === 'string') return v.trim() as T
return v
}
function verifyOptionalInteger(v: number | null | undefined): number | undefined {
if (v === undefined || v === null) return undefined
if (typeof v !== 'number' || !Number.isInteger(v)) throw new sdk.WERR_INTERNAL('An integer is required.')
return v
}
type DojoSyncStatus = 'success' | 'error' | 'identified' | 'updated' | 'unknown'
function convertSyncStatus(status: DojoSyncStatus): sdk.SyncStatus {
return status
}
function forceToBase64(s?: string | null): string {
if (!s) return randomBytesBase64(12)
if (isHexString(s)) return Utils.toBase64(asArray(s.trim()))
return s.trim()
}