@subwallet-connect/transaction-preview
v1.0.8
Published
In app preview of Ethereum transactions
Downloads
15
Maintainers
Readme
@subwallet-connect/transaction-preview
A modular UI for previewing a single or set of unsigned Ethereum transactions.
Give Transaction Preview a test run by previewing Vitalik swapping tokens and find full package documentation here
Full Simulation Platform API documentation can be found here
Install
NPM
npm i @subwallet-connect/core @subwallet-connect/injected @subwallet-connect/transaction-preview
Yarn
yarn add @subwallet-connect/core @subwallet-connect/injected @subwallet-connect/transaction-preview
Usage with Web3-Onboard Core package
To use the Transaction Preview package with web3-onboard all a developer needs to do is initialize web3-onboard with their Blocknative API key and pass in the module as shown below.
import Onboard from '@subwallet-connect/core'
import injectedModule from '@subwallet-connect/injected'
import transactionPreviewModule from '@subwallet-connect/transaction-preview'
const injected = injectedModule()
const transactionPreview = transactionPreviewModule({
// Optional: Require balance change approval prior to sending transaction to wallet
// Defaults to true
// requireTransactionApproval?: false
// i18n?: i18nOptions - Internationalization options
})
const onboard = Onboard({
transactionPreview,
apiKey: 'xxx387fb-bxx1-4xxc-a0x3-9d37e426xxxx'
wallets: [injected],
chains: [
{
id: '0x1',
token: 'ETH',
label: 'Ethereum',
rpcUrl: 'https://mainnet.infura.io/v3/17c1e1500e384acfb6a72c5d2e67742e'
}
]
// ... other Onboard options
})
// Transaction code here using Ether.js or Web3.js or custom
// The transaction will automatically be picked up and simulated with a UI displaying in the upper right corner
Standalone Usage
To use the Transaction Preview package without web3-onboard all a developer needs to do is:
- Execute the entry function from the
@subwallet-connect/transaction-preview
package and optional params - Run the returned
init
function with their Blocknative API key, an initialized instance of their Blocknative SDK and a containerElement string with the html ID of the target element to append the visualization to - Finally pass a transaction meant for a wallet provider (created using libraries like Ethers or Web3)
With the above steps a UI will be rendered with the balance changes and gas used.
import transactionPreviewModule from '@subwallet-connect/transaction-preview'
const {init, previewTransaction} = transactionPreviewModule({
// Optional: Require balance change approval prior to sending transaction to wallet
// Defaults to true
// requireTransactionApproval?: false
// i18n?: i18nOptions - Internationalization options
})
await init({
/**
* Blocknative API key (https://explorer.blocknative.com/account)
*/
apiKey: string
/**
* Your Blocknative SDK instance
* */
sdk: SDK
/**
* Optional dom query string to mount UI to
* */
containerElement: string})
// Transaction code here using Ether.js or Web3.js or construct your own transactions
const simulate = async provider => {
// if using ethers v6 this is:
// ethersProvider = new ethers.BrowserProvider(wallet.provider, 'any')
const ethersProvider = new ethers.providers.Web3Provider(provider, 'any')
const signer = ethersProvider.getSigner()
const addressFrom = '0xcxxxxxx11111999991111'
// Uniswap V2
const CONTRACT_ADDRESS = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d'
const erc20_interface = [
'function approve(address _spender, uint256 _value) public returns (bool success)',
'function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)',
'function balanceOf(address owner) view returns (uint256)'
]
const uniswapV2router_interface = [
'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)'
]
const weth = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
const oneInch = '0x111111111117dc0aa78b770fa6a738034120c302'
let swapTxData
let approveTxData
const swapContract = new ethers.Contract(
CONTRACT_ADDRESS,
uniswapV2router_interface
)
const erc20_contract = new ethers.Contract(oneInch, erc20_interface)
const oneEther = ethers.BigNumber.from('9000000000000000000')
approveTxData = await erc20_contract.populateTransaction.approve(
CONTRACT_ADDRESS,
oneEther
)
const amountOutMin = 0
const amountOutMinHex = ethers.BigNumber.from(amountOutMin).toHexString()
const path = [oneInch, weth]
const deadline = Math.floor(Date.now() / 1000) + 60 * 1 // 1 minutes from the current Unix time
const inputAmountHex = oneEther.toHexString()
swapTxData = await swapContract.populateTransaction.swapExactTokensForETH(
inputAmountHex,
amountOutMinHex,
path,
addressFrom,
deadline
)
const uniswapV2Router = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
const popApproveTransaction = await signer.populateTransaction(approveTxData)
const popTransaction = await signer.populateTransaction(swapTxData)
const transactions = [
{ ...popApprovedTransaction, value: 0 },
{
...popTransaction,
from: addressFrom,
to: uniswapV2Router,
value: 0
}
]
return await previewTransaction(transactions)
}
const simData = simulate(ethereumProvider)
console.log(simData)
Options & Types
export type TransactionPreviewModule = (
options: TransactionPreviewOptions
) => TransactionPreviewAPI
export type FullPreviewOptions = TransactionPreviewOptions &
TransactionPreviewInitOptions
export type TransactionPreviewAPI = {
/**
* This Method accepts a standard EIP1193 provider
* (such as an injected wallet from window.ethereum)
* and it will be patched to allow for transaction previewing
*/
patchProvider: (provider: PatchedEIP1193Provider) => PatchedEIP1193Provider
/**
* This Method accepts:
* apiKey: string - Blocknative API key (https://explorer.blocknative.com/)
* sdk: instance of an initialized bnc-sdk (www.npmjs.com/package/bnc-sdk)
* containerElement: string of an html id selector (e.g. "#my-html-el")
*/
init: (initializationOptions: TransactionPreviewInitOptions) => void
/**
* This method accepts a transaction meant for a wallet provider
* (created using libraries like Ethers or Web3),
* simulates the transaction and generates a corresponding UI and
* return a response from the Blocknative Transaction Preview API.
* Note: the package will need to initialized with the `init`
* function prior to usage
*/
previewTransaction: (
transaction: TransactionForSim[]
) => Promise<MultiSimOutput>
}
export type PatchedEIP1193Provider = EIP1193Provider & { simPatched: boolean }
export interface ProviderReq {
method: string
params?: Array<unknown>
}
export type RequestOptions = Pick<TransactionPreviewInitOptions, 'apiKey'>
export type TransactionPreviewInitOptions = {
/**
* Blocknative API key (https://explorer.blocknative.com/account)
*/
apiKey: string
/**
* Your Blocknative SDK instance (https://www.npmjs.com/package/bnc-sdk)
* */
sdk: SDK
/**
* Optional dom query string to mount UI to
* */
containerElement: string
}
export type TransactionPreviewOptions = {
/**
* Optional requirement for user to accept transaction balance changes
* prior to sending the transaction to the wallet
* Defaults to true
* */
requireTransactionApproval?: boolean
/**
* An optional internationalization object that defines the display
* text for different locales. Can also be used to override the default text.
* To override the default text, pass in a object for the en locale
*/
i18n?: i18nOptions
}
export type Locale = string
export type i18nOptions = Record<Locale, i18n>
export type i18n = typeof en
export type DeviceNotBrowser = {
type: null
os: null
browser: null
}
export type TransactionForSim = SimulationTransaction & {
data?: string
}
export interface SimulationTransaction {
from: string
to: string
value: number
gas: number
input: string
// Either Type 1 Gas (gasPrice) or Type 2 Gas (maxPriorityFeePerGas & maxFeePerGas)
// must be included in the payload
gasPrice?: number
maxPriorityFeePerGas?: number
maxFeePerGas?: number
}
export type MultiSimOutput = {
id?: string
contractCall: ContractCall[]
error?: any
gasUsed: number[]
internalTransactions: InternalTransaction[][]
netBalanceChanges: NetBalanceChange[][]
network: Network
simDetails: SimDetails
serverVersion: string
system: System
status: Status
simulatedBlockNumber: number
transactions: InternalTransaction[]
}
export interface ContractCall {
contractType?: string
contractAddress?: string
contractAlias?: string
methodName: string
params: Record<string, unknown>
contractName?: string
contractDecimals?: number
decimalValue?: string
}
export interface InternalTransaction {
type: string
from: string
to: string
input: string
gas: number
gasUsed: number
value: string
contractCall: ContractCall
error?: string
errorReason?: string
}
export interface NetBalanceChange {
address: string
balanceChanges: BalanceChange[]
}
export interface BalanceChange {
delta: string
asset: Asset
breakdown: BreakDown[]
}
export interface Asset {
type: string
symbol: string
contractAddress: string
}
export interface BreakDown {
counterparty: string
amount: string
}
export interface InternalTransaction {
type: string
from: string
to: string
input: string
gas: number
gasUsed: number
value: string
contractCall: ContractCall
}
export type System = 'bitcoin' | 'ethereum'
export type Network =
| 'main'
| 'testnet'
| 'ropsten'
| 'rinkeby'
| 'goerli'
| 'kovan'
| 'xdai'
| 'bsc-main'
| 'matic-main'
| 'fantom-main'
| 'matic-mumbai'
| 'local'
export type Status =
| 'pending'
| 'confirmed'
| 'speedup'
| 'cancel'
| 'failed'
| 'dropped'
| 'simulated'
export interface SimDetails {
blockNumber: number
e2eMs: number
performanceProfile: any
}