Penumbra dApp is the canonical wallet functionality interface for the Penumbra network.
To use the Penumbra dApp, you must first install the Penumbra wallet extension and import/create a new wallet.
The Penumbra wallet extension is available for installation in the Chrome store.
You can also build the wallet extension locally by following the build instructions.
After you've installed the extension, navigate to the dApp: https://app.testnet.penumbra.zone
-
Building the site locally
npm install
npm run dev
Add library to your app.
-
Configure registry
npm config set @buf:registry https://buf.build/gen/npm/v1/
-
Packages
- bufbuild/connect-es
npm install @buf/penumbra-zone_penumbra.bufbuild_connect-es@latest
- bufbuild/es
npm install @buf/penumbra-zone_penumbra.bufbuild_es@latest
- bufbuild/connect-web
npm install @buf/penumbra-zone_penumbra.bufbuild_connect-web@latest
Add to global.d.ts
import {
AddressByIndexRequest,
AddressByIndexResponse,
AssetsRequest,
AssetsResponse,
ChainParametersRequest,
ChainParametersResponse,
FMDParametersRequest,
FMDParametersResponse,
NotesRequest,
StatusRequest,
StatusResponse,
TransactionInfoByHashRequest,
TransactionInfoByHashResponse,
TransactionPlannerRequest,
TransactionPlannerResponse,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1alpha1/view_pb'
declare global {
interface Window {
penumbra: Penumbra.PenumbraApi
}
}
export declare namespace Penumbra {
type PenumbraApi = {
requestAccounts: () => Promise<[string]>
on(event: Events, cb: (state: any) => any, args?: any): object
getChainParameters: (
request?: ChainParametersRequest
) => Promise<ChainParametersResponse>
getStatus: (request?: StatusRequest) => Promise<StatusResponse>
getFmdParameters: (
request?: FMDParametersRequest
) => Promise<FMDParametersResponse>
signTransaction: (request: any) => Promise<TransactionResponse>
getTransactionInfoByHash: (
request: TransactionInfoByHashRequest
) => Promise<TransactionInfoByHashResponse>
getAddressByIndex: (
request: AddressByIndexRequest
) => Promise<AddressByIndexResponse>
getTransactionPlanner: (
request: TransactionPlannerRequest
) => Promise<TransactionPlannerResponse>
}
}
export type TransactionResponse = {
id: number
jsonrpc: string
result: {
code: 1 | 0
codespace: string
data: string
hash: string
log: string
}
}
export type Events =
| 'state'
| 'status'
| 'balance'
| 'assets'
| 'transactions'
| 'notes'
| 'accountsChanged'
import { createRouterTransport } from '@bufbuild/connect'
import { ViewProtocolService } from '@buf/penumbra-zone_penumbra.bufbuild_connect-es/penumbra/view/v1alpha1/view_connect'
import {
AddressByIndexRequest,
AssetsRequest,
AssetsResponse,
BalanceByAddressRequest,
BalanceByAddressResponse,
ChainParametersRequest,
ChainParametersResponse,
FMDParametersRequest,
FMDParametersResponse,
NotesRequest,
NotesResponse,
StatusRequest,
StatusResponse,
StatusStreamRequest,
StatusStreamResponse,
TransactionInfoByHashRequest,
TransactionInfoByHashResponse,
TransactionInfoRequest,
TransactionInfoResponse,
TransactionPlannerRequest,
TransactionPlannerResponse,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1alpha1/view_pb'
export const extensionTransport = (s: typeof ViewProtocolService) =>
createRouterTransport(({ service }) => {
let receiveMessage: (value: unknown) => void = function () {}
function waitForNextMessage() {
return new Promise(resolve => {
receiveMessage = resolve
})
}
async function* createMessageStream() {
while (true) {
yield waitForNextMessage()
}
}
service(s, {
status: async (message: StatusRequest) => {
const response = await window.penumbra.getStatus()
return new StatusResponse(response)
},
addressByIndex: async (request: AddressByIndexRequest) => {
const response = await window.penumbra.getAddressByIndex(request)
return response
},
transactionPlanner: async (message: TransactionPlannerRequest) => {
const response = await window.penumbra.getTransactionPlanner(message)
return new TransactionPlannerResponse(response)
},
transactionInfoByHash: async (message: TransactionInfoByHashRequest) => {
const response = await window.penumbra.getTransactionInfoByHash(message)
return new TransactionInfoByHashResponse(response)
},
fMDParameters: async (message: FMDParametersRequest) => {
const response = await window.penumbra.getFmdParameters()
return new FMDParametersResponse(response)
},
chainParameters: async (message: ChainParametersRequest) => {
const response = await window.penumbra.getChainParameters()
return new ChainParametersResponse(response)
},
async *statusStream(message: StatusStreamRequest) {
window.penumbra.on('status', status => receiveMessage(status))
for await (const res of createMessageStream()) {
yield new StatusStreamResponse(res)
}
},
async *assets(message: AssetsRequest) {
window.penumbra.on('assets', asset => receiveMessage(asset))
for await (const res of createMessageStream()) {
yield new AssetsResponse(res)
}
},
async *balanceByAddress(message: BalanceByAddressRequest) {
window.penumbra.on('balance', balance => receiveMessage(balance))
for await (const res of createMessageStream()) {
yield new BalanceByAddressResponse(res)
}
},
async *notes(message: NotesRequest) {
window.penumbra.on('notes', note => receiveMessage(note))
for await (const res of createMessageStream()) {
yield new NotesResponse(res)
}
},
async *transactionInfo(message: TransactionInfoRequest) {
window.penumbra.on(
'transactions',
tx => receiveMessage(tx),
message.toJson()
)
for await (const res of createMessageStream()) {
yield new TransactionInfoResponse(res)
}
},
})
})
import { createPromiseClient } from '@bufbuild/connect'
import { ViewProtocolService } from '@buf/penumbra-zone_penumbra.bufbuild_connect-es/penumbra/view/v1alpha1/view_connect'
const client = createPromiseClient(
ViewProtocolService,
extensionTransport(ViewProtocolService)
)
Authenticates user with his/her account;
Usage:
const poll = (
resolve: (result: boolean) => void,
reject: (...args: unknown[]) => void,
attempt = 0,
retries = 30,
interval = 100
) => {
if (attempt > retries) return resolve(false)
if (typeof window !== 'undefined' && 'undefined') {
return resolve(true)
} else setTimeout(() => poll(resolve, reject, ++attempt), interval)
}
const _isPenumbraInstalled = new Promise(poll)
export async function isPenumbraInstalled() {
return _isPenumbraInstalled
}
const [walletAddress, setWalletAddress] = useState<string>('')
const [isPenumbra, setIsPenumbra] = useState<boolean>(false)
const checkIsPenumbraInstalled = async () => {
const isInstalled = await isPenumbraInstalled()
setIsPenumbra(isInstalled)
}
useEffect(() => {
checkIsPenumbraInstalled()
}, [])
useEffect(() => {
if (!isPenumbra) return
addWalletListener(isPenumbra)
}, [isPenumbra])
const addWalletListener = async (isPenumbra: boolean) => {
if (isPenumbra) {
window.penumbra.on('accountsChanged', (accounts: [string]) => {
setWalletAddress(accounts[0])
})
} else {
/* Penumbra is not installed */
setWalletAddress('')
console.log('Please install Penumbra Wallet')
}
}
const signin = async () => {
if (isPenumbra) {
try {
/* Penumbra is installed */
const accounts = await window.penumbra.requestAccounts()
setWalletAddress(accounts[0])
} catch (err) {
console.error(err)
}
} else {
/* Penumbra is not installed */
console.log('Please install Penumbra Wallet')
}
}
Output example:
[
'penumbrav2t13vh0fkf3qkqjacpm59g23ufea9n5us45e4p5h6hty8vg73r2t8g5l3kynad87uvn9eragf3hhkgkhqe5vhngq2cw493k48c9qg9ms4epllcmndd6ly4v4dwwjcnxaxzjqnlvnw',
]
The view protocol is used by a view client, who wants to do some transaction-related actions, to request data from a view service, which is responsible for synchronizing and scanning the public chain state with one or more full viewing keys.
View protocol requests optionally include the account group ID, used to identify which set of data to query.
- Status
- Status Stream
- Notes
- Assets
- ChainParameters
- FMDParameters
- AddressByIndex
- BalanceByAddress
- TransactionInfoByHash
- TransactionInfo
- TransactionPlanner
Get current status of chain sync
const request = new StatusRequest({})
const response = await client.status(request)
Queries for notes that have been accepted by the core.chain.v1alpha1.
const request = new StatusStreamRequest({})
for await (const status of client.statusStream(statusRequest)) {
console.log(status)
}
Queries for notes that have been accepted by the core.chain.v1alpha1.
const request = new StatusStreamRequest({})
for await (const status of client.statusStream(statusRequest)) {
console.log(status)
}
Queries for notes that have been accepted by the core.chain.v1alpha1.
const request = new NotesRequest({})
for await (const note of client.notes(statusRequest)) {
console.log(note)
}
Queries for assets that have been accepted by the core.chain.v1alpha1.
const request = new AssetsRequest({})
for await (const asset of client.assets(request)) {
console.log(asset)
}
Query for the current chain parameters.
const request = new ChainParametersRequest({})
const response = await client.chainParameters(request)
Query for the current FMD parameters.
const request = new FMDParametersRequest({})
const response = await client.fMDParameters(request)
Query for an address given an address index
const request = new AddressByIndexRequest({})
const response = await client.addressByIndex(request)
Query for balance of a given address
const request = new BalanceByAddressRequest({})
for await (const balance of client.balanceByAddress(request)) {
console.log(balance)
}
Query for a given transaction by its hash.
const request = new BalanceByAddressRequest({})
const response = await client.transactionInfoByHash(request)
Query for the full transactions in the given range of blocks.
const request = new TransactionInfoRequest({})
for await (const balance of client.transactionInfo(request)) {
console.log(balance)
}
Query for a transaction plan
const request = new TransactionPlannerRequest({})
const response = await client.transactionPlanner(request)
const client = createPromiseClient(ViewProtocolService,extensionTransport(ViewProtocolService))
const transactionPlan = (await client.transactionPlanner(new TransactionPlannerRequest({
outputs: [
{
value: {
amount: {
lo: [amont * 10 ** exponents],
hi: 0
},
assetId:
{ inner: [assetId]
},
},
address: {
inner: [receiver as Uint8Array],
altBech32m: [reciever],
},
},
],
}))).plan
const tx = await window.penumbra.signTransaction(transactionPlan?.toJson())
Error's class | Code | Type | Example |
---|---|---|---|