-
Notifications
You must be signed in to change notification settings - Fork 55
/
Wallet.ts
146 lines (125 loc) · 4.2 KB
/
Wallet.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { APIErrorCode, ApiError } from '../errors';
import { Logger, dummyLogger } from 'ts-log';
import { WalletApi } from './types';
import { WindowMaybeWithCardano } from '../injectWindow';
/**
* CIP30 Specification version
*/
export type SpecificationVersion = string;
/**
* Unique identifier, used to inject into the cardano namespace
*/
export type WalletName = string;
export type WalletProperties = { name: WalletName; version: SpecificationVersion };
/**
* Resolve true to authorise access to the WalletAPI, or resolve false to deny.
*
* Errors: `ApiError`
*/
export type RequestAccess = () => Promise<boolean>;
export type WalletOptions = {
logger?: Logger;
persistAllowList?: boolean;
storage?: Storage;
};
const defaultOptions = {
logger: dummyLogger,
persistAllowList: false,
storage: window.localStorage
};
export class Wallet {
readonly version: SpecificationVersion;
readonly name: WalletName;
private allowList: string[];
private logger: Logger;
private readonly options: Required<WalletOptions>;
constructor(
properties: WalletProperties,
private api: WalletApi,
private requestAccess: RequestAccess,
options?: WalletOptions
) {
this.options = { ...defaultOptions, ...options };
this.name = properties.name;
this.version = properties.version;
this.allowList = this.options.persistAllowList ? this.getAllowList() : [];
this.logger = this.options.logger;
}
public getPublicApi(window: WindowMaybeWithCardano) {
return {
enable: this.enable.bind(this, window),
isEnabled: this.isEnabled.bind(this, window),
name: this.name,
version: this.version
};
}
private getAllowList(): string[] {
// JSON.parse(null) seems to be legit
return JSON.parse(this.options.storage.getItem(this.name)!) || [];
}
private allowApplication(appName: string) {
this.allowList.push(appName);
if (this.options.persistAllowList) {
const currentList = this.getAllowList();
// Todo: Encrypt
this.options.storage?.setItem(this.name, JSON.stringify([...currentList, appName]));
this.logger.debug(
{
allowList: this.getAllowList(),
module: 'Wallet',
walletName: this.name
},
'Allow list persisted'
);
}
}
/**
* Returns true if the dApp is already connected to the user's wallet, or if requesting access
* would return true without user confirmation (e.g. the dApp is whitelisted), and false otherwise.
*
* If this function returns true, then any subsequent calls to wallet.enable()
* during the current session should succeed and return the API object.
*
* Errors: `ApiError`
*/
public async isEnabled(window: WindowMaybeWithCardano): Promise<Boolean> {
const appName = window.location.hostname;
return this.allowList.includes(appName);
}
/**
* This is the entrypoint to start communication with the user's wallet.
*
* The wallet should request the user's permission to connect the web page to the user's wallet,
* and if permission has been granted, the full API will be returned to the dApp to use.
*
* The wallet can choose to maintain a whitelist to not necessarily ask the user's permission
* every time access is requested, but this behavior is up to the wallet and should be transparent
* to web pages using this API.
*
* If a wallet is already connected this function should not request access a second time,
* and instead just return the API object.
*
* Errors: `ApiError`
*/
public async enable(window: WindowMaybeWithCardano): Promise<WalletApi> {
const appName = window.location.hostname;
if (this.options.persistAllowList && this.allowList.includes(appName)) {
this.logger.debug(
{
module: 'Wallet',
walletName: this.name
},
`${appName} has previously been allowed`
);
return this.api;
}
// gain authorization from wallet owner
const isAuthed = await this.requestAccess();
if (!isAuthed) {
throw new ApiError(APIErrorCode.Refused, 'wallet not authorized.');
}
this.allowApplication(appName);
return this.api;
}
}
export type WalletPublic = ReturnType<Wallet['getPublicApi']>;