Skip to content

Commit

Permalink
Merge pull request #138 from tallycash/pop-up-video
Browse files Browse the repository at this point in the history
Pop Up Video: Add browser notifications for balance changes
  • Loading branch information
mhluongo committed Aug 15, 2021
2 parents cb98c12 + 7853a83 commit 8b86185
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 64 deletions.
81 changes: 18 additions & 63 deletions api/temp-stubs/index.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,37 @@
// since this is a temp file, disable linting
/* eslint-disable */
import Blocknative, {
BlocknativeNetworkIds,
} from "../third-party-data/blocknative"
import * as stub from "./stub"
import BlocknativeSdk from "bnc-sdk"
import { EthereumTransactionData } from "bnc-sdk/dist/types/src/interfaces"

let time = 1e3 * 15
let importWasCalled = !!window.localStorage.temp
let number = 1
const { accountsResult } = stub

const ethermineAccount = "0xea674fdde714fd979de3edf0f56aa9716b898ec8"
const blockNative = new BlocknativeSdk({
dappId: "6e6dbfa7-7eac-4e59-8ca2-985bee1ece8f",
networkId: 1,
transactionHandlers: [(event) => console.log(event.transaction)],
})

type Account = typeof accountsResult
type Account = typeof stub.accountsResult

// Some remedial typing for BlockNative; see blocknative/sdk#138 .
type EthereumNetBalanceChanges = {
address: string
balanceChanges: EthereumAssetBalanceChanges[]
}

type EthereumAssetBalanceChanges = {
delta: string
asset: AssetDetails
breakdown: TransferDetails[]
}

type AssetDetails = {
type: AssetType
symbol: string
}

type AssetType = "ether" | "ERC20"

type TransferDetails = {
counterparty: string
amount: string
}
const blocknative = Blocknative.connect(
"6e6dbfa7-7eac-4e59-8ca2-985bee1ece8f",
BlocknativeNetworkIds.ethereum.mainnet
)

let handlers: Array<(accounts: Account) => void> = []
function subscribeBlockNative() {
blockNative
.account(ethermineAccount)
.emitter.on("txConfirmed", (transactionData) => {
if (
"contractCall" in transactionData && // not a Bitcoin tx
transactionData.system == "ethereum" // not a log
) {
// Lock type to transaction data, augment with netBalanceChanges info.
const transaction = transactionData as EthereumTransactionData & {
netBalanceChanges: EthereumNetBalanceChanges[]
}

transaction.netBalanceChanges
.filter(({ address }) => address.toLowerCase() == ethermineAccount)
.forEach(({ balanceChanges }) => {
balanceChanges
.filter(({ asset: { type: assetType } }) => assetType == "ether")
.forEach(({ delta }) => {
// TODO Adjust this when we start shipping raw BigInts over to UI.
accountsResult.total_balance.amount +=
Number(BigInt(delta) / 10n ** 16n) / 100
})
})
blocknative.watchBalanceUpdatesFor(
ethermineAccount,
(transaction, balanceDelta) => {
accountsResult.total_balance.amount +=
Number(balanceDelta / 10n ** 16n) / 100
accountsResult.activity.push(transaction)

accountsResult.activity.push(transaction)

handlers.forEach((handler) => handler(accountsResult))
}
})
handlers.forEach((handler) => handler(accountsResult))
}
)
}
function unsubscribeBlockNative() {
blockNative.account(ethermineAccount).emitter.off("txConfirmed")
blockNative.unsubscribe(ethermineAccount)
blocknative.unwatchBalanceUpdatesFor(ethermineAccount)
}

export const apiStubs = {
Expand Down
79 changes: 79 additions & 0 deletions api/third-party-data/blocknative/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import BlocknativeSdk from "bnc-sdk"

import { EthereumTransactionData } from "./types"

export const BlocknativeNetworkIds = {
ethereum: {
mainnet: 1,
},
}

// TODO Improve code to clearly discriminate between Bitcoin and
// TODO Ethereum---either top-level or inside the instance.

/**
* The Blocknative class wraps access to the Blocknative API for the Tally
* extension backend. It exposes Tally-specific functionality, and manages
* connection and disconnection from Blocknative based on registered needs and
* feedback from the Blocknative system to minimize usage when possible.
*/
export default class Blocknative {
private blocknative: BlocknativeSdk

static connect(apiKey: string, networkId: number): Blocknative {
const connection = new this(apiKey, networkId)

return connection
}

private constructor(apiKey: string, networkId: number) {
// TODO Handle lazy connection, disconnects, resubscribing, rate limits,
// TODO etc.
this.blocknative = new BlocknativeSdk({
dappId: apiKey,
networkId,
})
}

watchBalanceUpdatesFor(
accountAddress: string,
handler: (
transactionData: EthereumTransactionData,
balanceDelta: bigint
) => void
): void {
// TODO Centralize handling of txConfirmed.
this.blocknative
.account(accountAddress)
.emitter.on("txConfirmed", (transactionData) => {
if (
"system" in transactionData &&
transactionData.system === "ethereum" // not Bitcoin or a log
) {
const transaction = transactionData as EthereumTransactionData

const balanceDelta = transaction.netBalanceChanges
?.filter(({ address }) => address.toLowerCase() === accountAddress)
.flatMap(({ balanceChanges }) => balanceChanges)
.filter(({ asset: { type: assetType } }) => assetType === "ether")
.reduce(
(ethBalanceChangeDelta, { delta }) =>
ethBalanceChangeDelta + BigInt(delta),
0n
)

if (balanceDelta) {
// Only if there is a balance delta to report.
handler(transaction, balanceDelta)
}
}
})
}

unwatchBalanceUpdatesFor(accountAddress: string): void {
// TODO After centralizing handling, handle overall unsubscribing through
// that mechanism.
this.blocknative.account(accountAddress).emitter.off("txConfirmed")
this.blocknative.unsubscribe(accountAddress)
}
}
29 changes: 29 additions & 0 deletions api/third-party-data/blocknative/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { EthereumTransactionData as BlocknativeEthereumTransactionData } from "bnc-sdk/dist/types/src/interfaces"

// Some remedial typing for BlockNative; see blocknative/sdk#138 .
type EthereumNetBalanceChanges = {
address: string
balanceChanges: EthereumAssetBalanceChanges[]
}

type EthereumAssetBalanceChanges = {
delta: string
asset: AssetDetails
breakdown: TransferDetails[]
}

type AssetDetails = {
type: AssetType
symbol: string
}

type AssetType = "ether" | "ERC20"

type TransferDetails = {
counterparty: string
amount: string
}

export type EthereumTransactionData = BlocknativeEthereumTransactionData & {
netBalanceChanges?: EthereumNetBalanceChanges[]
}
2 changes: 1 addition & 1 deletion manifest/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@

"background": {
"persistent": true,
"scripts": ["background.js"]
"scripts": ["background.js", "background-ui.js"]
}
}
29 changes: 29 additions & 0 deletions src/background-ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { browser, startApi } from "@tallyho/tally-api"

startApi().then(async ({ main }) => {
const accountsApi = main.getApi()["/accounts/"]
let latestActivityHashes = (
await accountsApi.GET({ address: null })
).activity.map(({ hash }) => hash)
main
.getApi()
["/accounts/"].subscribe(
({
total_balance: { amount: totalBalance },
activity: updatedActivity,
}) => {
const newActivity = updatedActivity.filter(({ hash }) =>
latestActivityHashes.includes(hash)
)

browser.notifications.create("balance-udpate", {
type: "basic",
title: "Balance Update",
message: `<address> has balance ${totalBalance}`,
contextMessage: `${newActivity.length} transactions have updated the balance for <address> to ${totalBalance}`,
})

latestActivityHashes = updatedActivity.map(({ hash }) => hash)
}
)
})
1 change: 1 addition & 0 deletions webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const baseConfig: Configuration = {
entry: {
ui: "./src/ui.ts",
background: "./src/background.ts",
"background-ui": "./src/background-ui.ts",
// Don't have these yet.....
// inpage: './src/inpage.js',
// "content-script": './src/content-script.js'
Expand Down

0 comments on commit 8b86185

Please sign in to comment.