@effectstream/wallets 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +30 -0
- package/src/IProvider.ts +62 -0
- package/src/algorand/algorand.ts +168 -0
- package/src/algorand/wrapper.ts +22 -0
- package/src/avail/avail.ts +81 -0
- package/src/avail/wrapper.ts +44 -0
- package/src/cardano/cardano.ts +194 -0
- package/src/cardano/wrapper.ts +34 -0
- package/src/effectstream.ts +382 -0
- package/src/evm/ethers.ts +104 -0
- package/src/evm/injected.ts +429 -0
- package/src/evm/types.ts +2 -0
- package/src/evm/wrapper-ethers.ts +49 -0
- package/src/evm/wrapper-injected.ts +103 -0
- package/src/midnight/midnight.ts +196 -0
- package/src/midnight/wrapper.ts +36 -0
- package/src/mina/mina.ts +175 -0
- package/src/mina/wrapper.ts +22 -0
- package/src/mod.ts +10 -0
- package/src/polkadot/polkadot.ts +166 -0
- package/src/polkadot/wrapper.ts +30 -0
- package/src/types.ts +61 -0
- package/src/utils.test.ts +64 -0
- package/src/utils.ts +133 -0
- package/src/wallet-modes.ts +124 -0
- package/src/wallets.ts +62 -0
- package/src/windows.ts +10 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { AddressType, type MidnightAddress } from "@effectstream/utils";
|
|
2
|
+
import type {
|
|
3
|
+
ConnectedAPI,
|
|
4
|
+
InitialAPI,
|
|
5
|
+
Signature,
|
|
6
|
+
} from "@midnight-ntwrk/dapp-connector-api";
|
|
7
|
+
import semver from "semver";
|
|
8
|
+
import {
|
|
9
|
+
type IInjectedConnector,
|
|
10
|
+
type ActiveConnection,
|
|
11
|
+
type IProvider,
|
|
12
|
+
type AddressAndType,
|
|
13
|
+
type UserSignature,
|
|
14
|
+
type IConnector,
|
|
15
|
+
type ConnectionOption,
|
|
16
|
+
optionToActive,
|
|
17
|
+
} from "../IProvider.ts";
|
|
18
|
+
import { getWindow } from "../windows.ts";
|
|
19
|
+
|
|
20
|
+
export type MidnightApi = ConnectedAPI;
|
|
21
|
+
|
|
22
|
+
const COMPATIBLE_CONNECTOR_API_VERSION = ">=1.0.0";
|
|
23
|
+
const DEFAULT_NETWORK_ID = "undeployed";
|
|
24
|
+
|
|
25
|
+
// TODO Implement this class
|
|
26
|
+
export class MidnightConnector
|
|
27
|
+
implements IConnector<MidnightApi>, IInjectedConnector<MidnightApi> {
|
|
28
|
+
private provider: MidnightProvider | undefined;
|
|
29
|
+
private static INSTANCE: undefined | MidnightConnector = undefined;
|
|
30
|
+
private static networkId = DEFAULT_NETWORK_ID;
|
|
31
|
+
|
|
32
|
+
static getWalletOptions(): ConnectionOption<MidnightApi>[] {
|
|
33
|
+
const midnightApi: Record<string, InitialAPI> = (getWindow() as any)
|
|
34
|
+
?.midnight ?? {};
|
|
35
|
+
console.log("MidnightConnector.getWalletOptions: window.midnight content:", midnightApi);
|
|
36
|
+
const options = Object.entries(midnightApi).reduce((options, [key, api]) => {
|
|
37
|
+
console.log(`MidnightConnector.getWalletOptions: Checking wallet "${key}"`, api);
|
|
38
|
+
if (!isInitialApi(api)) {
|
|
39
|
+
console.log(`MidnightConnector.getWalletOptions: Wallet "${key}" is NOT a valid InitialAPI (missing connect or apiVersion)`);
|
|
40
|
+
return options;
|
|
41
|
+
}
|
|
42
|
+
if (!isCompatibleApiVersion(api.apiVersion)) {
|
|
43
|
+
console.log(`MidnightConnector.getWalletOptions: Wallet "${key}" has incompatible version ${api.apiVersion} (required: ${COMPATIBLE_CONNECTOR_API_VERSION})`);
|
|
44
|
+
return options;
|
|
45
|
+
}
|
|
46
|
+
const name = api.rdns?.trim() || key;
|
|
47
|
+
const displayName = api.name?.trim() || name;
|
|
48
|
+
console.log(`MidnightConnector.getWalletOptions: Wallet "${key}" is COMPATIBLE. Name: ${name}, DisplayName: ${displayName}`);
|
|
49
|
+
options.push({
|
|
50
|
+
metadata: {
|
|
51
|
+
name,
|
|
52
|
+
displayName,
|
|
53
|
+
icon: api.icon,
|
|
54
|
+
},
|
|
55
|
+
api: () => connectWithNetwork(api, MidnightConnector.networkId),
|
|
56
|
+
});
|
|
57
|
+
return options;
|
|
58
|
+
}, [] as ConnectionOption<MidnightApi>[]);
|
|
59
|
+
console.log(`MidnightConnector.getWalletOptions: Found ${options.length} compatible wallets`);
|
|
60
|
+
return options;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static instance(): MidnightConnector {
|
|
64
|
+
if (MidnightConnector.INSTANCE == null) {
|
|
65
|
+
const newInstance = new MidnightConnector();
|
|
66
|
+
MidnightConnector.INSTANCE = newInstance;
|
|
67
|
+
}
|
|
68
|
+
return MidnightConnector.INSTANCE;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setNetworkId = (networkId: string): void => {
|
|
72
|
+
MidnightConnector.networkId = networkId || DEFAULT_NETWORK_ID;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
getOrThrowProvider = (): MidnightProvider => {
|
|
76
|
+
if (this.provider == null) {
|
|
77
|
+
throw new Error(`MidnightConnector provider isn't initialized yet`);
|
|
78
|
+
}
|
|
79
|
+
return this.provider;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
getProvider = (): undefined | MidnightProvider => {
|
|
83
|
+
return this.provider;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
isConnected(): boolean {
|
|
87
|
+
return this.provider != null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
connectSimple = async (): Promise<MidnightProvider> => {
|
|
91
|
+
if (this.provider != null) {
|
|
92
|
+
return this.provider;
|
|
93
|
+
}
|
|
94
|
+
const options = MidnightConnector.getWalletOptions();
|
|
95
|
+
if (options.length === 0) {
|
|
96
|
+
throw new Error(`No Midnight wallet found`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return await this.connectExternal(await optionToActive(options[0]));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
connectNamed = async (name: string): Promise<MidnightProvider> => {
|
|
103
|
+
if (this.provider?.getConnection().metadata?.name === name) {
|
|
104
|
+
return this.provider;
|
|
105
|
+
}
|
|
106
|
+
const provider = MidnightConnector.getWalletOptions().find(
|
|
107
|
+
entry => entry.metadata.name === name
|
|
108
|
+
);
|
|
109
|
+
if (provider == null) {
|
|
110
|
+
throw new Error(`Midnight wallet ${name} not found`);
|
|
111
|
+
}
|
|
112
|
+
return await this.connectExternal(await optionToActive(provider));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
connectExternal = async (conn: ActiveConnection<MidnightApi>): Promise<MidnightProvider> => {
|
|
116
|
+
if (this.provider?.getConnection().metadata?.name === conn.metadata.name) {
|
|
117
|
+
return this.provider;
|
|
118
|
+
}
|
|
119
|
+
this.provider = await MidnightProvider.init(conn);
|
|
120
|
+
return this.provider;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// TODO Implement this class
|
|
126
|
+
export class MidnightProvider implements IProvider<MidnightApi> {
|
|
127
|
+
constructor(
|
|
128
|
+
private readonly conn: ActiveConnection<MidnightApi>,
|
|
129
|
+
readonly address: MidnightAddress
|
|
130
|
+
) {}
|
|
131
|
+
|
|
132
|
+
static init = async (
|
|
133
|
+
conn: ActiveConnection<MidnightApi>
|
|
134
|
+
): Promise<MidnightProvider> => {
|
|
135
|
+
const hexAddress = await MidnightProvider.fetchAddress(conn);
|
|
136
|
+
// const prefix = pickBech32Prefix(hexAddress);
|
|
137
|
+
// const words = bech32.toWords(hexStringToUint8Array(hexAddress));
|
|
138
|
+
// const userAddress = bech32.encode(prefix, words, 200);
|
|
139
|
+
|
|
140
|
+
return new MidnightProvider(conn, hexAddress as MidnightAddress);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
getConnection(): ActiveConnection<MidnightApi> {
|
|
144
|
+
return this.conn;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
signMessage = async (
|
|
148
|
+
message: string,
|
|
149
|
+
keyType: "unshielded" = "unshielded"
|
|
150
|
+
): Promise<UserSignature> => {
|
|
151
|
+
const signature = (await this.conn.api.signData(message, {
|
|
152
|
+
encoding: "text",
|
|
153
|
+
keyType,
|
|
154
|
+
})) as Signature;
|
|
155
|
+
return signature.signature;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
getAddress(): AddressAndType {
|
|
159
|
+
return {
|
|
160
|
+
type: AddressType.MIDNIGHT,
|
|
161
|
+
address: this.address,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static fetchAddress = async (conn: ActiveConnection<MidnightApi>): Promise<string> => {
|
|
166
|
+
const addresses = await conn.api.getShieldedAddresses();
|
|
167
|
+
if (!addresses?.shieldedAddress) {
|
|
168
|
+
throw new Error("Midnight wallet did not return a shielded address");
|
|
169
|
+
}
|
|
170
|
+
return addresses.shieldedAddress;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const isInitialApi = (api: InitialAPI | undefined): api is InitialAPI => {
|
|
175
|
+
return (
|
|
176
|
+
!!api &&
|
|
177
|
+
typeof api.connect === "function" &&
|
|
178
|
+
typeof api.apiVersion === "string"
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const isCompatibleApiVersion = (apiVersion: string): boolean => {
|
|
183
|
+
return semver.satisfies(
|
|
184
|
+
apiVersion,
|
|
185
|
+
COMPATIBLE_CONNECTOR_API_VERSION,
|
|
186
|
+
{ includePrerelease: true }
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const connectWithNetwork = async (
|
|
191
|
+
api: InitialAPI,
|
|
192
|
+
networkId: string
|
|
193
|
+
): Promise<ConnectedAPI> => {
|
|
194
|
+
const connect = api.connect.bind(api);
|
|
195
|
+
return await connect(networkId);
|
|
196
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Result } from "@effectstream/utils";
|
|
2
|
+
import type { IProvider } from "../IProvider.ts";
|
|
3
|
+
import type { WalletMode, ApiForMode } from "../utils.ts";
|
|
4
|
+
import { type LoginInfoMap, connectInjected } from "../wallet-modes.ts";
|
|
5
|
+
import { MidnightConnector } from "./midnight.ts";
|
|
6
|
+
|
|
7
|
+
export async function checkMidnightWalletStatus(): Promise<Result<string>> {
|
|
8
|
+
const provider = MidnightConnector.instance().getProvider();
|
|
9
|
+
if (provider == null) {
|
|
10
|
+
return {
|
|
11
|
+
success: false,
|
|
12
|
+
errorMessage: "No Midnight wallet found",
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return { success: true, result: "OK" };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function midnightLoginWrapper(
|
|
20
|
+
loginInfo: LoginInfoMap[WalletMode.Midnight]
|
|
21
|
+
): Promise<Result<IProvider<ApiForMode<WalletMode.Midnight>>>> {
|
|
22
|
+
const connector = MidnightConnector.instance();
|
|
23
|
+
connector.setNetworkId(loginInfo.networkId ?? "undeployed");
|
|
24
|
+
const loginResult = await connectInjected(
|
|
25
|
+
"midnightLoginWrapper",
|
|
26
|
+
loginInfo,
|
|
27
|
+
connector
|
|
28
|
+
);
|
|
29
|
+
if (loginResult.success === false) {
|
|
30
|
+
return loginResult;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
success: true,
|
|
34
|
+
result: loginResult.result,
|
|
35
|
+
};
|
|
36
|
+
}
|
package/src/mina/mina.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import * as AuroMinaApi from "@aurowallet/mina-provider";
|
|
2
|
+
import { AddressType } from "@effectstream/utils";
|
|
3
|
+
import {
|
|
4
|
+
type IConnector,
|
|
5
|
+
type IInjectedConnector,
|
|
6
|
+
type ConnectionOption,
|
|
7
|
+
type IProvider,
|
|
8
|
+
optionToActive,
|
|
9
|
+
type ActiveConnection,
|
|
10
|
+
type AddressAndType,
|
|
11
|
+
type UserSignature,
|
|
12
|
+
} from "../IProvider.ts";
|
|
13
|
+
import { getWindow } from "../windows.ts";
|
|
14
|
+
|
|
15
|
+
export type MinaApi = AuroMinaApi.default;
|
|
16
|
+
|
|
17
|
+
type MinaAddress = any;
|
|
18
|
+
|
|
19
|
+
// JSR Error: modifying global types is not allowed
|
|
20
|
+
// declare global {
|
|
21
|
+
// interface Window {
|
|
22
|
+
// mina?: Record<string, { name?: string; enable?: () => Promise<MinaApi> }>;
|
|
23
|
+
// }
|
|
24
|
+
// }
|
|
25
|
+
|
|
26
|
+
export class MinaConnector
|
|
27
|
+
implements IConnector<MinaApi>, IInjectedConnector<MinaApi>
|
|
28
|
+
{
|
|
29
|
+
private provider: MinaProvider | undefined;
|
|
30
|
+
private static INSTANCE: undefined | MinaConnector = undefined;
|
|
31
|
+
|
|
32
|
+
static getWalletOptions(): ConnectionOption<MinaApi>[] {
|
|
33
|
+
const minaApi: Record<string, { name?: string; enable?: () => Promise<MinaApi> }> = (getWindow() as any)?.mina;
|
|
34
|
+
if (minaApi == null || !minaApi.isAuro) return [];
|
|
35
|
+
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
metadata: {
|
|
39
|
+
name: "auro",
|
|
40
|
+
displayName: "Auro Wallet",
|
|
41
|
+
},
|
|
42
|
+
api: async (): Promise<MinaApi> => {
|
|
43
|
+
return minaApi as unknown as MinaApi;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static instance(): MinaConnector {
|
|
50
|
+
if (MinaConnector.INSTANCE == null) {
|
|
51
|
+
const newInstance = new MinaConnector();
|
|
52
|
+
MinaConnector.INSTANCE = newInstance;
|
|
53
|
+
}
|
|
54
|
+
return MinaConnector.INSTANCE;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async connectSimple(): Promise<IProvider<any>> {
|
|
58
|
+
if (this.provider != null) {
|
|
59
|
+
return this.provider;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const options = MinaConnector.getWalletOptions();
|
|
63
|
+
|
|
64
|
+
if (options.length === 0) {
|
|
65
|
+
throw new Error(`No Mina wallet found`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return await this.connectExternal(await optionToActive(options[0]));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async connectNamed(name: string): Promise<IProvider<any>> {
|
|
72
|
+
if (this.provider?.getConnection().metadata?.name === name) {
|
|
73
|
+
return this.provider;
|
|
74
|
+
}
|
|
75
|
+
const provider = MinaConnector.getWalletOptions().find(
|
|
76
|
+
(entry) => entry.metadata.name === name
|
|
77
|
+
);
|
|
78
|
+
if (provider == null) {
|
|
79
|
+
throw new Error(`MinaProvider: unsupported connection type ${name}`);
|
|
80
|
+
}
|
|
81
|
+
return await this.connectExternal(await optionToActive(provider));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
connectExternal = async (
|
|
85
|
+
conn: ActiveConnection<MinaApi>
|
|
86
|
+
): Promise<MinaProvider> => {
|
|
87
|
+
if (this.provider?.getConnection().metadata?.name === conn.metadata.name) {
|
|
88
|
+
return this.provider;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.provider = await MinaProvider.init(conn);
|
|
92
|
+
|
|
93
|
+
conn.api.on("accountsChanged", (accounts) => {
|
|
94
|
+
if (!accounts || !accounts[0] || accounts[0] !== this.provider?.address) {
|
|
95
|
+
this.provider = undefined;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return this.provider;
|
|
100
|
+
};
|
|
101
|
+
getProvider = (): undefined | MinaProvider => {
|
|
102
|
+
return this.provider;
|
|
103
|
+
};
|
|
104
|
+
getOrThrowProvider = (): MinaProvider => {
|
|
105
|
+
if (this.provider == null) {
|
|
106
|
+
throw new Error(`MinaConnector provider isn't initialized yet`);
|
|
107
|
+
}
|
|
108
|
+
return this.provider;
|
|
109
|
+
};
|
|
110
|
+
isConnected = (): boolean => {
|
|
111
|
+
return this.provider != null;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class MinaProvider implements IProvider<MinaApi> {
|
|
116
|
+
constructor(
|
|
117
|
+
private readonly conn: ActiveConnection<MinaApi>,
|
|
118
|
+
readonly address: MinaAddress
|
|
119
|
+
) {}
|
|
120
|
+
|
|
121
|
+
static init = async (
|
|
122
|
+
conn: ActiveConnection<MinaApi>
|
|
123
|
+
): Promise<MinaProvider> => {
|
|
124
|
+
const address = await MinaProvider.fetchAddress(conn);
|
|
125
|
+
|
|
126
|
+
return new MinaProvider(conn, address);
|
|
127
|
+
};
|
|
128
|
+
getConnection = (): ActiveConnection<MinaApi> => {
|
|
129
|
+
return this.conn;
|
|
130
|
+
};
|
|
131
|
+
getAddress = (): AddressAndType => {
|
|
132
|
+
return {
|
|
133
|
+
type: AddressType.MINA,
|
|
134
|
+
address: this.address,
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
signMessage = async (message: string): Promise<UserSignature> => {
|
|
138
|
+
// There is no way of choosing the signing account here. At most we could
|
|
139
|
+
// monitor the changed events and erroring out if it changed.
|
|
140
|
+
const signed = await this.conn.api.signMessage({ message: message });
|
|
141
|
+
|
|
142
|
+
if ("signature" in signed) {
|
|
143
|
+
const { signature } = signed;
|
|
144
|
+
|
|
145
|
+
const networkKind = await this.conn.api.requestNetwork();
|
|
146
|
+
|
|
147
|
+
// only these two options are used
|
|
148
|
+
// https://github.com/aurowallet/auro-wallet-browser-extension/blob/624de322dd99baaa09617bbad4a4a838f0a88edc/src/background/lib/index.js#L17
|
|
149
|
+
// this is part of the hashed message
|
|
150
|
+
const network =
|
|
151
|
+
networkKind.networkID === "mina:mainnet" ? "mainnet" : "testnet";
|
|
152
|
+
|
|
153
|
+
return `${signature.field};${signature.scalar};${network}`;
|
|
154
|
+
} else {
|
|
155
|
+
throw new Error("Failed to sign message");
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
static fetchAddress = async (
|
|
160
|
+
conn: ActiveConnection<MinaApi>
|
|
161
|
+
): Promise<string> => {
|
|
162
|
+
try {
|
|
163
|
+
const addresses = await conn.api.requestAccounts();
|
|
164
|
+
if ("length" in addresses && addresses.length > 0) {
|
|
165
|
+
return addresses[0];
|
|
166
|
+
} else {
|
|
167
|
+
throw new Error("requestAccounts request failed");
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.log("[pickMinaAddress] error calling requestAccounts:", err);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new Error("[pickMinaAddress] no used or unused addresses");
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Result } from "@effectstream/utils";
|
|
2
|
+
import type { IProvider } from "../IProvider.ts";
|
|
3
|
+
import type { WalletMode, ApiForMode } from "../utils.ts";
|
|
4
|
+
import { type LoginInfoMap, connectInjected } from "../wallet-modes.ts";
|
|
5
|
+
import { MinaConnector } from "./mina.ts";
|
|
6
|
+
|
|
7
|
+
export async function minaLoginWrapper(
|
|
8
|
+
loginInfo: LoginInfoMap[WalletMode.Mina]
|
|
9
|
+
): Promise<Result<IProvider<ApiForMode<WalletMode.Mina>>>> {
|
|
10
|
+
const loginResult = await connectInjected(
|
|
11
|
+
"minaLoginWrapper",
|
|
12
|
+
loginInfo,
|
|
13
|
+
MinaConnector.instance()
|
|
14
|
+
);
|
|
15
|
+
if (loginResult.success === false) {
|
|
16
|
+
return loginResult;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
success: true,
|
|
20
|
+
result: loginResult.result,
|
|
21
|
+
};
|
|
22
|
+
}
|
package/src/mod.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { IProvider, AddressAndType, UserSignature, WalletOption } from "./IProvider.ts";
|
|
2
|
+
export type { Wallet } from "./types.ts";
|
|
3
|
+
export type { AddressType, WalletAddress } from "@effectstream/utils";
|
|
4
|
+
export { accountPayload_, accountMessages } from "@effectstream/concise";
|
|
5
|
+
export { WalletMode, WalletNameMap } from "./utils.ts";
|
|
6
|
+
// Main entry point
|
|
7
|
+
export { walletLogin } from "./wallets.ts";
|
|
8
|
+
export { allInjectedWallets } from "./utils.ts";
|
|
9
|
+
export { getAddressType } from "./wallet-modes.ts";
|
|
10
|
+
export * from "./effectstream.ts";
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InjectedExtension,
|
|
3
|
+
InjectedWindowProvider,
|
|
4
|
+
} from "@polkadot/extension-inject/types";
|
|
5
|
+
import { AddressType } from "@effectstream/utils";
|
|
6
|
+
import {
|
|
7
|
+
type IConnector,
|
|
8
|
+
type IInjectedConnector,
|
|
9
|
+
type ConnectionOption,
|
|
10
|
+
type ActiveConnection,
|
|
11
|
+
optionToActive,
|
|
12
|
+
type IProvider,
|
|
13
|
+
type AddressAndType,
|
|
14
|
+
type UserSignature,
|
|
15
|
+
} from "../IProvider.ts";
|
|
16
|
+
import { utf8ToHex } from "web3-utils";
|
|
17
|
+
import { getWindow } from "../windows.ts";
|
|
18
|
+
|
|
19
|
+
// @polkadot/extension-dapp is browser-only (its bundle touches `window` at module
|
|
20
|
+
// load). Import it lazily inside the methods that need it so this file can be
|
|
21
|
+
// parsed in Node/Bun test contexts — matches the non-browser design note in
|
|
22
|
+
// ../windows.ts.
|
|
23
|
+
export type PolkadotAddress = string;
|
|
24
|
+
export type PolkadotApi = InjectedExtension;
|
|
25
|
+
|
|
26
|
+
// declare global {
|
|
27
|
+
// interface Window {
|
|
28
|
+
// injectedWeb3?: Record<string, InjectedWindowProvider>;
|
|
29
|
+
// }
|
|
30
|
+
// }
|
|
31
|
+
|
|
32
|
+
export class PolkadotConnector
|
|
33
|
+
implements IConnector<PolkadotApi>, IInjectedConnector<PolkadotApi>
|
|
34
|
+
{
|
|
35
|
+
private provider: PolkadotProvider | undefined;
|
|
36
|
+
private static INSTANCE: undefined | PolkadotConnector = undefined;
|
|
37
|
+
|
|
38
|
+
static async getWalletOptions(): Promise<ConnectionOption<PolkadotApi>[]> {
|
|
39
|
+
const injectedWeb3: Record<string, InjectedWindowProvider> = (getWindow() as any)?.injectedWeb3;
|
|
40
|
+
if (injectedWeb3 == null) return [];
|
|
41
|
+
return Object.keys(injectedWeb3).map((wallet) => ({
|
|
42
|
+
metadata: {
|
|
43
|
+
name: wallet,
|
|
44
|
+
// polkadot provides no way to get a human-friendly name or icon for wallets
|
|
45
|
+
displayName: wallet,
|
|
46
|
+
},
|
|
47
|
+
api: async (): Promise<PolkadotApi> => {
|
|
48
|
+
const { web3Enable, web3FromSource } = await import("@polkadot/extension-dapp");
|
|
49
|
+
await web3Enable(/*gameName*/ "paima");
|
|
50
|
+
const injector = await web3FromSource(wallet);
|
|
51
|
+
return injector;
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static instance(): PolkadotConnector {
|
|
57
|
+
if (PolkadotConnector.INSTANCE == null) {
|
|
58
|
+
const newInstance = new PolkadotConnector();
|
|
59
|
+
PolkadotConnector.INSTANCE = newInstance;
|
|
60
|
+
}
|
|
61
|
+
return PolkadotConnector.INSTANCE;
|
|
62
|
+
}
|
|
63
|
+
connectSimple = async (): Promise<PolkadotProvider> => {
|
|
64
|
+
if (this.provider != null) {
|
|
65
|
+
return this.provider;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { web3Enable, web3Accounts, web3FromAddress } = await import("@polkadot/extension-dapp");
|
|
69
|
+
const extensions = await web3Enable(/*gameName*/ "paima");
|
|
70
|
+
if (extensions.length === 0) {
|
|
71
|
+
throw new Error(`[polkadot] no extension detected`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// we get all accounts instead of picking a specific extension
|
|
75
|
+
// because some extensions could have no accounts in them
|
|
76
|
+
const allAccounts = await web3Accounts();
|
|
77
|
+
for (const account of allAccounts) {
|
|
78
|
+
const injector = await web3FromAddress(account.address);
|
|
79
|
+
if (injector.signer.signRaw == null) continue;
|
|
80
|
+
return await this.connectExternal({
|
|
81
|
+
metadata: {
|
|
82
|
+
name: account.meta.source,
|
|
83
|
+
// polkadot provides no way to get a human-friendly name or icon for wallets
|
|
84
|
+
displayName: account.meta.source,
|
|
85
|
+
},
|
|
86
|
+
api: injector,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
throw new Error("[polkadot] No account detected that supports signing! Found extensions: " + extensions.map(e => e.name).join(", "));
|
|
90
|
+
};
|
|
91
|
+
connectExternal = async (
|
|
92
|
+
conn: ActiveConnection<PolkadotApi>
|
|
93
|
+
): Promise<PolkadotProvider> => {
|
|
94
|
+
if (this.provider?.getConnection().metadata?.name === conn.metadata.name) {
|
|
95
|
+
return this.provider;
|
|
96
|
+
}
|
|
97
|
+
this.provider = await PolkadotProvider.init(conn);
|
|
98
|
+
return this.provider;
|
|
99
|
+
};
|
|
100
|
+
connectNamed = async (name: string): Promise<PolkadotProvider> => {
|
|
101
|
+
if (this.provider?.getConnection().metadata?.name === name) {
|
|
102
|
+
return this.provider;
|
|
103
|
+
}
|
|
104
|
+
const provider = (await PolkadotConnector.getWalletOptions()).find(
|
|
105
|
+
(entry) => entry.metadata.name === name
|
|
106
|
+
);
|
|
107
|
+
if (provider == null) {
|
|
108
|
+
throw new Error(`Polkadot wallet ${name} not found`);
|
|
109
|
+
}
|
|
110
|
+
return await this.connectExternal(await optionToActive(provider));
|
|
111
|
+
};
|
|
112
|
+
getProvider = (): undefined | PolkadotProvider => {
|
|
113
|
+
return this.provider;
|
|
114
|
+
};
|
|
115
|
+
getOrThrowProvider = (): PolkadotProvider => {
|
|
116
|
+
if (this.provider == null) {
|
|
117
|
+
throw new Error(`PolkadotConnector not initialized yet`);
|
|
118
|
+
}
|
|
119
|
+
return this.provider;
|
|
120
|
+
};
|
|
121
|
+
isConnected = (): boolean => {
|
|
122
|
+
return this.provider != null;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class PolkadotProvider implements IProvider<PolkadotApi> {
|
|
127
|
+
constructor(
|
|
128
|
+
private readonly conn: ActiveConnection<PolkadotApi>,
|
|
129
|
+
readonly address: PolkadotAddress
|
|
130
|
+
) {}
|
|
131
|
+
static init = async (
|
|
132
|
+
conn: ActiveConnection<PolkadotApi>
|
|
133
|
+
): Promise<PolkadotProvider> => {
|
|
134
|
+
// { address, name, type: "sr25519" }
|
|
135
|
+
const addresses = await conn.api.accounts.get(true);
|
|
136
|
+
const account = addresses.filter(a => a.type === "sr25519")[0]?.address;
|
|
137
|
+
if (account == null) {
|
|
138
|
+
throw new Error("Unknown error while receiving Polkadot accounts");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return new PolkadotProvider(conn, account);
|
|
142
|
+
};
|
|
143
|
+
getConnection = (): ActiveConnection<PolkadotApi> => {
|
|
144
|
+
return this.conn;
|
|
145
|
+
};
|
|
146
|
+
getAddress = (): AddressAndType => {
|
|
147
|
+
return {
|
|
148
|
+
type: AddressType.POLKADOT,
|
|
149
|
+
address: this.address,
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
signMessage = async (message: string): Promise<UserSignature> => {
|
|
153
|
+
if (this.conn.api.signer.signRaw == null) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`[polkadot] extension ${this.conn.metadata.name} does not support signRaw`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const hexMessage = utf8ToHex(message);
|
|
159
|
+
const { signature } = await this.conn.api.signer.signRaw({
|
|
160
|
+
address: this.getAddress().address,
|
|
161
|
+
data: hexMessage,
|
|
162
|
+
type: "bytes",
|
|
163
|
+
});
|
|
164
|
+
return signature;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// import { buildEndpointErrorFxn, PaimaMiddlewareErrorCode } from '../errors.js';
|
|
2
|
+
// import { getGameName } from '../state.js';
|
|
3
|
+
// import type { LoginInfoMap } from '../types.js';
|
|
4
|
+
// import { PolkadotConnector } from '@effectstream/providers';
|
|
5
|
+
// import type { ApiForMode, IProvider, WalletMode } from '@effectstream/providers';
|
|
6
|
+
// import { connectInjected } from './wallet-modes.js';
|
|
7
|
+
// import type { Result } from '@effectstream/utils';
|
|
8
|
+
|
|
9
|
+
import type { Result } from "@effectstream/utils";
|
|
10
|
+
import type { IProvider } from "../IProvider.ts";
|
|
11
|
+
import type { WalletMode, ApiForMode } from "../utils.ts";
|
|
12
|
+
import { type LoginInfoMap, connectInjected } from "../wallet-modes.ts";
|
|
13
|
+
import { PolkadotConnector } from "./polkadot.ts";
|
|
14
|
+
|
|
15
|
+
export async function polkadotLoginWrapper(
|
|
16
|
+
loginInfo: LoginInfoMap[WalletMode.Polkadot]
|
|
17
|
+
): Promise<Result<IProvider<ApiForMode<WalletMode.Polkadot>>>> {
|
|
18
|
+
const loginResult = await connectInjected(
|
|
19
|
+
'polkadotLoginWrapper',
|
|
20
|
+
loginInfo,
|
|
21
|
+
PolkadotConnector.instance()
|
|
22
|
+
);
|
|
23
|
+
if (loginResult.success === false) {
|
|
24
|
+
return loginResult;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
result: loginResult.result,
|
|
29
|
+
};
|
|
30
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type Hash = string;
|
|
2
|
+
import type { WalletAddress } from "@effectstream/utils";
|
|
3
|
+
import type { IProvider, UserSignature, WalletOption } from "./IProvider.ts";
|
|
4
|
+
|
|
5
|
+
export type SignFunction = (message: string) => Promise<UserSignature>;
|
|
6
|
+
|
|
7
|
+
export type QueryValue = string | number | boolean;
|
|
8
|
+
export type QueryOptions = Record<string, QueryValue>;
|
|
9
|
+
|
|
10
|
+
export interface Wallet {
|
|
11
|
+
provider: IProvider<unknown>;
|
|
12
|
+
walletAddress: WalletAddress;
|
|
13
|
+
metadata: WalletOption;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface BatcherPostResponseSuccessful {
|
|
17
|
+
success: true;
|
|
18
|
+
hash: Hash;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface BatcherPostResponseUnsuccessful {
|
|
22
|
+
success: false;
|
|
23
|
+
message: string;
|
|
24
|
+
code?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type BatcherPostResponse =
|
|
28
|
+
| BatcherPostResponseSuccessful
|
|
29
|
+
| BatcherPostResponseUnsuccessful;
|
|
30
|
+
|
|
31
|
+
interface BatcherTrackResponseCore {
|
|
32
|
+
success: true;
|
|
33
|
+
hash: Hash;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface BatcherTrackResponsePosted extends BatcherTrackResponseCore {
|
|
37
|
+
status: "posted";
|
|
38
|
+
block_height: number;
|
|
39
|
+
transaction_hash: Hash;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface BatcherTrackResponseRejected extends BatcherTrackResponseCore {
|
|
43
|
+
status: "rejected";
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface BatcherTrackResponseOther extends BatcherTrackResponseCore {
|
|
48
|
+
status: "accepted" | "validating";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type BatcherTrackResponse =
|
|
52
|
+
| BatcherTrackResponsePosted
|
|
53
|
+
| BatcherTrackResponseRejected
|
|
54
|
+
| BatcherTrackResponseOther;
|
|
55
|
+
|
|
56
|
+
export type BaseSuccessResponse = {
|
|
57
|
+
success: true;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type PostDataResponse = BaseSuccessResponse & { blockHeight: number };
|
|
61
|
+
export type PostDataResponseAsync = BaseSuccessResponse & { hash: string };
|