Skip to content

Instantly share code, notes, and snippets.

@paullinator
Last active December 11, 2025 17:54
Show Gist options
  • Select an option

  • Save paullinator/c37efc137f50fd7e11e6e4ffca7b0a94 to your computer and use it in GitHub Desktop.

Select an option

Save paullinator/c37efc137f50fd7e11e6e4ffca7b0a94 to your computer and use it in GitHub Desktop.
React Native Zcash SDK Interface
class KeyTool {
static deriveViewKey (seedBytesHex: string): string
static deriveSpendingKey: (seedBytesHex: string): string
}
class AddressTool {
static deriveShieldedAddress (viewingKey: string): string
static deriveTransparentAddress (viewingKey: string): string
static isValidShieldedAddress (address: string): boolean
static isValidTransparentAddress (address: string): boolean
}
interface InitializerConfig {
host: string
port: number
fullViewingKey: string
// alias: ??
birthdayHeight: number
}
interface WalletBalance {
shieldedAvailable: string
shieldedTotal: string
transparentAvailable: string
transparentTotal: string
}
interface InitializerCallbacks {
onError: (e: Error): void
onTransactionsChanged: (): void
onBalanceChanged: (walletBalance: WalletBalance): void
}
interface SpendInfo {
zatoshi: string
toAddress: string
memo: string
fromAccountIndex: number
spendingKey?: string
}
interface ZcashTransaction {
txId: string
fee: string
value: string
direction: 'inbound' | 'outbound'
toAddress: string
memo?: string
minedHeight: number // 0 for unconfirmed
blockTime: number // UNIX timestamp
}
type PendingTransaction = ZcashTransaction & {
accountIndex: number
expiryHeight: number
cancelled: number
submitAttemps: number
errorMessage?: string
errorCode?: number
createTime: number
}
interface TransactionQuery {
offset?: number
limit?: number
startDate?: number
endDate?: number
}
class Synchronizer {
static init (initializerConfig: InitializerConfig): void
setCallbackHandlers (callbacks: InitializerCallbacks): void
getLatestHeight (): number
getProgress (): number // 0-1
// getStatus (): ??
getBalance (): WalletBalance
estimateFee (spendInfo: SpendInfo): string
sendToAddress (spendInfo: SpendInfo): void
start (): void
stop (): void
getPendingTransactions (): PendingTransactions[]
getConfirmedTransactions (query: TransactionQuery): ZcashTransaction[]
}
@gmale
Copy link

gmale commented Aug 10, 2020

Comments:
First, this looks great thanks for putting the energy into writing out the typescript interface! A few things stand out after seeing this converted into code that were probably missing from the original document so I've typed out feedback below:

  1. Alias here is a good catch. Yes, we should include it.
  2. I'm a little concerned about the way balance is represented given that
    a. Transparent and shielded balances operate differently. One requires downloading, then decrypting blocks locally, the other is effectively an instant API call.
    b. In practice, "shielded" and "transparent" funds operate more like two separate coins. If a wallet has "ETH" and "BTC" funds, it might not want one WalletBalance object to represent them both and it might similarly prefer separate BalanceChanged events for each coin.
    c. Mixing both into one WalletBalance object seems like it might cause challenges. Particularly when we have info for one but not the other. Or when one has been updated and the other is stale. Especially if the UI shows any "last updated" timestamps.
    d. Additionally, representing balance as a string seems a bit ambiguous and potentially less precise
    e. Using a Long to represent zatoshi seems less ambiguous and more precise but may require a formatter tool to convert from zatoshi to ZEC and to provide the desired scale and precision.
    f. The SDK currently has such a formatter and we might want to wrap it in a "Tool". In the SDK, everything is always in Zatoshi until it is displayed to a user, at which point the wallet uses the formatter to convert into the precision shown on-screen (sometimes 3 places, sometimes the max of 8)
  3. The name InitializerCallbacks seems a little ambiguous. A developer seeing that might wonder what it contains or how it relates to initialization.
    a. Perhaps we separate these into Change Handlers and Error Handlers, where the former set of callbacks is strictly concerned with receiving updates when anything changes and the latter only handles errors.
    b. Perhaps the word Initializer in InitializerCallbacks could be replaced with something like Wallet or Synchronizer since these callbacks could be set separately from initialization (i.e. setErrorHandlers (callbacks: SynchronizerErrorCallbacks): void)
  4. minedHeight: number // 0 for unconfirmed - since 0 is a valid block number, we've been using -1 for unconfirmed
  5. memo?: string - a memo isn't always a string, it can be 512 bytes of anything. We may want to future-proof this by letting it stay bytes and then providing conversions. Perhaps using a Uint8Array if the added complexity is worth it. Otherwise, we can keep it as a string for simplicity but define a way to distinguish between an empty memo and a memo that exists but cannot be represented as a string.
  6. accountIndex: number - might need to be moved to the base ZcashTransaction object because I think this is something that is always known for a transaction and a necessary property to track when using multiple accounts under one seed.
  7. estimateFee (spendInfo: SpendInfo): string - We don't have any functionality for this, at the moment. For shielded transactions, it is always a fixed value of 10000 zatoshi (or 0.0001 ZEC). We are currently developing full transparent funds support and I'm unclear on how we'll handle transparent fees in the SDK.
  8. getStatus - there is a lot of info available for this. We can iterate and decide on what is useful at this level but what's available currently is (I've bolded the ones that are most useful): Connected, Disconnected, Syncing, Synced, Downloading, Validating, Scanning, Scanned(range: IntRange), Enhancing, Stopped, Initialized
  9. We might want to add getLatestScannedHeight to more clearly differentiate between the height of the network and the height of the wallet. This information can be approximated from progress and status but it's often useful to know exactly which block was processed last.
  10. getPendingTransactions (): PendingTransactions[] - typo. should probably be singular: PendingTransaction[]

@gmale
Copy link

gmale commented Sep 12, 2020

I just read back through my feedback above and converted it into suggested edits on the doc. I don't think I can modify the gist so I've included them here as code snippets:

interface InitializerConfig {
  // bullet 1. add alias to existing properties
  alias: String
}

// bullet 2. split up balance objects
interface WalletBalance {
  available: number // always zatoshi
  total: number // always zatoshi
}
class TransparentBalance implements WalletBalance {
  available: number
  total: number
}
class ShieldedBalance implements WalletBalance {
  available: number
  total: number
}

// bullet 2f. add currency formatter.
class ZecFormatterTool {
  static toZecString (zatoshi: number, minDecimals: number, maxDecimals: number): string
  static toZec (zatoshi: number): number
  static toZatoshi (zec: number): number
}

// bullet 3: split up callbacks
interface SynchronzerChangedCallbacks {
  onTransactionsChanged: (): void
  onBalanceChanged: (walletBalance: WalletBalance): void
}
interface SynchronizerErrorCallbacks {
  // note: hopefully we can subclass errors to match what currently exists: ProcessorError, ChainError, CriticalError, SubmissionError
  onError: (e: Error): void
}

// bullet4: optional change: I'm okay with using 0, we'd just have to adjust in the NativeModule, since we use -1

// bullets 5 & 6
interface ZcashTransaction {
  // keep memo as bytes (perhaps add a formatter to get it as a UTF-8 string)
  memo?: Uint8Array // hopefully, react native can support bytes in some way and we can use whatever format is necessary to achieve that (i.e. maybe just a hex string)
  // move accountIndex up to here from PendingTransaction interface
  accountIndex: number
}

// bullet 7: we probably need to discuss this one

// bullet 8: status info
enum SynchronizerStatus {
  /** Indicates that [stop] has been called on this Synchronizer and it will no longer be used. */
  STOPPED,
  /** Indicates that this Synchronizer is disconnected from its lightwalletd server. When set, a UI element may want to turn red. */
  DISCONNECTED,
  /** Indicates that this Synchronizer is actively downloading new blocks from the server. */
  DOWNLOADING,
  /** Indicates that this Synchronizer is actively validating new blocks that were downloaded from the server. Blocks need to be verified before they are scanned. This confirms that each block is chain-sequential, thereby detecting missing blocks and reorgs. */
  VALIDATING,
  /** Indicates that this Synchronizer is actively decrypting new blocks that were downloaded from the server. */
  SCANNING,
  /** Indicates that this Synchronizer is actively enhancing newly scanned blocks with additional transaction details, fetched from the server. */
  ENHANCING,
  /** Indicates that this Synchronizer is fully up to date and ready for all wallet functions. When set, a UI element may want to turn green. In this state, the balance can be trusted. */
  SYNCED
}

// bullet 9 & 10: scanned v. network height and typo correction
class Synchronizer {
  // split latest height into two values
  getLatestNetworkHeight(): number
  getLatestScannedHeight(): number
  // correct minor typo
  getPendingTransactions (): PendingTransaction[]
}

@gmale
Copy link

gmale commented Sep 23, 2020

Note: formatter probably not be needed.

@gmale
Copy link

gmale commented Sep 23, 2020

Quick notes on action items:

  • add alias
  • return zatoshi as a string and don't worry about conversion utilities (strings force conversion)
  • separate object
  • memo : HEX is fine but it does have to be a string
  • get status: just return processor info

@nuttycom
Copy link

@paullinator I have a number of questions here, but one thing that strikes me right away is that the paginated interface for transaction retrieval seems fragile with respect to race conditions. If you retrieve a page of results, and then some additional syncing happens in the background, the page boundaries might change as additional transactions involving your wallet are discovered. Do you have a suggestion of how you would want the API provider to address this, as a user of the API?

@pacu
Copy link

pacu commented Dec 11, 2025

oh I recall this gist. I think most of it was actually implemented. The paginated was partially but never used.

@paullinator
Copy link
Author

@nuttycom Databases deal with page boundaries all the time. Blockchains are even easier. Since the starting point of the query is fixed at a certain date (or blockheight), the number of transactions at each page, and which transactions are in each page should stay constant. Other than due to a reorg, there shouldn't be any possibility of a page ever returning different transactions given a specific starting date/height.

However, due to the possiblity of reorgs, it would be recommended that SDK users have a rollback number of blocks to query when it gets a notification of new balances or transaction updates. ie.

Let's assume the api uses blockheight instead of date for simplicity
App has ROLLBACK_BLOCKS = 20
Current block is 500000
App requests transactions from date 0 at start. it gets say 10 transactions at various heights. It considers itself synced up to block 500000
App gets notification of new transactions, it then queries the sdk from blockheight 499980 which would get all updates for any transactions that may have been changed/reorged since the last update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment