├── .github
└── workflows
│ ├── main.yml
│ ├── npm.yml
│ └── size.yml
├── .gitignore
├── LICENSE
├── README.md
├── example
├── .npmignore
├── index.html
├── index.tsx
├── package.json
├── tsconfig.json
└── yarn.lock
├── package-lock.json
├── package.json
├── src
├── index.ts
├── store
│ └── index.ts
├── test
│ └── balance.spec.ts
├── typescript
│ └── cip30.ts
├── utils.ts
└── wallets.ts
├── tsconfig.json
└── yarn.lock
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}
6 |
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | node: ['14.x', '16.x']
11 | os: [ubuntu-latest, windows-latest, macOS-latest]
12 |
13 | steps:
14 | - name: Checkout repo
15 | uses: actions/checkout@v2
16 |
17 | - name: Use Node ${{ matrix.node }}
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: ${{ matrix.node }}
21 |
22 | - name: Install deps and build (with cache)
23 | uses: bahmutov/npm-install@v1
24 |
25 | - name: Lint
26 | run: yarn lint
27 |
28 | - name: Test
29 | run: yarn test --ci --coverage --maxWorkers=2
30 |
31 | - name: Build
32 | run: yarn build
33 |
--------------------------------------------------------------------------------
/.github/workflows/npm.yml:
--------------------------------------------------------------------------------
1 | on: push
2 |
3 | jobs:
4 | publish:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v3
8 | - uses: actions/setup-node@v3
9 | with:
10 | node-version: 14
11 | - run: npm install
12 | - run: npm test
13 | - uses: JS-DevTools/npm-publish@v1
14 | with:
15 | token: ${{ secrets.NPM_TOKEN }}
16 | access: public
17 |
--------------------------------------------------------------------------------
/.github/workflows/size.yml:
--------------------------------------------------------------------------------
1 | name: size
2 | on: [pull_request]
3 | jobs:
4 | size:
5 | runs-on: ubuntu-latest
6 | env:
7 | CI_JOB_NUMBER: 1
8 | steps:
9 | - uses: actions/checkout@v1
10 | - uses: andresz1/size-limit-action@v1
11 | with:
12 | github_token: ${{ secrets.GITHUB_TOKEN }}
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 MartinSchere
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # use-cardano-wallet
2 |
3 | A simple react hook to connect your application to cardano wallets. This library does not take care of transaction building. For that, you should use something like [Lucid](https://github.com/Berry-Pool/lucid).
4 |
5 | > ℹ This library does not depend on `cardano-serialization-lib`, so you don't have to worry about big budle sizes!
6 |
7 | ## Installation
8 |
9 | ```
10 | npm i @asterium-dev/use-cardano-wallet
11 | ```
12 |
13 | ```
14 | yarn add @asterium-dev/use-cardano-wallet
15 | ```
16 |
17 | ## Basic usage
18 |
19 | ```ts
20 | const {
21 | connect,
22 | isConnecting,
23 | isConnected,
24 | lovelaceBalance,
25 | address,
26 | network,
27 | selectedWallet,
28 | connectedWallet,
29 | api,
30 | disconnect,
31 | isRefetchingBalance,
32 | refetchBalance,
33 | } = useCardanoWallet();
34 | ```
35 |
36 | ## Example
37 |
38 | Check out the example in [this](./example/) folder
39 |
40 | ---
41 |
42 | ## API
43 |
44 | ▸ **useCardanoWallet**(`options?`): ReturnVal
45 |
46 | ### Parameters
47 |
48 | | Name | Type | Default value |
49 | | :-------- | :---------------------- | :--------------- |
50 | | `options` | UseCardanoWalletOptions | `defaultOptions` |
51 |
52 | ### UseCardanoWalletOptions:
53 |
54 | | Name | Type | Description |
55 | | :----------------- | :------ | :-------------------------------------------------------------------------------------------------------------------- |
56 | | `autoConnect?` | boolean | Specify if the connector should automatically try to connect to previously connected wallets. Relies on localStorage. |
57 | | `localStorageKey?` | string | Specify a local storage key to store the connected wallet name |
58 |
59 | ### Returns object:
60 |
61 | | Name | Type | Description |
62 | | :-------------------- | :---------------- | :------------------------------------------------------------------------------- |
63 | | `address` | null \| string | bech32 representation of the wallet address |
64 | | `api` | null \| WalletApi | The [CIP30](https://cips.cardano.org/cips/cip30/) object of the connected wallet |
65 | | `connect` | Function | Primary function to connect the desired wallet |
66 | | `connectedWallet` | null \| string | The wallet that is currently connected. |
67 | | `disconnect` | Function | Disconnects the current wallet |
68 | | `isConnected` | boolean | True if the wallet is connected |
69 | | `isConnecting` | boolean | Loading indicator for the wallet connection |
70 | | `isRefetchingBalance` | boolean | Loading indicator for balance refetch |
71 | | `lovelaceBalance` | null \| number | Wallet balance, in lovelace (1 ADA = 1000000 lovelace) |
72 | | `network` | null \| NetworkId | 0 if testnet, 1 if mainnet |
73 | | `refetchBalance` | Function | Refresh the wallet's balance |
74 | | `selectedWallet` | null \| string | The wallet that was selected to connect. |
75 |
--------------------------------------------------------------------------------
/example/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | dist
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Playground
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/index.tsx:
--------------------------------------------------------------------------------
1 | import 'react-app-polyfill/ie11';
2 | import * as React from 'react';
3 | import * as ReactDOM from 'react-dom';
4 | import useCardanoWallet, { WalletName } from '../.';
5 |
6 | const App = () => {
7 | const {
8 | connect,
9 | isConnecting,
10 | isConnected,
11 | lovelaceBalance,
12 | address,
13 | network,
14 | selectedWallet,
15 | connectedWallet,
16 | api,
17 | disconnect,
18 | isRefetchingBalance,
19 | refetchBalance,
20 | } = useCardanoWallet();
21 | return (
22 |
28 | {Object.values(WalletName).map(walletName => (
29 |
38 | ))}
39 |
40 | - Address: {address}
41 | - Network: {network}
42 | -
43 | Balance:{' '}
44 | {typeof lovelaceBalance === 'number' &&
45 | `${lovelaceBalance / 1000000}ADA`}
46 |
47 | - Selected wallet: {selectedWallet}
48 | - Connected wallet: {connectedWallet}
49 |
50 |
51 | );
52 | };
53 |
54 | ReactDOM.render(, document.getElementById('root'));
55 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "parcel index.html",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "react-app-polyfill": "^1.0.0"
12 | },
13 | "alias": {
14 | "react": "../node_modules/react",
15 | "react-dom": "../node_modules/react-dom/profiling",
16 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^16.9.11",
20 | "@types/react-dom": "^16.8.4",
21 | "parcel": "^1.12.3",
22 | "typescript": "^3.4.5"
23 | },
24 | "resolutions": {
25 | "@babel/preset-env": "7.13.8"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": false,
4 | "target": "es5",
5 | "module": "commonjs",
6 | "jsx": "react",
7 | "moduleResolution": "node",
8 | "noImplicitAny": false,
9 | "noUnusedLocals": false,
10 | "noUnusedParameters": false,
11 | "removeComments": true,
12 | "strictNullChecks": true,
13 | "preserveConstEnums": true,
14 | "sourceMap": true,
15 | "lib": ["es2015", "es2016", "dom"],
16 | "types": ["node"]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.3.6",
3 | "license": "MIT",
4 | "main": "dist/index.js",
5 | "typings": "dist/index.d.ts",
6 | "files": [
7 | "dist",
8 | "src"
9 | ],
10 | "engines": {
11 | "node": ">=10"
12 | },
13 | "scripts": {
14 | "start": "tsdx watch",
15 | "build": "tsdx build",
16 | "test": "tsdx test --passWithNoTests",
17 | "lint": "tsdx lint",
18 | "prepare": "tsdx build",
19 | "size": "size-limit",
20 | "analyze": "size-limit --why"
21 | },
22 | "peerDependencies": {
23 | "react": ">=16"
24 | },
25 | "husky": {
26 | "hooks": {
27 | "pre-commit": "tsdx lint"
28 | }
29 | },
30 | "prettier": {
31 | "printWidth": 80,
32 | "semi": true,
33 | "singleQuote": true,
34 | "trailingComma": "es5"
35 | },
36 | "name": "use-cardano-wallet",
37 | "author": "MartinSchere",
38 | "module": "dist/use-cardano-wallet.esm.js",
39 | "size-limit": [
40 | {
41 | "path": "dist/use-cardano-wallet.cjs.production.min.js",
42 | "limit": "10 KB"
43 | },
44 | {
45 | "path": "dist/use-cardano-wallet.esm.js",
46 | "limit": "10 KB"
47 | }
48 | ],
49 | "devDependencies": {
50 | "@size-limit/preset-small-lib": "^8.0.1",
51 | "@types/react": "^18.0.17",
52 | "@types/react-dom": "^18.0.6",
53 | "husky": "^8.0.1",
54 | "react": "^18.2.0",
55 | "react-dom": "^18.2.0",
56 | "size-limit": "^8.0.1",
57 | "tsdx": "^0.14.1",
58 | "tslib": "^2.4.0",
59 | "typedoc-plugin-markdown": "^3.13.4",
60 | "typescript": "^4.7.4"
61 | },
62 | "dependencies": {
63 | "base32-encode": "^2.0.0",
64 | "bech32": "^2.0.0",
65 | "bech32-buffer": "^0.2.0",
66 | "cbor-x": "^1.5.9",
67 | "immer": "^9.0.15",
68 | "zustand": "^4.0.0"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import { useStore, WalletInfo as _, State } from './store';
4 | import { WalletName } from './typescript/cip30';
5 |
6 | export type UseCardanoWalletOptions = {
7 | /**
8 | * Specify if the connector should automatically try to connect
9 | * to previously connected wallets. Relies on localStorage.
10 | */
11 | autoConnect?: boolean;
12 |
13 | /**
14 | * Specify a local storage key to store the connected wallet name
15 | */
16 | localStorageKey?: string;
17 |
18 | /**
19 | * If specified, it will call `api.getUtxos()` using the specified
20 | * interval (in milliseconds). When `utxos` is referenced from the
21 | * hook, it will return the prefetched array.
22 | *
23 | * This is useful for saving on load times when building transactions.
24 | */
25 | // prefetchUtxosInterval?: number | null;
26 | };
27 |
28 | const defaultOptions: UseCardanoWalletOptions = {
29 | autoConnect: true,
30 | localStorageKey: 'cardano-wallet-name',
31 | };
32 |
33 | type ReturnVal = Omit & {
34 | connect: (walletName: WalletName) => Promise;
35 | };
36 |
37 | const useCardanoWallet = ({
38 | autoConnect = true,
39 | localStorageKey = 'cardano-wallet-name',
40 | }: UseCardanoWalletOptions = defaultOptions): ReturnVal => {
41 | const {
42 | connect: connectWithStore,
43 | getDetectedWallets,
44 | disconnect: storeDisconnect,
45 | ...rest
46 | } = useStore();
47 |
48 | useEffect(() => {
49 | getDetectedWallets();
50 | }, [getDetectedWallets]);
51 |
52 | useEffect(() => {
53 | // Check localStorage in case we are in SSR
54 | if (autoConnect && typeof localStorage !== 'undefined') {
55 | const connectedWalletName = localStorage.getItem(
56 | localStorageKey
57 | ) as WalletName | null;
58 |
59 | if (!connectedWalletName) {
60 | return;
61 | }
62 |
63 | // Setting timeout to let the wallet be injected and not get undefined
64 | setTimeout(() => {
65 | connectWithStore(connectedWalletName, localStorageKey);
66 | }, 10);
67 | }
68 | }, [autoConnect, localStorageKey, connectWithStore]);
69 |
70 | const connect = async (walletName: WalletName) => {
71 | await connectWithStore(walletName, localStorageKey);
72 | };
73 | const disconnect = () => {
74 | localStorage.removeItem(localStorageKey);
75 | storeDisconnect();
76 | };
77 |
78 | return { connect, disconnect, ...rest };
79 | };
80 |
81 | export * from './typescript/cip30';
82 |
83 | export default useCardanoWallet;
84 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { bech32 } from 'bech32';
2 | import produce from 'immer';
3 | import create from 'zustand';
4 |
5 | import { NetworkId, WalletApi, WalletName } from '../typescript/cip30';
6 | import { fromHex, parseBalance, toWalletInfo } from '../utils';
7 |
8 | export interface WalletInfo {
9 | name: WalletName;
10 | icon: string;
11 | displayName: string;
12 | }
13 |
14 | export type State = {
15 | isConnected: boolean;
16 | isConnecting: boolean;
17 | isRefetchingBalance: boolean;
18 |
19 | detectedWallets: WalletInfo[];
20 |
21 | address: null | string;
22 |
23 | rewardAddress: null | string;
24 |
25 | /**
26 | * The wallet that was selected to connect.
27 | */
28 | selectedWallet: null | WalletInfo;
29 |
30 | /**
31 | * The wallet that is currently connected.
32 | */
33 | connectedWallet: null | WalletInfo;
34 |
35 | lovelaceBalance: null | number;
36 | api: null | WalletApi;
37 | network: null | NetworkId;
38 |
39 | connect: (walletName: WalletName, localStorageKey: string) => Promise;
40 | getDetectedWallets: () => void;
41 | disconnect: () => void;
42 | refetchBalance: () => Promise;
43 | };
44 |
45 | const defaults = {
46 | isConnecting: false,
47 | isConnected: false,
48 | detectedWallets: [],
49 | address: null,
50 | lovelaceBalance: null,
51 | api: null,
52 | selectedWallet: null,
53 | connectedWallet: null,
54 | network: null,
55 | isRefetchingBalance: false,
56 | rewardAddress: null,
57 | };
58 |
59 | export const useStore = create()((set, get) => ({
60 | ...defaults,
61 | disconnect: () => {
62 | set((prev: State) => {
63 | return {
64 | ...defaults,
65 | // Keep detected wallets
66 | detectedWallets: prev.detectedWallets,
67 | };
68 | });
69 | },
70 |
71 | refetchBalance: async () => {
72 | const api = get().api;
73 |
74 | if (api === null) return;
75 |
76 | set(
77 | produce((draft: State) => {
78 | draft.isRefetchingBalance = true;
79 | })
80 | );
81 |
82 | const balance = await api.getBalance();
83 | const lovelace = parseBalance(balance);
84 |
85 | set(
86 | produce((draft: State) => {
87 | draft.isRefetchingBalance = false;
88 | draft.lovelaceBalance = lovelace;
89 | })
90 | );
91 | },
92 |
93 | getDetectedWallets: async () => {
94 | if (typeof window === 'undefined' || !(window as any).cardano) {
95 | return;
96 | }
97 |
98 | const ns = (window as any).cardano;
99 |
100 | set(
101 | produce((draft: State) => {
102 | draft.detectedWallets = Object.keys(ns)
103 | .filter(ns => Object.values(WalletName).includes(ns as WalletName))
104 | .map(n => toWalletInfo(n as WalletName));
105 | })
106 | );
107 | },
108 |
109 | connect: async (walletName: WalletName, localStorageKey: string) => {
110 | set(
111 | produce((draft: State) => {
112 | draft.isConnecting = true;
113 | draft.selectedWallet = toWalletInfo(walletName);
114 | })
115 | );
116 |
117 | try {
118 | // Exit early if the Cardano dApp-Wallet Web Bridge (CIP 30) has not been injected
119 | // This can happen in a SSR scenario for example
120 | if (typeof window === 'undefined' || !(window as any).cardano) {
121 | throw Error('window.cardano is undefined');
122 | }
123 |
124 | const api: WalletApi = await (window as any).cardano[walletName].enable();
125 | let rawAddress = await api.getChangeAddress();
126 | if (!rawAddress) {
127 | [rawAddress] = await api.getUsedAddresses();
128 | }
129 | if (!rawAddress) {
130 | [rawAddress] = await api.getUnusedAddresses();
131 | }
132 |
133 | let [rewardAddress] = await api.getRewardAddresses();
134 |
135 | if (rewardAddress) {
136 | const buf = fromHex(rewardAddress);
137 | const words = bech32.toWords(buf);
138 | rewardAddress = bech32.encode(
139 | buf[0] === NetworkId.MAINNET ? 'stake' : 'stake_test',
140 | words,
141 | 130
142 | );
143 | }
144 |
145 | const address = fromHex(rawAddress);
146 | const balance = await api.getBalance();
147 |
148 | const decodedBalance = parseBalance(balance);
149 | const words = bech32.toWords(address);
150 |
151 | const bechAddr = bech32.encode(
152 | address[0] === NetworkId.MAINNET ? 'addr' : 'addr_test',
153 | words,
154 | 130
155 | );
156 |
157 | localStorage.setItem(localStorageKey, walletName);
158 |
159 | set(
160 | produce((draft: State) => {
161 | draft.isConnecting = false;
162 | draft.isConnected = true;
163 | draft.api = api;
164 | draft.lovelaceBalance = decodedBalance;
165 | draft.rewardAddress = rewardAddress;
166 | draft.address = bechAddr;
167 | draft.network = address[0] as NetworkId;
168 | draft.connectedWallet = toWalletInfo(walletName);
169 | })
170 | );
171 | } catch (e) {
172 | console.error(e);
173 | set(
174 | produce((draft: State) => {
175 | draft.isConnecting = false;
176 | draft.selectedWallet = null;
177 | })
178 | );
179 | }
180 | },
181 | }));
182 |
--------------------------------------------------------------------------------
/src/test/balance.spec.ts:
--------------------------------------------------------------------------------
1 | import { parseBalance } from '../utils';
2 |
3 | describe('Batcher tests', () => {
4 | it('Tx building cases', () => {
5 | const result = parseBalance('1a94b130b6');
6 | expect(result).toBe('2494640310');
7 |
8 | const result2 = parseBalance('1b00000001dc87463b');
9 | expect(result2).toBe('7994820155');
10 |
11 | const result3 = parseBalance(
12 | '821a0510379ea1581cceb8a6f66d8abf778e111ffb982626e6e795d8ef15e7261ea98738d2a14d4d61727342697264733732383001'
13 | );
14 |
15 | expect(result3).toBe('84948894');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/typescript/cip30.ts:
--------------------------------------------------------------------------------
1 | export enum NetworkId {
2 | TESTNET = 0,
3 | MAINNET = 1,
4 | }
5 | /**
6 | * A hex-encoded string of the corresponding bytes.
7 | */
8 | export type Bytes = string;
9 |
10 | /**
11 | * A hex-encoded string representing CBOR either inside
12 | * of the Shelley Multi-asset binary spec or, if not present there,
13 | * from the CIP-0008 signing spec.
14 | */
15 | export type Cbor = string;
16 |
17 | /**
18 | * Used to specify optional pagination for some API calls.
19 | Limits results to {limit} each page, and uses a 0-indexing {page}
20 | to refer to which of those pages of {limit} items each.
21 | */
22 | export type Paginate = { page: number; limit: number };
23 |
24 | /**
25 | * DataSignature type as described in CIP-0030.
26 | */
27 |
28 | type CoseSign1CborHex = string;
29 | type CoseKeyCborHex = string;
30 |
31 | export interface Cip30DataSignature {
32 | key: CoseKeyCborHex;
33 | signature: CoseSign1CborHex;
34 | }
35 |
36 | /**
37 | * Returns the network id of the currently connected account.
38 | * 0 is testnet and 1 is mainnet but other networks can possibly be returned by wallets.
39 | * Those other network ID values are not governed by this document.
40 | *
41 | * This result will stay the same unless the connected account has changed.
42 | *
43 | * @throws ApiError
44 | */
45 | export type GetNetworkId = () => Promise;
46 | /**
47 | * If `amount` is `undefined`, this shall return a list of all UTxOs (unspent transaction outputs)
48 | * controlled by the wallet.
49 | *
50 | * If `amount` is not `undefined`, this request shall be limited to just the UTxOs that are required
51 | * to reach the combined ADA/multi-asset value target specified in `amount`,
52 | * and if this cannot be attained, `undefined` shall be returned.
53 | *
54 | * The results can be further paginated by `paginate` if it is not `undefined`.
55 | *
56 | * @throws ApiError
57 | * @throws PaginateError
58 | */
59 | export type GetUtxos = (
60 | amount?: Cbor,
61 | paginate?: Paginate
62 | ) => Promise;
63 |
64 | /**
65 | * @returns a list of one or more UTxOs (unspent transaction outputs) controlled by the wallet
66 | * that are required to reach AT LEAST the combined ADA value target specified in amount
67 | * AND the best suitable to be used as collateral inputs
68 | * for transactions with plutus script inputs (pure ADA-only UTxOs).
69 | * @throws ApiError
70 | */
71 | export type GetCollateral = (params?: {
72 | amount?: Cbor;
73 | }) => Promise;
74 |
75 | /**
76 | * Returns the total balance available of the wallet.
77 | *
78 | * This is the same as summing the results of `api.getUtxos()`, but it is both useful to dApps
79 | * and likely already maintained by the implementing wallet in a more efficient manner
80 | * so it has been included in the API as well.
81 | *
82 | * @throws ApiError
83 | */
84 | export type GetBalance = () => Promise;
85 |
86 | /**
87 | * Returns a list of all used (included in some on-chain transaction) addresses controlled by the wallet.
88 | *
89 | * The results can be further paginated by `paginate` if it is not `undefined`.
90 | *
91 | * @throws ApiError
92 | * @throws PaginateError
93 | */
94 | export type GetUsedAddresses = (paginate?: Paginate) => Promise;
95 |
96 | /**
97 | * Returns a list of unused addresses controlled by the wallet.
98 | *
99 | * @throws ApiError
100 | */
101 | export type GetUnusedAddresses = () => Promise;
102 |
103 | /**
104 | * Returns an address owned by the wallet that should be used as a change address to return
105 | * leftover assets during transaction creation back to the connected wallet.
106 | *
107 | * This can be used as a generic receive address as well.
108 | *
109 | * @throws ApiError
110 | */
111 | export type GetChangeAddress = () => Promise;
112 |
113 | /**
114 | * Returns the reward addresses owned by the wallet. This can return multiple addresses e.g. CIP-0018.
115 | *
116 | * @throws ApiError
117 | */
118 | export type GetRewardAddresses = () => Promise;
119 |
120 | /**
121 | * Requests that a user sign the unsigned portions of the supplied transaction.
122 | *
123 | * The wallet should ask the user for permission, and if given,
124 | * try to sign the supplied body and return a signed transaction.
125 | *
126 | * If `partialSign` is `true`, the wallet only tries to sign what it can.
127 | *
128 | * If `partialSign` is `false` and the wallet could not sign the entire transaction,
129 | * `TxSignError` shall be returned with the `ProofGeneration` code.
130 | *
131 | * Likewise if the user declined in either case it shall return the `UserDeclined` code.
132 | *
133 | * Only the portions of the witness set that were signed as a result of this call are
134 | * returned to encourage dApps to verify the contents returned by this endpoint while building the final transaction.
135 | *
136 | * @throws ApiError
137 | * @throws TxSignError
138 | */
139 | export type SignTx = (tx: Cbor, partialSign?: boolean) => Promise;
140 |
141 | /**
142 | * This endpoint utilizes the CIP-0008 signing spec for standardization/safety reasons.
143 | *
144 | * It allows the dApp to request the user to sign data conforming to said spec.
145 | *
146 | * The user's consent should be requested and the details of `sig_structure` shown to them in an informative way.
147 | *
148 | * Please refer to the CIP-0008 spec for details on how to construct the sig structure.
149 | *
150 | * @throws ApiError
151 | * @throws DataSignError
152 | */
153 | export type SignData = (
154 | addr: string,
155 | payload: Bytes
156 | ) => Promise;
157 |
158 | /**
159 | * As wallets should already have this ability, we allow dApps to request that a transaction be sent through it.
160 | *
161 | * If the wallet accepts the transaction and tries to send it, it shall return the transaction id for the dApp to track.
162 | *
163 | * The wallet is free to return the `TxSendError` with code `Refused` if they do not wish to send it,
164 | * or `Failure` if there was an error in sending it (e.g. preliminary checks failed on signatures).
165 | *
166 | * @throws ApiError
167 | * @throws TxSendError
168 | */
169 | export type SubmitTx = (tx: Cbor) => Promise;
170 |
171 | export enum WalletName {
172 | NAMI = 'nami',
173 | ETERNL = 'eternl',
174 | FLINT = 'flint',
175 | BEGIN = 'begin',
176 | GERO = 'gero',
177 | TYPHON = 'typhoncip30',
178 | LACE = 'lace',
179 | VESPR = 'vespr',
180 | }
181 |
182 | export interface WalletApi {
183 | getNetworkId: GetNetworkId;
184 |
185 | getUtxos: GetUtxos;
186 |
187 | getBalance: GetBalance;
188 |
189 | getCollateral: GetCollateral;
190 |
191 | getUsedAddresses: GetUsedAddresses;
192 |
193 | getUnusedAddresses: GetUnusedAddresses;
194 |
195 | getChangeAddress: GetChangeAddress;
196 |
197 | getRewardAddresses: GetRewardAddresses;
198 |
199 | signTx: SignTx;
200 |
201 | signData: SignData;
202 |
203 | submitTx: SubmitTx;
204 | }
205 |
206 | export type WalletMethod = keyof WalletApi;
207 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { Encoder } from 'cbor-x';
2 | import { WalletName } from './typescript/cip30';
3 | import { WalletInfo } from './store';
4 | import { walletPrettyNames } from './wallets';
5 |
6 | export const fromHex = (hexString: string) => Buffer.from(hexString, 'hex');
7 |
8 | export const parseBalance = (balance: string) => {
9 | const encoder = new Encoder({ mapsAsObjects: false });
10 | const decodedBalance = encoder.decode(Buffer.from(balance, 'hex'));
11 |
12 | if (typeof decodedBalance === 'bigint') {
13 | return decodedBalance.toString();
14 | }
15 | if (typeof decodedBalance === 'number') {
16 | return decodedBalance.toString();
17 | }
18 | return (decodedBalance[0] ?? 0).toString();
19 | };
20 |
21 | export const toWalletInfo = (walletName: WalletName): WalletInfo => {
22 | const ns = (window as any).cardano;
23 |
24 | return {
25 | name: walletName,
26 | displayName: walletPrettyNames[walletName],
27 | icon: ns[walletName].icon,
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/src/wallets.ts:
--------------------------------------------------------------------------------
1 | import { WalletName } from './typescript/cip30';
2 |
3 | export const walletPrettyNames: Record = {
4 | begin: 'Begin',
5 | eternl: 'Eternl',
6 | flint: 'Flint',
7 | gero: 'Gero',
8 | lace: 'Lace',
9 | nami: 'Nami',
10 | typhoncip30: 'Typhon',
11 | vespr: 'Vespr',
12 | };
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
3 | "include": ["src", "types"],
4 | "compilerOptions": {
5 | "module": "esnext",
6 | "lib": ["dom", "esnext"],
7 | "importHelpers": true,
8 | // output .d.ts declaration files for consumers
9 | "declaration": true,
10 | // output .js.map sourcemap files for consumers
11 | "sourceMap": true,
12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index
13 | "rootDir": "./src",
14 | // stricter type-checking for stronger correctness. Recommended by TS
15 | "strict": true,
16 | // linter checks for common issues
17 | "noImplicitReturns": true,
18 | "noFallthroughCasesInSwitch": true,
19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | // use Node's module resolution algorithm, instead of the legacy TS one
23 | "moduleResolution": "node",
24 | // transpile JSX to React.createElement
25 | "jsx": "react",
26 | // interop between ESM and CJS modules. Recommended by TS
27 | "esModuleInterop": true,
28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
29 | "skipLibCheck": true,
30 | // error out if import and file system have a casing mismatch. Recommended by TS
31 | "forceConsistentCasingInFileNames": true,
32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
33 | "noEmit": true
34 | }
35 | }
36 |
--------------------------------------------------------------------------------