Skip to content

Commit

Permalink
docs: updating Namada Keychain specs (#1400)
Browse files Browse the repository at this point in the history
  • Loading branch information
jurevans authored Dec 12, 2024
1 parent 5fae455 commit 07806b0
Showing 1 changed file with 53 additions and 95 deletions.
148 changes: 53 additions & 95 deletions specs/browser-extension/integration.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
# Browser Extension - API & Integration
# Namada Keychain - API & Integration

The extension is able to be integrated with in a similar fashion to Keplr and Metamask. If the extension has been installed (and assuming the user has gone through the process of
setting up an account), we can use the following to integrate with another interface.

For integrating an application with the extension, it is recommended that you import the `@namada/integrations` and `@namada/types` packages.
For integrating an application with the extension, it is recommended that you import the `@namada/types` package.

## Table of Contents

- [Detecting the Extension](#detecting-the-extension)
- [Connecting to the Extension](#connecting-to-the-extension)
- [Using the Namada Integration](#using-the-namada-integration)
- [Using the SDK client](#using-the-sdk-client)
- [Submitting a Transfer](#submitting-a-transfer)
- [Using the Namada Keychain injected provider](#using-the-namada-keychain-injected-provider)
- [Handling Extension Events](#handling-extension-events)

## Detecting the extension
Expand All @@ -22,34 +20,32 @@ If the extension is installed, and the domain is enabled (currently, all domains
const isExtensionInstalled = typeof window.namada === "object";
```

A better practice would be to use the `Namada` integration, which provides functionality for interacting with the Extension's public API:
A better practice would be to define a helper (and hook, if using React). See [useNamadaKeychain.ts](../../apps/namadillo/src/hooks/useNamadaKeychain.ts) in Namadillo for an example on how to do this.

```ts
import { Namada } from "@namada/integrations";
With this hook, you can invoke like this (in React):

// Create integration instance
const chainId = "namada-test.XXXXXXXXXX"; // Replace with chain ID
const namada = new Namada(chainId);
```ts
const { connect } = useNamadaKeychain();

// Detect extension
const isDetected = namada.detect(); // boolean
// ...
await connect();
// ...
```

The `@namada/integrations` package provides a common interface for interacting with extensions, with Keplr & Metamask currently supported alongside Namada.

[ [Table of Contents](#table-of-contents) ]

## Connecting to the extension

To connect your application to the extension, you can invoke the following, providing a `chainId`:
To connect your application to the extension manually after detection, you can invoke the following, providing a `chainId`:

```ts
import { Namada } from "@namada/integrations";

import { WindowWithNamada } from "@namada/types";
const chainId = "namada-test.XXXXXXXXXX";
const namada = new Namada(chainId);

async function myApp(): Promise<void> {
const namada = (window as WindowWithNamada).namada;
// Prompt to approve connection:
await namada.connect(chainId);
}

Expand All @@ -60,119 +56,81 @@ This will prompt the user to either `Accept` or `Reject` a connection, and a cli

[ [Table of Contents](#table-of-contents) ]

## Using the Namada Integration
## Using the Namada Keychain injected provider

The `Namada` integration provides a convenience method to query all accounts from the extension:
The `Namada Keychain` injected class provides several methods for interacting with the keychain:

```ts
async function myApp(): Promise<void> {
// Connect to extension
await namada.connect(chainId);

// Query accounts with integration
const accounts = await client.accounts();
const accounts = await namada.accounts();

// Alternatively, and as is most often the case, you will be interacting with the SDK client,
// which contains this function as well:
const client = await namada.signer();
console.log(await client.accounts());
// Query default account, which is currently selected in keychain
const defaultAccount = await namada.defaultAccount()
}

// Example accounts results:
[
{
"alias": "My Account",
"address": "atest1d9khqw36xc65xwp3xc6rwsfcxpprssesxsenjs3cxpznqvfkxppnxw2989pnssfkgsenzvphx0u6kj",
"chainId": "namada-75a7e12.69483d59a9fb174",
"isShielded": false
"address": "tnam1qry3lnk03j965y92np6e25jvadk3kw9u7cvwjclp",
...
},
...
]
```

The `Namada` integration can also be used to query balances for these accounts:

```ts
const owner =
"atest1d9khqw36xc65xwp3xc6rwsfcxpprssesxsenjs3cxpznqvfkxppnxw2989pnssfkgsenzvphx0u6kj";
const balances = await client.queryBalances(owner);
```

[ [Table of Contents](#table-of-contents) ]

## Using the SDK client

The SDK client (an instance of the `Signer` class, defined in [Signer.ts](https://github.com/anoma/namada-interface/blob/main/apps/extension/src/provider/Signer.ts)),
provides the functionality of encoding transactions to be signed and broadcasted by the SDK, passing
these encoded transactions to the [Namada.ts](https://github.com/anoma/namada-interface/blob/main/apps/extension/src/provider/Namada.ts)
provider (which is responsible for constructing messages to pass into the extension). The `Signer` represents
a portion of the public API exposed when the extension is installed.
### Signing with the keychain

### Submitting a Transfer

In order to submit a `Transfer` transaction via the extension integration, we can make use of the `AccountType`, TransferProps`and`TxProps` type definitions.
The injected `namada` provider gives access to signing functionality. The `namada.sign()` method can sign any number of transactions with the specified account address:

```ts
import { AccountType, TransferProps, TxProps } from "@namada/types";
// SignProps from @namada/types

export type SignProps = {
// address of signing account
signer: string;
// array of TxProps, which is the type returned from the various `build...` functions in the SDK package
txs: TxProps[];
// Optionally, provide wasm Tx checksums in signing requests. This allows
// the keychain to deserialize Tx data and display details
checksums?: Record<string, string>;
};
```

`TxProps` represents the transaction parameters, whereas `TransferProp` represents the details of the transfer itself.
We use the `AccountType` enum to specify the type of account, which in turn will determine how signing occurs (e.g., if
the account is of type `AccountType.Ledger`, we will expect signing to occur externally on the hardware wallet, otherwise,
the extension will query the keystore for the keys associated with that `source` address, and sign via the SDK).

Consider this example:
The following example assumes you are building transactions with the `@namada/sdk` package. See [packages/sdk/README.md](../../packages/sdk/README.md) for more details.

```ts
import { AccountType, TransferProps, TxProps } from "@namada/types";

// Address for NAM token
const token =
"atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5";
const chainId = "namada-devnet.95bcc8eaf0f39f1d3fa27629";

const myTransferTx: TransferProps = {
source:
"atest1d9khqw36g3ryxd29xgmrjsjr89znsse5g5cn2wpsgs6nqv33xguryw2p89znsd2rxqcnzvehcnyzxw",
target:
"atest1d9khqw36xcmyzve3gvmrqdfexdz5zd2rxu6nv3zp8ym5zwfcgyeygv2x8pzrz3fcgscngs3nchvahj",
token,
amount: new BigNumber(1.234),
nativeToken: "NAM",
// NOTE: tx adheres to the TxProps type
tx: {
token,
feeAmount: new BigNumber(100),
gasLimit: new BigNumber(200),
chainId,
},
};
import { WindowWithNamada } from "@namada/types";

async function myApp(): Promise<void> {
const namada = new Namada("namada-test.XXXXXXXXXX");
// Address of signer:
const signer = "tnam1qry3lnk03j965y92np6e25jvadk3kw9u7cvwjclp";

if (!namada.detect()) {
throw new Error("The extension is not installed!");
}
// Within your app
const { rpc, tx } = sdk;
const bond = tx.buildBond(props);

const client = namada.signer();
// Using Namada Keychain to sign:
const namada = (window as WindowWithNamada).namada;

await client
.submitTransfer(myTransferTx, AccountType.PrivateKey)
.then(() =>
console.log("Transaction was approved by user and submitted via the SDK")
)
.catch((e) => console.error(`Transaction was rejected: ${e}`));
}
const signedTx = await namada.sign({ signer, txs: [bond] });

myApp();
// See SDK for details on broadcasting:
const txResponse = await rpc.broadcastTx(signedTx, txArgs);

// NOTE: Optionally, you can use an alternative interface to signing with the Signer client.
// This can accept one or more transactions:

const client = await namada.getSigner();
const signedBond = await client.sign(signer, bond);
```

In the above example, we define the transfer transaction properties, connect to the extension, and submit
a transfer transaction. The extension will launch a popup prompting the user for approval. Once the transfer
is approved, an event is dispatched from the extension indicating that a transaction has started, along with
the transaction type. When the transaction is completed, another event will fire indicating it's status.
If the transaction fails in the SDK, the returned error will be dispatched in the completion event.
See [tx.ts](../../packages/types/src/tx/schema/tx.ts) to see properties of `TxProps` (this type is aliased from the `TxMsgValue` schema).

See [Handling Extension Events](#handling-extension-events) for more information on what extension events may be subscribed to.

Expand Down

1 comment on commit 07806b0

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.