Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,6 @@ hardware through WebHID or BitBoxBridge:
make sandbox-dev
```

The sandbox aliases `bitbox-api-ts` to `../src/index.ts`, so source edits
The sandbox aliases `@bitboxswiss/bitbox-api` to `../src/index.ts`, so source edits
hot-reload without building `dist/`. Browsers cannot connect to the raw TCP
simulator transport directly; simulator coverage belongs in `npm run test:sim`.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# bitbox-api-ts
# @bitboxswiss/bitbox-api

Pure TypeScript library for integrating BitBox02 hardware wallets in browser
applications.

`bitbox-api-ts` is source-compatible with the current `bitbox-api`
`@bitboxswiss/bitbox-api` is source-compatible with the current `bitbox-api`
Rust/WASM package for the implemented surface, but it does not ship WASM and
does not require a WASM init step.

Expand All @@ -20,7 +20,7 @@ does not require a WASM init step.
## Installation

```bash
npm install bitbox-api-ts
npm install @bitboxswiss/bitbox-api
```

`@noble/ciphers`, `@noble/curves`, and `@noble/hashes` are peer dependencies so
Expand All @@ -43,7 +43,7 @@ For currently implemented methods, the intended migration is just the import
name:

```ts
import * as bitbox from 'bitbox-api-ts';
import * as bitbox from '@bitboxswiss/bitbox-api';
```

There is no `init()` call and no WASM loader. Existing Webpack/Vite WASM plugin
Expand All @@ -68,7 +68,7 @@ compatibility, but currently reject with typed errors as listed in
## Connecting and Pairing

```ts
import * as bitbox from 'bitbox-api-ts';
import * as bitbox from '@bitboxswiss/bitbox-api';

async function connectBitBox(): Promise<bitbox.PairedBitBox | undefined> {
try {
Expand Down Expand Up @@ -131,8 +131,10 @@ const address = await bb02.ethAddress(chainId, keypath, true);
```

Transaction byte fields are big-endian `Uint8Array`s without a `0x` prefix.
Pass `ethIdentifyCase()` for the optional recipient case hint when you derive
the transaction from a hex address string.
Returned signature byte fields are plain `number[]` arrays, matching the
runtime shape of the old WASM package. Pass `ethIdentifyCase()` for the
optional recipient case hint when you derive the transaction from a hex address
string.

```ts
function hexToBytes(hex: string): Uint8Array {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "bitbox-api-ts",
"name": "@bitboxswiss/bitbox-api",
"version": "0.1.0",
"description": "A pure TypeScript library to interact with BitBox hardware wallets.",
"license": "Apache-2.0",
Expand Down
2 changes: 1 addition & 1 deletion sandbox/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BitBox02 sandbox (bitbox-api-ts)</title>
<title>BitBox02 sandbox (@bitboxswiss/bitbox-api)</title>
</head>
<body>
<div id="root"></div>
Expand Down
6 changes: 3 additions & 3 deletions sandbox/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0

import { useState } from 'react';
import * as bitbox from 'bitbox-api-ts';
import * as bitbox from '@bitboxswiss/bitbox-api';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is the idea to not bring the wasm api under the @BitBoxSwiss namespace and deprecate it eventually? So they're both 'bitbox-api', but only this one is under @BitBoxSwiss?

import './App.css';

import { Ethereum } from './Ethereum';
Expand Down Expand Up @@ -86,7 +86,7 @@ function App() {
<button onClick={() => bb02.close()}>Close connection</button>
</div>
<Accordion opened title="General">
<General />
<General bb02={bb02} />
</Accordion>
{bb02.ethSupported() && (
<Accordion title="Ethereum">
Expand Down Expand Up @@ -124,7 +124,7 @@ function App() {
)}
<p className="portNote">
This sandbox is backed by the in-tree{' '}
<a href="https://github.com/BitBoxSwiss/bitbox-api-ts">bitbox-api-ts</a>{' '}
<a href="https://github.com/BitBoxSwiss/bitbox-api-ts">@bitboxswiss/bitbox-api</a>{' '}
package. It validates the current browser integration and currently
wired flows; it is not intended to track full API parity.
</p>
Expand Down
25 changes: 11 additions & 14 deletions sandbox/src/Ethereum.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0

import { FormEvent, useState } from 'react';
import * as bitbox from 'bitbox-api-ts';
import * as bitbox from '@bitboxswiss/bitbox-api';

import { ErrorNotification } from './ErrorNotification';

Expand Down Expand Up @@ -39,6 +39,10 @@ function ResultBlock({ value }: { value: string }) {
);
}

function formatResult(value: unknown | undefined): string {
return value === undefined ? '' : JSON.stringify(value, null, 2);
}

function EthXPub({ bb02 }: Props) {
const keypaths = ["m/44'/60'/0'/0", "m/44'/1'/0'/0"];
const [keypath, setKeypath] = useState(keypaths[0]);
Expand Down Expand Up @@ -199,7 +203,7 @@ function EthSignTransaction({ bb02 }: Props) {
<label>Transaction</label>
<textarea value={txJson} onChange={e => setTxJson(e.target.value)} rows={9} />
<button type="submit" disabled={running}>Sign transaction</button>
<ResultBlock value={result ? JSON.stringify(result, replaceUint8Arrays, 2) : ''} />
<ResultBlock value={formatResult(result)} />
{err !== undefined && (
<ErrorNotification message={err.message} code={err.code} onClose={() => setErr(undefined)} />
)}
Expand Down Expand Up @@ -270,7 +274,7 @@ function EthSign1559Transaction({ bb02 }: Props) {
<label>Transaction</label>
<textarea value={txJson} onChange={e => setTxJson(e.target.value)} rows={9} />
<button type="submit" disabled={running}>Sign EIP-1559 transaction</button>
<ResultBlock value={result ? JSON.stringify(result, replaceUint8Arrays, 2) : ''} />
<ResultBlock value={formatResult(result)} />
{err !== undefined && (
<ErrorNotification message={err.message} code={err.code} onClose={() => setErr(undefined)} />
)}
Expand Down Expand Up @@ -320,7 +324,7 @@ function EthSignMessage({ bb02 }: Props) {
<label>Message</label>
<textarea value={msg} onChange={e => setMsg(e.target.value)} rows={4} />
<button type="submit" disabled={running}>Sign message</button>
<ResultBlock value={result ? JSON.stringify(result, replaceUint8Arrays, 2) : ''} />
<ResultBlock value={formatResult(result)} />
{err !== undefined && (
<ErrorNotification message={err.message} code={err.code} onClose={() => setErr(undefined)} />
)}
Expand Down Expand Up @@ -417,7 +421,7 @@ function EthSignTypedMessage({ bb02 }: Props) {
<label>EIP-712 typed message</label>
<textarea value={msg} onChange={e => setMsg(e.target.value)} rows={20} />
<button type="submit" disabled={running}>Sign typed message</button>
<ResultBlock value={result ? JSON.stringify(result, replaceUint8Arrays, 2) : ''} />
<ResultBlock value={formatResult(result)} />
{err !== undefined && (
<ErrorNotification message={err.message} code={err.code} onClose={() => setErr(undefined)} />
)}
Expand All @@ -426,13 +430,6 @@ function EthSignTypedMessage({ bb02 }: Props) {
);
}

function replaceUint8Arrays(_key: string, value: unknown): unknown {
if (value instanceof Uint8Array) {
return Array.from(value, b => b.toString(16).padStart(2, '0')).join('');
}
return value;
}

const STREAMING_THRESHOLD = 6144;
const DEFAULT_STREAMING_BYTES = 7000;

Expand Down Expand Up @@ -507,7 +504,7 @@ function EthSign1559TransactionStreaming({ bb02 }: Props) {
/>
</label>
<button type="submit" disabled={running}>Sign streaming transaction</button>
<ResultBlock value={result ? JSON.stringify(result, replaceUint8Arrays, 2) : ''} />
<ResultBlock value={formatResult(result)} />
{err !== undefined && (
<ErrorNotification message={err.message} code={err.code} onClose={() => setErr(undefined)} />
)}
Expand Down Expand Up @@ -583,7 +580,7 @@ function EthSignTypedMessageStreaming({ bb02 }: Props) {
/>
</label>
<button type="submit" disabled={running}>Sign streaming typed message</button>
<ResultBlock value={result ? JSON.stringify(result, replaceUint8Arrays, 2) : ''} />
<ResultBlock value={formatResult(result)} />
{err !== undefined && (
<ErrorNotification message={err.message} code={err.code} onClose={() => setErr(undefined)} />
)}
Expand Down
106 changes: 99 additions & 7 deletions sandbox/src/General.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,104 @@
// SPDX-License-Identifier: Apache-2.0

export function General() {
import { FormEvent, useState } from 'react';
import * as bitbox from '@bitboxswiss/bitbox-api';

import { ErrorNotification } from './ErrorNotification';

type Props = { bb02: bitbox.PairedBitBox };

function RootFingerprint({ bb02 }: Props) {
const [rootFingerprint, setRootFingerprint] = useState('');
const [running, setRunning] = useState(false);
const [err, setErr] = useState<bitbox.Error>();

const submitForm = async (e: FormEvent) => {
e.preventDefault();
setRunning(true);
setRootFingerprint('');
setErr(undefined);
try {
setRootFingerprint(await bb02.rootFingerprint());
} catch (e2) {
setErr(bitbox.ensureError(e2));
} finally {
setRunning(false);
}
};

return (
<>
<h4>Root Fingerprint</h4>
<form className="verticalForm" onSubmit={submitForm}>
<button type="submit" disabled={running}>Show</button>
{rootFingerprint !== '' && (
<div className="resultContainer">
<label>
Result: <b><code>{rootFingerprint}</code></b>
</label>
</div>
)}
{err !== undefined && (
<ErrorNotification message={err.message} code={err.code} onClose={() => setErr(undefined)} />
)}
</form>
</>
);
}

function DeviceInfo({ bb02 }: Props) {
const [deviceInfo, setDeviceInfo] = useState<bitbox.DeviceInfo>();
const [running, setRunning] = useState(false);
const [err, setErr] = useState<bitbox.Error>();

const submitForm = async (e: FormEvent) => {
e.preventDefault();
setRunning(true);
setDeviceInfo(undefined);
setErr(undefined);
try {
setDeviceInfo(await bb02.deviceInfo());
} catch (e2) {
setErr(bitbox.ensureError(e2));
} finally {
setRunning(false);
}
};

const parsedDeviceInfo = deviceInfo ? JSON.stringify(deviceInfo, undefined, 2) : '';

return (
<>
<h4>Device Info</h4>
<form className="verticalForm" onSubmit={submitForm}>
<button type="submit" disabled={running}>Show</button>
{deviceInfo !== undefined && (
<div className="resultContainer">
<label>Result</label>
<textarea
rows={parsedDeviceInfo.split('\n').length}
readOnly
defaultValue={parsedDeviceInfo}
/>
</div>
)}
{err !== undefined && (
<ErrorNotification message={err.message} code={err.code} onClose={() => setErr(undefined)} />
)}
</form>
</>
);
}

export function General({ bb02 }: Props) {
return (
<div className="action">
<p>
Additional general device actions will appear here once they are wired into the paired
API.
</p>
</div>
<>
<div className="action">
<RootFingerprint bb02={bb02} />
</div>
<div className="action">
<DeviceInfo bb02={bb02} />
</div>
</>
);
}
2 changes: 1 addition & 1 deletion sandbox/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"paths": {
"bitbox-api-ts": ["../src/index.ts"]
"@bitboxswiss/bitbox-api": ["../src/index.ts"]
}
},
"include": ["src", "vite.config.ts"]
Expand Down
2 changes: 1 addition & 1 deletion sandbox/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default defineConfig({
alias: {
// Point the sandbox at the library source so edits in ../src/ hot-reload
// without needing a rebuild.
'bitbox-api-ts': path.resolve(__dirname, '../src/index.ts'),
'@bitboxswiss/bitbox-api': path.resolve(__dirname, '../src/index.ts'),
},
},
build: {
Expand Down
Loading
Loading