├── .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 | 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 | --------------------------------------------------------------------------------