@bsv/sdk
Version:
BSV Blockchain Software Development Kit
1,615 lines (1,497 loc) • 64.3 kB
text/typescript
import {
AcquireCertificateArgs,
AcquireCertificateResult,
SecurityLevel,
SecurityLevels,
Base64String,
BasketStringUnder300Bytes,
BEEF,
BooleanDefaultFalse,
BooleanDefaultTrue,
Byte,
CertificateFieldNameUnder50Bytes,
CertificateResult,
CreateActionArgs,
CreateActionResult,
DescriptionString5to50Bytes,
DiscoverCertificatesResult,
EntityIconURLStringMax500Bytes,
EntityNameStringMax100Bytes,
HexString,
InternalizeActionArgs,
ISOTimestampString,
KeyIDStringUnder800Bytes,
LabelStringUnder300Bytes,
ListActionsArgs,
ListActionsResult,
ListCertificatesResult,
ListOutputsArgs,
ListOutputsResult,
OriginatorDomainNameStringUnder250Bytes,
OutpointString,
OutputTagStringUnder300Bytes,
PositiveInteger,
PositiveIntegerDefault10Max10000,
PositiveIntegerMax10,
PositiveIntegerOrZero,
ProtocolString5To400Bytes,
ProveCertificateArgs,
ProveCertificateResult,
PubKeyHex,
SatoshiValue,
SignActionArgs,
SignActionResult,
TXIDHexString,
VersionString7To30Bytes,
WalletInterface,
ActionStatus
} from '../Wallet.interfaces.js'
import WalletWire from './WalletWire.js'
import Certificate from '../../auth/certificates/Certificate.js'
import * as Utils from '../../primitives/utils.js'
import calls, { CallType } from './WalletWireCalls.js'
import { WalletError } from '../WalletError.js'
/**
* A way to make remote calls to a wallet over a wallet wire.
*/
export default class WalletWireTransceiver implements WalletInterface {
wire: WalletWire
constructor(wire: WalletWire) {
this.wire = wire
}
private async transmit(
call: CallType,
originator: OriginatorDomainNameStringUnder250Bytes = '',
params: number[] = []
): Promise<number[]> {
const frameWriter = new Utils.Writer()
frameWriter.writeUInt8(calls[call])
const originatorArray = Utils.toArray(originator, 'utf8')
frameWriter.writeUInt8(originatorArray.length)
frameWriter.write(originatorArray)
if (params.length > 0) {
frameWriter.write(params)
}
const frame = frameWriter.toArray()
const result = await this.wire.transmitToWallet(frame)
const resultReader = new Utils.Reader(result)
const errorByte = resultReader.readUInt8()
if (errorByte === 0) {
const resultFrame = resultReader.read()
return resultFrame
} else {
// Deserialize the error message length
const errorMessageLength = resultReader.readVarIntNum()
const errorMessageBytes = resultReader.read(errorMessageLength)
const errorMessage = Utils.toUTF8(errorMessageBytes)
// Deserialize the stack trace length
const stackTraceLength = resultReader.readVarIntNum()
const stackTraceBytes = resultReader.read(stackTraceLength)
const stackTrace = Utils.toUTF8(stackTraceBytes)
// Construct a custom wallet error
const e = new WalletError(errorMessage, errorByte, stackTrace)
throw e
}
}
async createAction(
args: CreateActionArgs,
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<CreateActionResult> {
const paramWriter = new Utils.Writer()
// Serialize description
const descriptionBytes = Utils.toArray(args.description, 'utf8')
paramWriter.writeVarIntNum(descriptionBytes.length)
paramWriter.write(descriptionBytes)
// input BEEF
if (args.inputBEEF != null) {
paramWriter.writeVarIntNum(args.inputBEEF.length)
paramWriter.write(args.inputBEEF)
} else {
paramWriter.writeVarIntNum(-1)
}
// Serialize inputs
if (args.inputs != null) {
paramWriter.writeVarIntNum(args.inputs.length)
for (const input of args.inputs) {
// outpoint
paramWriter.write(this.encodeOutpoint(input.outpoint))
// unlockingScript / unlockingScriptLength
if (input.unlockingScript != null && input.unlockingScript !== '') {
const unlockingScriptBytes = Utils.toArray(
input.unlockingScript,
'hex'
)
paramWriter.writeVarIntNum(unlockingScriptBytes.length)
paramWriter.write(unlockingScriptBytes)
} else {
paramWriter.writeVarIntNum(-1)
paramWriter.writeVarIntNum(input.unlockingScriptLength ?? 0)
}
// inputDescription
const inputDescriptionBytes = Utils.toArray(
input.inputDescription,
'utf8'
)
paramWriter.writeVarIntNum(inputDescriptionBytes.length)
paramWriter.write(inputDescriptionBytes)
// sequenceNumber
if (typeof input.sequenceNumber === 'number') {
paramWriter.writeVarIntNum(input.sequenceNumber)
} else {
paramWriter.writeVarIntNum(-1)
}
}
} else {
paramWriter.writeVarIntNum(-1)
}
// Serialize outputs
if (args.outputs != null) {
paramWriter.writeVarIntNum(args.outputs.length)
for (const output of args.outputs) {
// lockingScript
const lockingScriptBytes = Utils.toArray(output.lockingScript, 'hex')
paramWriter.writeVarIntNum(lockingScriptBytes.length)
paramWriter.write(lockingScriptBytes)
// satoshis
paramWriter.writeVarIntNum(output.satoshis)
// outputDescription
const outputDescriptionBytes = Utils.toArray(
output.outputDescription,
'utf8'
)
paramWriter.writeVarIntNum(outputDescriptionBytes.length)
paramWriter.write(outputDescriptionBytes)
// basket
if (output.basket != null && output.basket !== '') {
const basketBytes = Utils.toArray(output.basket, 'utf8')
paramWriter.writeVarIntNum(basketBytes.length)
paramWriter.write(basketBytes)
} else {
paramWriter.writeVarIntNum(-1)
}
// customInstructions
if (output.customInstructions != null && output.customInstructions !== '') {
const customInstructionsBytes = Utils.toArray(
output.customInstructions,
'utf8'
)
paramWriter.writeVarIntNum(customInstructionsBytes.length)
paramWriter.write(customInstructionsBytes)
} else {
paramWriter.writeVarIntNum(-1)
}
// tags
if (output.tags != null) {
paramWriter.writeVarIntNum(output.tags.length)
for (const tag of output.tags) {
const tagBytes = Utils.toArray(tag, 'utf8')
paramWriter.writeVarIntNum(tagBytes.length)
paramWriter.write(tagBytes)
}
} else {
paramWriter.writeVarIntNum(-1)
}
}
} else {
paramWriter.writeVarIntNum(-1)
}
// Serialize lockTime
if (typeof args.lockTime === 'number') {
paramWriter.writeVarIntNum(args.lockTime)
} else {
paramWriter.writeVarIntNum(-1)
}
// Serialize version
if (typeof args.version === 'number') {
paramWriter.writeVarIntNum(args.version)
} else {
paramWriter.writeVarIntNum(-1)
}
// Serialize labels
if (args.labels != null) {
paramWriter.writeVarIntNum(args.labels.length)
for (const label of args.labels) {
const labelBytes = Utils.toArray(label, 'utf8')
paramWriter.writeVarIntNum(labelBytes.length)
paramWriter.write(labelBytes)
}
} else {
paramWriter.writeVarIntNum(-1)
}
// Serialize options
if (args.options != null) {
paramWriter.writeInt8(1) // options present
// signAndProcess
if (typeof args.options.signAndProcess === 'boolean') {
paramWriter.writeInt8(args.options.signAndProcess ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
// acceptDelayedBroadcast
if (typeof args.options.acceptDelayedBroadcast === 'boolean') {
paramWriter.writeInt8(args.options.acceptDelayedBroadcast ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
// trustSelf
if (args.options.trustSelf === 'known') {
paramWriter.writeInt8(1)
} else {
paramWriter.writeInt8(-1)
}
// knownTxids
if (args.options.knownTxids != null) {
paramWriter.writeVarIntNum(args.options.knownTxids.length)
for (const txid of args.options.knownTxids) {
const txidBytes = Utils.toArray(txid, 'hex')
paramWriter.write(txidBytes)
}
} else {
paramWriter.writeVarIntNum(-1)
}
// returnTXIDOnly
if (typeof args.options.returnTXIDOnly === 'boolean') {
paramWriter.writeInt8(args.options.returnTXIDOnly ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
// noSend
if (typeof args.options.noSend === 'boolean') {
paramWriter.writeInt8(args.options.noSend ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
// noSendChange
if (args.options.noSendChange != null) {
paramWriter.writeVarIntNum(args.options.noSendChange.length)
for (const outpoint of args.options.noSendChange) {
paramWriter.write(this.encodeOutpoint(outpoint))
}
} else {
paramWriter.writeVarIntNum(-1)
}
// sendWith
if (args.options.sendWith != null) {
paramWriter.writeVarIntNum(args.options.sendWith.length)
for (const txid of args.options.sendWith) {
const txidBytes = Utils.toArray(txid, 'hex')
paramWriter.write(txidBytes)
}
} else {
paramWriter.writeVarIntNum(-1)
}
// randomizeOutputs
if (typeof args.options.randomizeOutputs === 'boolean') {
paramWriter.writeInt8(args.options.randomizeOutputs ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
} else {
paramWriter.writeInt8(0) // options not present
}
// Transmit and parse response
const result = await this.transmit(
'createAction',
originator,
paramWriter.toArray()
)
const resultReader = new Utils.Reader(result)
const response: {
txid?: TXIDHexString
tx?: BEEF
noSendChange?: OutpointString[]
sendWithResults?: Array<{
txid: TXIDHexString
status: 'unproven' | 'sending' | 'failed'
}>
signableTransaction?: {
tx: BEEF
reference: Base64String
}
} = {}
// Parse txid
const txidFlag = resultReader.readInt8()
if (txidFlag === 1) {
const txidBytes = resultReader.read(32)
response.txid = Utils.toHex(txidBytes)
}
// Parse tx
const txFlag = resultReader.readInt8()
if (txFlag === 1) {
const txLength = resultReader.readVarIntNum()
response.tx = resultReader.read(txLength)
}
// Parse noSendChange
const noSendChangeLength = resultReader.readVarIntNum()
if (noSendChangeLength >= 0) {
response.noSendChange = []
for (let i = 0; i < noSendChangeLength; i++) {
const outpoint = this.readOutpoint(resultReader)
response.noSendChange.push(outpoint)
}
}
// Parse sendWithResults
const sendWithResultsLength = resultReader.readVarIntNum()
if (sendWithResultsLength >= 0) {
response.sendWithResults = []
for (let i = 0; i < sendWithResultsLength; i++) {
const txidBytes = resultReader.read(32)
const txid = Utils.toHex(txidBytes)
const statusCode = resultReader.readInt8()
let status: 'unproven' | 'sending' | 'failed' = 'unproven'
if (statusCode === 1) status = 'unproven'
else if (statusCode === 2) status = 'sending'
else if (statusCode === 3) status = 'failed'
response.sendWithResults.push({ txid, status })
}
}
// Parse signableTransaction
const signableTransactionFlag = resultReader.readInt8()
if (signableTransactionFlag === 1) {
const txLength = resultReader.readVarIntNum()
const tx = resultReader.read(txLength)
const referenceLength = resultReader.readVarIntNum()
const referenceBytes = resultReader.read(referenceLength)
response.signableTransaction = {
tx,
reference: Utils.toBase64(referenceBytes)
}
}
return response
}
async signAction(
args: SignActionArgs,
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<SignActionResult> {
const paramWriter = new Utils.Writer()
// Serialize spends
const spendIndexes = Object.keys(args.spends)
paramWriter.writeVarIntNum(spendIndexes.length)
for (const index of spendIndexes) {
paramWriter.writeVarIntNum(Number(index))
const spend = args.spends[Number(index)]
// unlockingScript
const unlockingScriptBytes = Utils.toArray(spend.unlockingScript, 'hex')
paramWriter.writeVarIntNum(unlockingScriptBytes.length)
paramWriter.write(unlockingScriptBytes)
// sequenceNumber
if (typeof spend.sequenceNumber === 'number') {
paramWriter.writeVarIntNum(spend.sequenceNumber)
} else {
paramWriter.writeVarIntNum(-1)
}
}
// Serialize reference
const referenceBytes = Utils.toArray(args.reference, 'base64')
paramWriter.writeVarIntNum(referenceBytes.length)
paramWriter.write(referenceBytes)
// Serialize options
if (args.options != null) {
paramWriter.writeInt8(1) // options present
// acceptDelayedBroadcast
if (typeof args.options.acceptDelayedBroadcast === 'boolean') {
paramWriter.writeInt8(args.options.acceptDelayedBroadcast ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
// returnTXIDOnly
if (typeof args.options.returnTXIDOnly === 'boolean') {
paramWriter.writeInt8(args.options.returnTXIDOnly ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
// noSend
if (typeof args.options.noSend === 'boolean') {
paramWriter.writeInt8(args.options.noSend ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
// sendWith
if (args.options.sendWith != null) {
paramWriter.writeVarIntNum(args.options.sendWith.length)
for (const txid of args.options.sendWith) {
const txidBytes = Utils.toArray(txid, 'hex')
paramWriter.write(txidBytes)
}
} else {
paramWriter.writeVarIntNum(-1)
}
} else {
paramWriter.writeInt8(0) // options not present
}
// Transmit and parse response
const result = await this.transmit(
'signAction',
originator,
paramWriter.toArray()
)
const resultReader = new Utils.Reader(result)
const response: {
txid?: TXIDHexString
tx?: BEEF
noSendChange?: OutpointString[]
sendWithResults?: Array<{
txid: TXIDHexString
status: 'unproven' | 'sending' | 'failed'
}>
} = {}
// Parse txid
const txidFlag = resultReader.readInt8()
if (txidFlag === 1) {
const txidBytes = resultReader.read(32)
response.txid = Utils.toHex(txidBytes)
}
// Parse tx
const txFlag = resultReader.readInt8()
if (txFlag === 1) {
const txLength = resultReader.readVarIntNum()
response.tx = resultReader.read(txLength)
}
// Parse sendWithResults
const sendWithResultsLength = resultReader.readVarIntNum()
if (sendWithResultsLength >= 0) {
response.sendWithResults = []
for (let i = 0; i < sendWithResultsLength; i++) {
const txidBytes = resultReader.read(32)
const txid = Utils.toHex(txidBytes)
const statusCode = resultReader.readInt8()
let status: 'unproven' | 'sending' | 'failed' = 'unproven'
if (statusCode === 1) status = 'unproven'
else if (statusCode === 2) status = 'sending'
else if (statusCode === 3) status = 'failed'
response.sendWithResults.push({ txid, status })
}
}
return response
}
async abortAction(
args: { reference: Base64String },
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ aborted: true }> {
await this.transmit(
'abortAction',
originator,
Utils.toArray(args.reference, 'base64')
)
return { aborted: true }
}
async listActions(
args: ListActionsArgs,
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<ListActionsResult> {
const paramWriter = new Utils.Writer()
// Serialize labels
paramWriter.writeVarIntNum(args.labels.length)
for (const label of args.labels) {
const labelBytes = Utils.toArray(label, 'utf8')
paramWriter.writeVarIntNum(labelBytes.length)
paramWriter.write(labelBytes)
}
// Serialize labelQueryMode
if (args.labelQueryMode === 'any') {
paramWriter.writeInt8(1)
} else if (args.labelQueryMode === 'all') {
paramWriter.writeInt8(2)
} else {
paramWriter.writeInt8(-1)
}
// Serialize include options
const includeOptions = [
args.includeLabels,
args.includeInputs,
args.includeInputSourceLockingScripts,
args.includeInputUnlockingScripts,
args.includeOutputs,
args.includeOutputLockingScripts
]
for (const option of includeOptions) {
if (typeof option === 'boolean') {
paramWriter.writeInt8(option ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
}
// Serialize limit and offset
if (typeof args.limit === 'number') {
paramWriter.writeVarIntNum(args.limit)
} else {
paramWriter.writeVarIntNum(-1)
}
if (typeof args.offset === 'number') {
paramWriter.writeVarIntNum(args.offset)
} else {
paramWriter.writeVarIntNum(-1)
}
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
// Transmit and parse response
const result = await this.transmit(
'listActions',
originator,
paramWriter.toArray()
)
const resultReader = new Utils.Reader(result)
const totalActions = resultReader.readVarIntNum()
const actions: Array<{
txid: TXIDHexString
satoshis: SatoshiValue
status: ActionStatus
isOutgoing: boolean
description: DescriptionString5to50Bytes
labels?: LabelStringUnder300Bytes[]
version: PositiveIntegerOrZero
lockTime: PositiveIntegerOrZero
inputs?: Array<{
sourceOutpoint: OutpointString
sourceSatoshis: SatoshiValue
sourceLockingScript?: HexString
unlockingScript?: HexString
inputDescription: DescriptionString5to50Bytes
sequenceNumber: PositiveIntegerOrZero
}>
outputs?: Array<{
outputIndex: PositiveIntegerOrZero
satoshis: SatoshiValue
lockingScript?: HexString
spendable: boolean
outputDescription: DescriptionString5to50Bytes
basket: BasketStringUnder300Bytes
tags: OutputTagStringUnder300Bytes[]
customInstructions?: string
}>
}> = []
for (let i = 0; i < totalActions; i++) {
// Parse action fields
const txidBytes = resultReader.read(32)
const txid = Utils.toHex(txidBytes)
const satoshis = resultReader.readVarIntNum()
const statusCode = resultReader.readInt8()
let status: ActionStatus
switch (statusCode) {
case 1:
status = 'completed'
break
case 2:
status = 'unprocessed'
break
case 3:
status = 'sending'
break
case 4:
status = 'unproven'
break
case 5:
status = 'unsigned'
break
case 6:
status = 'nosend'
break
case 7:
status = 'nonfinal'
break
case 8:
status = 'failed'
break
default:
throw new Error(`Unknown status code: ${statusCode}`)
}
const isOutgoing = resultReader.readInt8() === 1
const descriptionLength = resultReader.readVarIntNum()
const descriptionBytes = resultReader.read(descriptionLength)
const description = Utils.toUTF8(descriptionBytes)
const action: any = {
txid,
satoshis,
status,
isOutgoing,
description,
version: 0,
lockTime: 0
}
// Parse labels
const labelsLength = resultReader.readVarIntNum()
if (labelsLength >= 0) {
action.labels = []
for (let j = 0; j < labelsLength; j++) {
const labelLength = resultReader.readVarIntNum()
const labelBytes = resultReader.read(labelLength)
action.labels.push(Utils.toUTF8(labelBytes))
}
}
// Parse version and lockTime
action.version = resultReader.readVarIntNum()
action.lockTime = resultReader.readVarIntNum()
// Parse inputs
const inputsLength = resultReader.readVarIntNum()
if (inputsLength >= 0) {
action.inputs = []
for (let k = 0; k < inputsLength; k++) {
const sourceOutpoint = this.readOutpoint(resultReader)
const sourceSatoshis = resultReader.readVarIntNum()
// sourceLockingScript
const sourceLockingScriptLength = resultReader.readVarIntNum()
let sourceLockingScript: string | undefined
if (sourceLockingScriptLength >= 0) {
const sourceLockingScriptBytes = resultReader.read(
sourceLockingScriptLength
)
sourceLockingScript = Utils.toHex(sourceLockingScriptBytes)
}
// unlockingScript
const unlockingScriptLength = resultReader.readVarIntNum()
let unlockingScript: string | undefined
if (unlockingScriptLength >= 0) {
const unlockingScriptBytes = resultReader.read(
unlockingScriptLength
)
unlockingScript = Utils.toHex(unlockingScriptBytes)
}
// inputDescription
const inputDescriptionLength = resultReader.readVarIntNum()
const inputDescriptionBytes = resultReader.read(
inputDescriptionLength
)
const inputDescription = Utils.toUTF8(inputDescriptionBytes)
// sequenceNumber
const sequenceNumber = resultReader.readVarIntNum()
action.inputs.push({
sourceOutpoint,
sourceSatoshis,
sourceLockingScript,
unlockingScript,
inputDescription,
sequenceNumber
})
}
}
// Parse outputs
const outputsLength = resultReader.readVarIntNum()
if (outputsLength >= 0) {
action.outputs = []
for (let l = 0; l < outputsLength; l++) {
const outputIndex = resultReader.readVarIntNum()
const satoshis = resultReader.readVarIntNum()
// lockingScript
const lockingScriptLength = resultReader.readVarIntNum()
let lockingScript: string | undefined
if (lockingScriptLength >= 0) {
const lockingScriptBytes = resultReader.read(lockingScriptLength)
lockingScript = Utils.toHex(lockingScriptBytes)
}
const spendable = resultReader.readInt8() === 1
// outputDescription
const outputDescriptionLength = resultReader.readVarIntNum()
const outputDescriptionBytes = resultReader.read(
outputDescriptionLength
)
const outputDescription = Utils.toUTF8(outputDescriptionBytes)
// basket
const basketLength = resultReader.readVarIntNum()
let basket: string | undefined
if (basketLength >= 0) {
const basketBytes = resultReader.read(basketLength)
basket = Utils.toUTF8(basketBytes)
}
// tags
const tagsLength = resultReader.readVarIntNum()
const tags: string[] = []
if (tagsLength >= 0) {
for (let m = 0; m < tagsLength; m++) {
const tagLength = resultReader.readVarIntNum()
const tagBytes = resultReader.read(tagLength)
tags.push(Utils.toUTF8(tagBytes))
}
}
// customInstructions
const customInstructionsLength = resultReader.readVarIntNum()
let customInstructions: string | undefined
if (customInstructionsLength >= 0) {
const customInstructionsBytes = resultReader.read(
customInstructionsLength
)
customInstructions = Utils.toUTF8(customInstructionsBytes)
}
action.outputs.push({
outputIndex,
satoshis,
lockingScript,
spendable,
outputDescription,
basket,
tags,
customInstructions
})
}
}
actions.push(action)
}
return {
totalActions,
actions
}
}
async internalizeAction(
args: InternalizeActionArgs,
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ accepted: true }> {
const paramWriter = new Utils.Writer()
paramWriter.writeVarIntNum(args.tx.length)
paramWriter.write(args.tx)
paramWriter.writeVarIntNum(args.outputs.length)
for (const out of args.outputs) {
paramWriter.writeVarIntNum(out.outputIndex)
if (out.protocol === 'wallet payment') {
if (out.paymentRemittance == null) {
throw new Error('Payment remittance is required for wallet payment')
}
paramWriter.writeUInt8(1)
paramWriter.write(
Utils.toArray(out.paymentRemittance.senderIdentityKey, 'hex')
)
const derivationPrefixAsArray = Utils.toArray(
out.paymentRemittance.derivationPrefix,
'base64'
)
paramWriter.writeVarIntNum(derivationPrefixAsArray.length)
paramWriter.write(derivationPrefixAsArray)
const derivationSuffixAsArray = Utils.toArray(
out.paymentRemittance.derivationSuffix,
'base64'
)
paramWriter.writeVarIntNum(derivationSuffixAsArray.length)
paramWriter.write(derivationSuffixAsArray)
} else {
paramWriter.writeUInt8(2)
const basketAsArray = Utils.toArray(
out.insertionRemittance?.basket,
'utf8'
)
paramWriter.writeVarIntNum(basketAsArray.length)
paramWriter.write(basketAsArray)
if (typeof out.insertionRemittance?.customInstructions === 'string' && out.insertionRemittance.customInstructions !== '') {
const customInstructionsAsArray = Utils.toArray(
out.insertionRemittance.customInstructions,
'utf8'
)
paramWriter.writeVarIntNum(customInstructionsAsArray.length)
paramWriter.write(customInstructionsAsArray)
} else {
paramWriter.writeVarIntNum(-1)
}
if (typeof out.insertionRemittance?.tags === 'object') {
paramWriter.writeVarIntNum(out.insertionRemittance.tags.length)
for (const tag of out.insertionRemittance.tags) {
const tagAsArray = Utils.toArray(tag, 'utf8')
paramWriter.writeVarIntNum(tagAsArray.length)
paramWriter.write(tagAsArray)
}
} else {
paramWriter.writeVarIntNum(0)
}
}
}
if (typeof args.labels === 'object') {
paramWriter.writeVarIntNum(args.labels.length)
for (const l of args.labels) {
const labelAsArray = Utils.toArray(l, 'utf8')
paramWriter.writeVarIntNum(labelAsArray.length)
paramWriter.write(labelAsArray)
}
} else {
paramWriter.writeVarIntNum(-1)
}
const descriptionAsArray = Utils.toArray(args.description)
paramWriter.writeVarIntNum(descriptionAsArray.length)
paramWriter.write(descriptionAsArray)
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
await this.transmit('internalizeAction', originator, paramWriter.toArray())
return { accepted: true }
}
async listOutputs(
args: ListOutputsArgs,
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<ListOutputsResult> {
const paramWriter = new Utils.Writer()
const basketAsArray = Utils.toArray(args.basket, 'utf8')
paramWriter.writeVarIntNum(basketAsArray.length)
paramWriter.write(basketAsArray)
if (typeof args.tags === 'object') {
paramWriter.writeVarIntNum(args.tags.length)
for (const tag of args.tags) {
const tagAsArray = Utils.toArray(tag, 'utf8')
paramWriter.writeVarIntNum(tagAsArray.length)
paramWriter.write(tagAsArray)
}
} else {
paramWriter.writeVarIntNum(0)
}
if (args.tagQueryMode === 'all') {
paramWriter.writeInt8(1)
} else if (args.tagQueryMode === 'any') {
paramWriter.writeInt8(2)
} else {
paramWriter.writeInt8(-1)
}
if (args.include === 'locking scripts') {
paramWriter.writeInt8(1)
} else if (args.include === 'entire transactions') {
paramWriter.writeInt8(2)
} else {
paramWriter.writeInt8(-1)
}
if (typeof args.includeCustomInstructions === 'boolean') {
paramWriter.writeInt8(args.includeCustomInstructions ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
if (typeof args.includeTags === 'boolean') {
paramWriter.writeInt8(args.includeTags ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
if (typeof args.includeLabels === 'boolean') {
paramWriter.writeInt8(args.includeLabels ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
if (typeof args.limit === 'number') {
paramWriter.writeVarIntNum(args.limit)
} else {
paramWriter.writeVarIntNum(-1)
}
if (typeof args.offset === 'number') {
paramWriter.writeVarIntNum(args.offset)
} else {
paramWriter.writeVarIntNum(-1)
}
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
const result = await this.transmit(
'listOutputs',
originator,
paramWriter.toArray()
)
const resultReader = new Utils.Reader(result)
const totalOutputs = resultReader.readVarIntNum()
const beefLength = resultReader.readVarIntNum()
let BEEF
if (beefLength >= 0) {
BEEF = resultReader.read(beefLength)
}
const outputs: Array<{
outpoint: OutpointString
satoshis: SatoshiValue
lockingScript?: HexString
tx?: BEEF
spendable: true
customInstructions?: string
tags?: OutputTagStringUnder300Bytes[]
labels?: LabelStringUnder300Bytes[]
}> = []
for (let i = 0; i < totalOutputs; i++) {
const outpoint = this.readOutpoint(resultReader)
const satoshis = resultReader.readVarIntNum()
const output: {
outpoint: OutpointString
satoshis: SatoshiValue
lockingScript?: HexString
tx?: BEEF
spendable: true
customInstructions?: string
tags?: OutputTagStringUnder300Bytes[]
labels?: LabelStringUnder300Bytes[]
} = {
spendable: true,
outpoint,
satoshis
}
const scriptLength = resultReader.readVarIntNum()
if (scriptLength >= 0) {
output.lockingScript = Utils.toHex(resultReader.read(scriptLength))
}
const customInstructionsLength = resultReader.readVarIntNum()
if (customInstructionsLength >= 0) {
output.customInstructions = Utils.toUTF8(
resultReader.read(customInstructionsLength)
)
}
const tagsLength = resultReader.readVarIntNum()
if (tagsLength !== -1) {
const tags: OutputTagStringUnder300Bytes[] = []
for (let i = 0; i < tagsLength; i++) {
const tagLength = resultReader.readVarIntNum()
tags.push(Utils.toUTF8(resultReader.read(tagLength)))
}
output.tags = tags
}
const labelsLength = resultReader.readVarIntNum()
if (labelsLength !== -1) {
const labels: LabelStringUnder300Bytes[] = []
for (let i = 0; i < labelsLength; i++) {
const labelLength = resultReader.readVarIntNum()
labels.push(Utils.toUTF8(resultReader.read(labelLength)))
}
output.labels = labels
}
outputs.push(output)
}
return {
totalOutputs,
BEEF,
outputs
}
}
async relinquishOutput(
args: { basket: BasketStringUnder300Bytes, output: OutpointString },
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ relinquished: true }> {
const paramWriter = new Utils.Writer()
const basketAsArray = Utils.toArray(args.basket, 'utf8')
paramWriter.writeVarIntNum(basketAsArray.length)
paramWriter.write(basketAsArray)
paramWriter.write(this.encodeOutpoint(args.output))
await this.transmit('relinquishOutput', originator, paramWriter.toArray())
return { relinquished: true }
}
private encodeOutpoint(outpoint: OutpointString): number[] {
const writer = new Utils.Writer()
const [txid, index] = outpoint.split('.')
writer.write(Utils.toArray(txid, 'hex'))
writer.writeVarIntNum(Number(index))
return writer.toArray()
}
private readOutpoint(reader: Utils.Reader): OutpointString {
const txid = Utils.toHex(reader.read(32))
const index = reader.readVarIntNum()
return `${txid}.${index}`
}
async getPublicKey(
args: {
seekPermission?: BooleanDefaultTrue
identityKey?: true
protocolID?: [SecurityLevel, ProtocolString5To400Bytes]
keyID?: KeyIDStringUnder800Bytes
privileged?: BooleanDefaultFalse
privilegedReason?: DescriptionString5to50Bytes
counterparty?: PubKeyHex | 'self' | 'anyone'
forSelf?: BooleanDefaultFalse
},
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ publicKey: PubKeyHex }> {
const paramWriter = new Utils.Writer()
paramWriter.writeUInt8(args.identityKey ? 1 : 0)
if (!args.identityKey) {
paramWriter.write(
this.encodeKeyRelatedParams(
args.protocolID ??= [SecurityLevels.Silent, 'default'],
args.keyID ??= '',
args.counterparty,
args.privileged,
args.privilegedReason
)
)
if (typeof args.forSelf === 'boolean') {
paramWriter.writeInt8(args.forSelf ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
} else {
paramWriter.write(
this.encodePrivilegedParams(args.privileged, args.privilegedReason)
)
}
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
const result = await this.transmit(
'getPublicKey',
originator,
paramWriter.toArray()
)
return {
publicKey: Utils.toHex(result)
}
}
async revealCounterpartyKeyLinkage(
args: {
counterparty: PubKeyHex
verifier: PubKeyHex
privilegedReason?: DescriptionString5to50Bytes
privileged?: BooleanDefaultFalse
},
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{
prover: PubKeyHex
verifier: PubKeyHex
counterparty: PubKeyHex
revelationTime: ISOTimestampString
encryptedLinkage: Byte[]
encryptedLinkageProof: number[]
}> {
const paramWriter = new Utils.Writer()
paramWriter.write(
this.encodePrivilegedParams(args.privileged, args.privilegedReason)
)
paramWriter.write(Utils.toArray(args.counterparty, 'hex'))
paramWriter.write(Utils.toArray(args.verifier, 'hex'))
const result = await this.transmit(
'revealCounterpartyKeyLinkage',
originator,
paramWriter.toArray()
)
const resultReader = new Utils.Reader(result)
const prover = Utils.toHex(resultReader.read(33))
const verifier = Utils.toHex(resultReader.read(33))
const counterparty = Utils.toHex(resultReader.read(33))
const revelationTimeLength = resultReader.readVarIntNum()
const revelationTime = Utils.toUTF8(
resultReader.read(revelationTimeLength)
)
const encryptedLinkageLength = resultReader.readVarIntNum()
const encryptedLinkage = resultReader.read(encryptedLinkageLength)
const encryptedLinkageProofLength = resultReader.readVarIntNum()
const encryptedLinkageProof = resultReader.read(
encryptedLinkageProofLength
)
return {
prover,
verifier,
counterparty,
revelationTime,
encryptedLinkage,
encryptedLinkageProof
}
}
async revealSpecificKeyLinkage(
args: {
counterparty: PubKeyHex
verifier: PubKeyHex
protocolID: [SecurityLevel, ProtocolString5To400Bytes]
keyID: KeyIDStringUnder800Bytes
privilegedReason?: DescriptionString5to50Bytes
privileged?: BooleanDefaultFalse
},
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{
prover: PubKeyHex
verifier: PubKeyHex
counterparty: PubKeyHex
protocolID: [SecurityLevel, ProtocolString5To400Bytes]
keyID: KeyIDStringUnder800Bytes
encryptedLinkage: Byte[]
encryptedLinkageProof: Byte[]
proofType: Byte
}> {
const paramWriter = new Utils.Writer()
paramWriter.write(
this.encodeKeyRelatedParams(
args.protocolID,
args.keyID,
args.counterparty,
args.privileged,
args.privilegedReason
)
)
paramWriter.write(Utils.toArray(args.verifier, 'hex'))
const result = await this.transmit(
'revealSpecificKeyLinkage',
originator,
paramWriter.toArray()
)
const resultReader = new Utils.Reader(result)
const prover = Utils.toHex(resultReader.read(33))
const verifier = Utils.toHex(resultReader.read(33))
const counterparty = Utils.toHex(resultReader.read(33))
const securityLevel = resultReader.readUInt8()
const protocolLength = resultReader.readVarIntNum()
const protocol = Utils.toUTF8(resultReader.read(protocolLength))
const keyIDLength = resultReader.readVarIntNum()
const keyID = Utils.toUTF8(resultReader.read(keyIDLength))
const encryptedLinkageLength = resultReader.readVarIntNum()
const encryptedLinkage = resultReader.read(encryptedLinkageLength)
const encryptedLinkageProofLength = resultReader.readVarIntNum()
const encryptedLinkageProof = resultReader.read(
encryptedLinkageProofLength
)
const proofType = resultReader.readUInt8()
return {
prover,
verifier,
counterparty,
protocolID: [securityLevel as SecurityLevel, protocol],
keyID,
encryptedLinkage,
encryptedLinkageProof,
proofType
}
}
async encrypt(
args: {
seekPermission?: BooleanDefaultTrue
plaintext: Byte[]
protocolID: [SecurityLevel, ProtocolString5To400Bytes]
keyID: KeyIDStringUnder800Bytes
privilegedReason?: DescriptionString5to50Bytes
counterparty?: PubKeyHex | 'self' | 'anyone'
privileged?: BooleanDefaultFalse
},
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ ciphertext: Byte[] }> {
const paramWriter = new Utils.Writer()
paramWriter.write(
this.encodeKeyRelatedParams(
args.protocolID,
args.keyID,
args.counterparty,
args.privileged,
args.privilegedReason
)
)
paramWriter.writeVarIntNum(args.plaintext.length)
paramWriter.write(args.plaintext)
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
return {
ciphertext: await this.transmit(
'encrypt',
originator,
paramWriter.toArray()
)
}
}
async decrypt(
args: {
seekPermission?: BooleanDefaultTrue
ciphertext: Byte[]
protocolID: [SecurityLevel, ProtocolString5To400Bytes]
keyID: KeyIDStringUnder800Bytes
privilegedReason?: DescriptionString5to50Bytes
counterparty?: PubKeyHex | 'self' | 'anyone'
privileged?: BooleanDefaultFalse
},
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ plaintext: Byte[] }> {
const paramWriter = new Utils.Writer()
paramWriter.write(
this.encodeKeyRelatedParams(
args.protocolID,
args.keyID,
args.counterparty,
args.privileged,
args.privilegedReason
)
)
paramWriter.writeVarIntNum(args.ciphertext.length)
paramWriter.write(args.ciphertext)
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
return {
plaintext: await this.transmit(
'decrypt',
originator,
paramWriter.toArray()
)
}
}
async createHmac(
args: {
seekPermission?: BooleanDefaultTrue
data: Byte[]
protocolID: [SecurityLevel, ProtocolString5To400Bytes]
keyID: KeyIDStringUnder800Bytes
privilegedReason?: DescriptionString5to50Bytes
counterparty?: PubKeyHex | 'self' | 'anyone'
privileged?: BooleanDefaultFalse
},
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ hmac: Byte[] }> {
const paramWriter = new Utils.Writer()
paramWriter.write(
this.encodeKeyRelatedParams(
args.protocolID,
args.keyID,
args.counterparty,
args.privileged,
args.privilegedReason
)
)
paramWriter.writeVarIntNum(args.data.length)
paramWriter.write(args.data)
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
return {
hmac: await this.transmit(
'createHmac',
originator,
paramWriter.toArray()
)
}
}
async verifyHmac(
args: {
seekPermission?: BooleanDefaultTrue
data: Byte[]
hmac: Byte[]
protocolID: [SecurityLevel, ProtocolString5To400Bytes]
keyID: KeyIDStringUnder800Bytes
privilegedReason?: DescriptionString5to50Bytes
counterparty?: PubKeyHex | 'self' | 'anyone'
privileged?: BooleanDefaultFalse
},
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ valid: true }> {
const paramWriter = new Utils.Writer()
paramWriter.write(
this.encodeKeyRelatedParams(
args.protocolID,
args.keyID,
args.counterparty,
args.privileged,
args.privilegedReason
)
)
paramWriter.write(args.hmac)
paramWriter.writeVarIntNum(args.data.length)
paramWriter.write(args.data)
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
await this.transmit('verifyHmac', originator, paramWriter.toArray())
return { valid: true }
}
async createSignature(
args: {
seekPermission?: BooleanDefaultTrue
data?: Byte[]
hashToDirectlySign?: Byte[]
protocolID: [SecurityLevel, ProtocolString5To400Bytes]
keyID: KeyIDStringUnder800Bytes
privilegedReason?: DescriptionString5to50Bytes
counterparty?: PubKeyHex | 'self' | 'anyone'
privileged?: BooleanDefaultFalse
},
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ signature: Byte[] }> {
const paramWriter = new Utils.Writer()
paramWriter.write(
this.encodeKeyRelatedParams(
args.protocolID,
args.keyID,
args.counterparty,
args.privileged,
args.privilegedReason
)
)
if (typeof args.data === 'object') {
paramWriter.writeUInt8(1)
paramWriter.writeVarIntNum(args.data.length)
paramWriter.write(args.data)
} else {
paramWriter.writeUInt8(2)
paramWriter.write(args.hashToDirectlySign ??= [])
}
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
return {
signature: await this.transmit(
'createSignature',
originator,
paramWriter.toArray()
)
}
}
async verifySignature(
args: {
seekPermission?: BooleanDefaultTrue
data?: Byte[]
hashToDirectlyVerify?: Byte[]
signature: Byte[]
protocolID: [SecurityLevel, ProtocolString5To400Bytes]
keyID: KeyIDStringUnder800Bytes
privilegedReason?: DescriptionString5to50Bytes
counterparty?: PubKeyHex | 'self' | 'anyone'
forSelf?: BooleanDefaultFalse
privileged?: BooleanDefaultFalse
},
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<{ valid: true }> {
const paramWriter = new Utils.Writer()
paramWriter.write(
this.encodeKeyRelatedParams(
args.protocolID,
args.keyID,
args.counterparty,
args.privileged,
args.privilegedReason
)
)
if (typeof args.forSelf === 'boolean') {
paramWriter.writeInt8(args.forSelf ? 1 : 0)
} else {
paramWriter.writeInt8(-1)
}
paramWriter.writeVarIntNum(args.signature.length)
paramWriter.write(args.signature)
if (typeof args.data === 'object') {
paramWriter.writeUInt8(1)
paramWriter.writeVarIntNum(args.data.length)
paramWriter.write(args.data)
} else {
paramWriter.writeUInt8(2)
paramWriter.write(args.hashToDirectlyVerify ?? [])
}
// Serialize seekPermission
paramWriter.writeInt8(
typeof args.seekPermission === 'boolean'
? args.seekPermission
? 1
: 0
: -1
)
await this.transmit('verifySignature', originator, paramWriter.toArray())
return { valid: true }
}
private encodeKeyRelatedParams(
protocolID: [SecurityLevel, ProtocolString5To400Bytes],
keyID: KeyIDStringUnder800Bytes,
counterparty?: PubKeyHex | 'self' | 'anyone',
privileged?: boolean,
privilegedReason?: string
): number[] {
const paramWriter = new Utils.Writer()
paramWriter.writeUInt8(protocolID[0])
const protocolAsArray = Utils.toArray(protocolID[1], 'utf8')
paramWriter.writeVarIntNum(protocolAsArray.length)
paramWriter.write(protocolAsArray)
const keyIDAsArray = Utils.toArray(keyID, 'utf8')
paramWriter.writeVarIntNum(keyIDAsArray.length)
paramWriter.write(keyIDAsArray)
if (typeof counterparty !== 'string') {
paramWriter.writeUInt8(0)
} else if (counterparty === 'self') {
paramWriter.writeUInt8(11)
} else if (counterparty === 'anyone') {
paramWriter.writeUInt8(12)
} else {
paramWriter.write(Utils.toArray(counterparty, 'hex'))
}
paramWriter.write(
this.encodePrivilegedParams(privileged, privilegedReason)
)
return paramWriter.toArray()
}
async acquireCertificate(
args: AcquireCertificateArgs,
originator?: OriginatorDomainNameStringUnder250Bytes
): Promise<AcquireCertificateResult> {
const paramWriter = new Utils.Writer()
paramWriter.write(Utils.toArray(args.type, 'base64'))
paramWriter.write(Utils.toArray(args.certifier, 'hex'))
const fieldEntries = Object.entries(args.fields)
paramWriter.writeVarIntNum(fieldEntries.length)
for (const [key, value] of fieldEntries) {
const keyAsArray = Utils.toArray(key, 'utf8')
const valueAsArray = Utils.toArray(value, 'utf8')
paramWriter.writeVarIntNum(keyAsArray.length)
paramWriter.write(keyAsArray)
paramWriter.writeVarIntNum(valueAsArray.length)
paramWriter.write(valueAsArray)
}
paramWriter.write(
this.encodePrivilegedParams(args.privileged, args.privilegedReason)
)
paramWriter.writeUInt8(args.acquisitionProtocol === 'direct' ? 1 : 2)
if (args.acquisitionProtocol === 'direct') {
paramWriter.write(Utils.toArray(args.serialNumber, 'base64'))
paramWriter.write(this.encodeOutpoint(args.revocationOutpoint ?? ''))
const signatureAsArray = Utils.toArray(args.signature, 'hex')
paramWriter.writeVarIntNum(signatureAsArray.length)
paramWriter.write(signatureAsArray)
const keyringRevealerAsArray =
args.keyringRevealer !== 'certifier'
? Utils.toArray(args.keyringRevealer, 'hex')
: [11]
paramWriter.write(keyringRevealerAsArray)
const keyringKeys = Object.keys(args.keyringForSubject ?? {})
paramWriter.writeVarIntNum(keyringKeys.length)
for (let i = 0; i < keyringKeys.length; i++) {
const keyringKeysAsArray = Utils.toArray(keyringKeys[i], 'utf8')
paramWriter.writeVarIntNum(keyringKeysAsArray.length)
paramWriter.write(keyringKeysAsArray)
const keyringForSubjectAsArray = Utils.toArray(
args.keyringForSubject?.[keyringKeys[i]],
'base64'
)
paramWriter.writeVarIntNum(keyringForSubjectAsArray.length)
paramWriter.write(keyringForSubjectAsArray)
}
} else {
const certifierUrlAsArray = Utils.toArray(args.certifierUrl, 'utf8')
paramWriter.writeVarIntNum(certifierUrlAsArray.length)
paramWriter.write(certifierUrlAsArray)
}
const result = await this.transmit(
'acquireCertificate',
originator,
paramWriter.toArray()
)
const cert = Certificate.fromBinary(result)
return {
...cert,
signature: cert.signature as string
}
}
private encodePrivilegedPar