@getpara/graz-connector 0.1.0-alpha.5 → 2.0.0-dev.10
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/__tests__/connector.test.tsx +491 -0
- package/__tests__/connectorModal.test.tsx +148 -0
- package/dist/chunk-M66XENHI.js +25 -0
- package/dist/connector.d.ts +49 -0
- package/dist/connector.js +274 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +4 -9
- package/package.json +31 -13
- package/src/connector.ts +269 -0
- package/src/index.ts +2 -3
- package/tsconfig.json +3 -3
- package/vitest.config.js +22 -0
- package/dist/ParaGrazProvider.d.ts +0 -33
- package/dist/ParaGrazProvider.js +0 -136
- package/dist/chunk-IV3L3JVM.js +0 -46
- package/dist/connectorModal.d.ts +0 -2
- package/dist/connectorModal.js +0 -36
- package/src/ParaGrazProvider.ts +0 -165
- package/src/connectorModal.tsx +0 -32
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Wallet, SignDoc as GrazSignDoc } from 'graz';
|
|
2
|
+
import { ParaWeb, Wallet as ParaWallet } from '@getpara/web-sdk';
|
|
3
|
+
import { DirectSignResponse, OfflineDirectSigner } from '@cosmjs/proto-signing';
|
|
4
|
+
import { AminoSignResponse, OfflineAminoSigner, StdSignature, StdSignDoc } from '@cosmjs/amino';
|
|
5
|
+
import { ChainInfo, KeplrSignOptions } from '@keplr-wallet/types';
|
|
6
|
+
export type ParaGrazConnectorEvents = {
|
|
7
|
+
onEnabled?: (chainIds: string[], connector: ParaGrazConnector) => void;
|
|
8
|
+
};
|
|
9
|
+
export interface ParaGrazConfig {
|
|
10
|
+
paraWeb: ParaWeb;
|
|
11
|
+
events?: ParaGrazConnectorEvents;
|
|
12
|
+
noModal?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function toArray<T>(v: T | T[]): T[];
|
|
15
|
+
export declare class ParaGrazConnector implements Omit<Wallet, 'experimentalSuggestChain'> {
|
|
16
|
+
protected readonly config: ParaGrazConfig;
|
|
17
|
+
protected readonly chains: ChainInfo[] | null;
|
|
18
|
+
protected paraWebClient: ParaWeb;
|
|
19
|
+
protected enabledChainIds: Set<string>;
|
|
20
|
+
protected readonly events?: ParaGrazConnectorEvents;
|
|
21
|
+
protected noModal?: boolean;
|
|
22
|
+
constructor(config: ParaGrazConfig, chains?: ChainInfo[] | null);
|
|
23
|
+
protected ensureChainEnabled(chainId: string): Promise<void>;
|
|
24
|
+
protected waitForLogin(timeoutMs?: number): Promise<void>;
|
|
25
|
+
protected waitForAccounts(timeoutMs?: number): Promise<ParaWallet[]>;
|
|
26
|
+
protected hasCosmosWallet(): Promise<boolean>;
|
|
27
|
+
enable(chainIdsInput: string | string[]): Promise<void>;
|
|
28
|
+
disconnect(): Promise<void>;
|
|
29
|
+
getFirstWallet(): Promise<ParaWallet>;
|
|
30
|
+
getBech32Prefix(chainId: string): string;
|
|
31
|
+
getParaWebClient(): ParaWeb;
|
|
32
|
+
getConfig(): ParaGrazConfig;
|
|
33
|
+
protected buildHybridSigner(chainId: string): OfflineAminoSigner & OfflineDirectSigner;
|
|
34
|
+
getKey(chainId: string): Promise<{
|
|
35
|
+
name: string;
|
|
36
|
+
algo: import("@cosmjs/amino").Algo;
|
|
37
|
+
pubKey: Uint8Array;
|
|
38
|
+
address: Uint8Array;
|
|
39
|
+
bech32Address: string;
|
|
40
|
+
isKeystone: boolean;
|
|
41
|
+
isNanoLedger: boolean;
|
|
42
|
+
}>;
|
|
43
|
+
getOfflineSignerOnlyAmino(chainId: string): OfflineAminoSigner;
|
|
44
|
+
getOfflineSigner(chainId: string): OfflineAminoSigner & OfflineDirectSigner;
|
|
45
|
+
getOfflineSignerAuto(chainId: string): Promise<OfflineAminoSigner | OfflineDirectSigner>;
|
|
46
|
+
signAmino(chainId: string, signer: string, signDoc: StdSignDoc, _signOptions?: KeplrSignOptions): Promise<AminoSignResponse>;
|
|
47
|
+
signDirect(chainId: string, signer: string, signDoc: GrazSignDoc, _signOptions?: KeplrSignOptions): Promise<DirectSignResponse>;
|
|
48
|
+
signArbitrary(chainId: string, signer: string, data: string | Uint8Array): Promise<StdSignature>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
__async
|
|
4
|
+
} from "./chunk-M66XENHI.js";
|
|
5
|
+
import { fromBech32 } from "@cosmjs/encoding";
|
|
6
|
+
import { ParaAminoSigner, ParaProtoSigner } from "@getpara/cosmjs-v0-integration";
|
|
7
|
+
function toArray(v) {
|
|
8
|
+
return Array.isArray(v) ? v : [v];
|
|
9
|
+
}
|
|
10
|
+
class ParaOfflineSigner {
|
|
11
|
+
constructor(chainId, connector) {
|
|
12
|
+
this.chainId = chainId;
|
|
13
|
+
this.connector = connector;
|
|
14
|
+
}
|
|
15
|
+
get para() {
|
|
16
|
+
return this.connector.getParaWebClient();
|
|
17
|
+
}
|
|
18
|
+
get prefix() {
|
|
19
|
+
return this.connector.getBech32Prefix(this.chainId);
|
|
20
|
+
}
|
|
21
|
+
wallet() {
|
|
22
|
+
return __async(this, null, function* () {
|
|
23
|
+
return this.connector.getFirstWallet();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
getAccounts() {
|
|
27
|
+
return __async(this, null, function* () {
|
|
28
|
+
const key = yield this.connector.getKey(this.chainId);
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
address: key.bech32Address,
|
|
32
|
+
algo: key.algo,
|
|
33
|
+
pubkey: key.pubKey
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
signDirect(signerAddress, signDoc) {
|
|
39
|
+
return __async(this, null, function* () {
|
|
40
|
+
if (this.chainId !== signDoc.chainId) {
|
|
41
|
+
throw new Error(`Para connector: signDirect chainId mismatch (expected ${this.chainId}, got ${signDoc.chainId})`);
|
|
42
|
+
}
|
|
43
|
+
const accounts = yield this.getAccounts();
|
|
44
|
+
if (accounts.every((a) => a.address !== signerAddress)) {
|
|
45
|
+
throw new Error(`Para connector: signerAddress ${signerAddress} not found in wallet`);
|
|
46
|
+
}
|
|
47
|
+
const signer = new ParaProtoSigner(this.para, this.prefix, (yield this.wallet()).id);
|
|
48
|
+
const result = yield signer.signDirect(signerAddress, signDoc);
|
|
49
|
+
return {
|
|
50
|
+
signed: {
|
|
51
|
+
bodyBytes: result.signed.bodyBytes,
|
|
52
|
+
authInfoBytes: result.signed.authInfoBytes,
|
|
53
|
+
chainId: result.signed.chainId,
|
|
54
|
+
accountNumber: result.signed.accountNumber
|
|
55
|
+
},
|
|
56
|
+
signature: result.signature
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
class ParaGrazConnector {
|
|
62
|
+
constructor(config, chains = null) {
|
|
63
|
+
this.config = config;
|
|
64
|
+
this.chains = chains;
|
|
65
|
+
this.enabledChainIds = /* @__PURE__ */ new Set();
|
|
66
|
+
if (!(config == null ? void 0 : config.paraWeb)) {
|
|
67
|
+
throw new Error("Para connector: missing paraWeb instance in config");
|
|
68
|
+
}
|
|
69
|
+
this.events = config.events;
|
|
70
|
+
this.paraWebClient = config.paraWeb;
|
|
71
|
+
this.noModal = config.noModal;
|
|
72
|
+
}
|
|
73
|
+
ensureChainEnabled(chainId) {
|
|
74
|
+
return __async(this, null, function* () {
|
|
75
|
+
if (!this.enabledChainIds.has(chainId)) {
|
|
76
|
+
throw new Error(`Para connector: chain ${chainId} was not enabled via wallet.enable()`);
|
|
77
|
+
}
|
|
78
|
+
if (!(yield this.paraWebClient.isFullyLoggedIn())) {
|
|
79
|
+
throw new Error("Para connector: wallet is not connected \u2013 call enable() first");
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
waitForLogin(timeoutMs = 6e4) {
|
|
84
|
+
return __async(this, null, function* () {
|
|
85
|
+
const deadline = Date.now() + timeoutMs;
|
|
86
|
+
let delay = 500;
|
|
87
|
+
const MAX_DELAY = 5e3;
|
|
88
|
+
while (true) {
|
|
89
|
+
if (yield this.paraWebClient.isFullyLoggedIn()) return;
|
|
90
|
+
if (Date.now() >= deadline) throw new Error("Para connector: login timeout");
|
|
91
|
+
yield new Promise((r) => setTimeout(r, delay));
|
|
92
|
+
delay = Math.min(delay * 1.5, MAX_DELAY);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
waitForAccounts(timeoutMs = 5e3) {
|
|
97
|
+
return __async(this, null, function* () {
|
|
98
|
+
const deadline = Date.now() + timeoutMs;
|
|
99
|
+
let delay = 250;
|
|
100
|
+
const MAX_DELAY = 1e3;
|
|
101
|
+
while (true) {
|
|
102
|
+
const wallets = this.paraWebClient.getWalletsByType("COSMOS");
|
|
103
|
+
if (wallets.length) return wallets;
|
|
104
|
+
if (Date.now() >= deadline) throw new Error("Para connector: no COSMOS wallets found after login");
|
|
105
|
+
yield new Promise((r) => setTimeout(r, delay));
|
|
106
|
+
delay = Math.min(delay * 1.5, MAX_DELAY);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
hasCosmosWallet() {
|
|
111
|
+
return __async(this, null, function* () {
|
|
112
|
+
return (yield this.paraWebClient.isFullyLoggedIn()) && this.paraWebClient.getWalletsByType("COSMOS").length > 0;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
enable(chainIdsInput) {
|
|
116
|
+
return __async(this, null, function* () {
|
|
117
|
+
var _a, _b, _c, _d;
|
|
118
|
+
const chainIds = toArray(chainIdsInput);
|
|
119
|
+
const previousEnabled = new Set(this.enabledChainIds);
|
|
120
|
+
try {
|
|
121
|
+
chainIds.forEach((id) => this.enabledChainIds.add(id));
|
|
122
|
+
if (yield this.hasCosmosWallet()) {
|
|
123
|
+
(_b = (_a = this.events) == null ? void 0 : _a.onEnabled) == null ? void 0 : _b.call(_a, chainIds, this);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!this.noModal) {
|
|
127
|
+
throw new Error("Modal rendering not supported in core library. Use @getpara/graz-integration for modal support.");
|
|
128
|
+
}
|
|
129
|
+
yield this.waitForLogin();
|
|
130
|
+
yield this.waitForAccounts();
|
|
131
|
+
(_d = (_c = this.events) == null ? void 0 : _c.onEnabled) == null ? void 0 : _d.call(_c, chainIds, this);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
this.enabledChainIds = previousEnabled;
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
disconnect() {
|
|
139
|
+
return __async(this, null, function* () {
|
|
140
|
+
yield this.paraWebClient.logout();
|
|
141
|
+
this.enabledChainIds.clear();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
getFirstWallet() {
|
|
145
|
+
return __async(this, null, function* () {
|
|
146
|
+
const [wallet] = yield this.waitForAccounts();
|
|
147
|
+
return wallet;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
getBech32Prefix(chainId) {
|
|
151
|
+
var _a, _b, _c;
|
|
152
|
+
return ((_c = (_b = (_a = this.chains) == null ? void 0 : _a.find((c) => c.chainId === chainId)) == null ? void 0 : _b.bech32Config) == null ? void 0 : _c.bech32PrefixAccAddr) || "cosmos";
|
|
153
|
+
}
|
|
154
|
+
getParaWebClient() {
|
|
155
|
+
return this.paraWebClient;
|
|
156
|
+
}
|
|
157
|
+
getConfig() {
|
|
158
|
+
return this.config;
|
|
159
|
+
}
|
|
160
|
+
buildHybridSigner(chainId) {
|
|
161
|
+
const aminoSigner = this.getOfflineSignerOnlyAmino(chainId);
|
|
162
|
+
const directSigner = new ParaOfflineSigner(chainId, this);
|
|
163
|
+
return {
|
|
164
|
+
getAccounts: () => directSigner.getAccounts(),
|
|
165
|
+
signAmino: (signer, signDoc) => aminoSigner.signAmino(signer, signDoc),
|
|
166
|
+
signDirect: (signer, signDoc) => directSigner.signDirect(signer, signDoc)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
getKey(chainId) {
|
|
170
|
+
return __async(this, null, function* () {
|
|
171
|
+
yield this.ensureChainEnabled(chainId);
|
|
172
|
+
const wallet = yield this.getFirstWallet();
|
|
173
|
+
const signer = new ParaProtoSigner(this.paraWebClient, this.getBech32Prefix(chainId), wallet.id);
|
|
174
|
+
const [account] = yield signer.getAccounts();
|
|
175
|
+
if (!account) throw new Error(`Para connector: wallet ${wallet.id} has no Cosmos accounts`);
|
|
176
|
+
return {
|
|
177
|
+
name: "Para Wallet",
|
|
178
|
+
algo: account.algo,
|
|
179
|
+
pubKey: account.pubkey,
|
|
180
|
+
address: fromBech32(account.address).data,
|
|
181
|
+
bech32Address: account.address,
|
|
182
|
+
isKeystone: false,
|
|
183
|
+
isNanoLedger: false
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
getOfflineSignerOnlyAmino(chainId) {
|
|
188
|
+
void this.ensureChainEnabled(chainId);
|
|
189
|
+
const wallet = this.paraWebClient.getWalletsByType("COSMOS")[0];
|
|
190
|
+
if (!wallet) {
|
|
191
|
+
throw new Error(`Para connector: no wallets found when requesting Amino signer for ${chainId}`);
|
|
192
|
+
}
|
|
193
|
+
return new ParaAminoSigner(this.paraWebClient, this.getBech32Prefix(chainId), wallet.id);
|
|
194
|
+
}
|
|
195
|
+
getOfflineSigner(chainId) {
|
|
196
|
+
void this.ensureChainEnabled(chainId);
|
|
197
|
+
return this.buildHybridSigner(chainId);
|
|
198
|
+
}
|
|
199
|
+
getOfflineSignerAuto(chainId) {
|
|
200
|
+
return __async(this, null, function* () {
|
|
201
|
+
void this.ensureChainEnabled(chainId);
|
|
202
|
+
return this.buildHybridSigner(chainId);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
signAmino(chainId, signer, signDoc, _signOptions) {
|
|
206
|
+
return __async(this, null, function* () {
|
|
207
|
+
yield this.ensureChainEnabled(chainId);
|
|
208
|
+
try {
|
|
209
|
+
const wallet = yield this.getFirstWallet();
|
|
210
|
+
const signerImpl = new ParaAminoSigner(this.paraWebClient, this.getBech32Prefix(chainId), wallet.id);
|
|
211
|
+
return yield signerImpl.signAmino(signer, signDoc);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
throw new Error(`Para connector: signAmino failed \u2013 ${err.message}`);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
signDirect(chainId, signer, signDoc, _signOptions) {
|
|
218
|
+
return __async(this, null, function* () {
|
|
219
|
+
var _a, _b;
|
|
220
|
+
yield this.ensureChainEnabled(chainId);
|
|
221
|
+
try {
|
|
222
|
+
const wallet = yield this.getFirstWallet();
|
|
223
|
+
const signerImpl = new ParaProtoSigner(this.paraWebClient, this.getBech32Prefix(chainId), wallet.id);
|
|
224
|
+
const convertedSignDoc = {
|
|
225
|
+
bodyBytes: (_a = signDoc.bodyBytes) != null ? _a : new Uint8Array(),
|
|
226
|
+
authInfoBytes: (_b = signDoc.authInfoBytes) != null ? _b : new Uint8Array(),
|
|
227
|
+
chainId: signDoc.chainId,
|
|
228
|
+
accountNumber: typeof signDoc.accountNumber === "bigint" ? signDoc.accountNumber : BigInt(signDoc.accountNumber)
|
|
229
|
+
};
|
|
230
|
+
const result = yield signerImpl.signDirect(signer, convertedSignDoc);
|
|
231
|
+
return {
|
|
232
|
+
signed: {
|
|
233
|
+
bodyBytes: result.signed.bodyBytes,
|
|
234
|
+
authInfoBytes: result.signed.authInfoBytes,
|
|
235
|
+
chainId: result.signed.chainId,
|
|
236
|
+
accountNumber: result.signed.accountNumber
|
|
237
|
+
},
|
|
238
|
+
signature: result.signature
|
|
239
|
+
};
|
|
240
|
+
} catch (err) {
|
|
241
|
+
throw new Error(`Para connector: signDirect failed \u2013 ${err.message}`);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
signArbitrary(chainId, signer, data) {
|
|
246
|
+
return __async(this, null, function* () {
|
|
247
|
+
yield this.ensureChainEnabled(chainId);
|
|
248
|
+
const encodedData = typeof data === "string" ? Buffer.from(data, "utf-8").toString("base64") : Buffer.from(data).toString("base64");
|
|
249
|
+
const signDoc = {
|
|
250
|
+
chain_id: "",
|
|
251
|
+
account_number: "0",
|
|
252
|
+
sequence: "0",
|
|
253
|
+
fee: { gas: "0", amount: [] },
|
|
254
|
+
msgs: [
|
|
255
|
+
{
|
|
256
|
+
type: "sign/MsgSignData",
|
|
257
|
+
value: { signer, data: encodedData }
|
|
258
|
+
}
|
|
259
|
+
],
|
|
260
|
+
memo: ""
|
|
261
|
+
};
|
|
262
|
+
try {
|
|
263
|
+
const response = yield this.signAmino(chainId, signer, signDoc);
|
|
264
|
+
return response.signature;
|
|
265
|
+
} catch (err) {
|
|
266
|
+
throw new Error(`Para connector: signArbitrary failed \u2013 ${err.message}`);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
export {
|
|
272
|
+
ParaGrazConnector,
|
|
273
|
+
toArray
|
|
274
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export type { ParaGrazConfig, ParaGrazModalProps } from './ParaGrazProvider.js';
|
|
1
|
+
export { toArray, ParaGrazConnector } from './connector.js';
|
|
2
|
+
export type { ParaGrazConfig } from './connector.js';
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import "./chunk-
|
|
3
|
-
import {
|
|
4
|
-
import { ParaGrazInternalProvider } from "./ParaGrazProvider.js";
|
|
2
|
+
import "./chunk-M66XENHI.js";
|
|
3
|
+
import { toArray, ParaGrazConnector } from "./connector.js";
|
|
5
4
|
export {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Environment,
|
|
9
|
-
OAuthMethod,
|
|
10
|
-
ParaGrazInternalProvider,
|
|
11
|
-
WalletType
|
|
5
|
+
ParaGrazConnector,
|
|
6
|
+
toArray
|
|
12
7
|
};
|
package/package.json
CHANGED
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpara/graz-connector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0-dev.10",
|
|
4
|
+
"sideEffects": false,
|
|
4
5
|
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
7
|
-
"sideEffects": false,
|
|
8
|
-
"dependencies": {
|
|
9
|
-
"@getpara/react-sdk": "1.11.0",
|
|
10
|
-
"@getpara/cosmjs-v0-integration": "1.11.0"
|
|
11
|
-
},
|
|
12
8
|
"scripts": {
|
|
13
9
|
"build": "rm -rf dist && yarn typegen && node ./scripts/build.mjs",
|
|
10
|
+
"test": "vitest run --coverage",
|
|
14
11
|
"typegen": "tsc --emitDeclarationOnly"
|
|
15
12
|
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@getpara/cosmjs-v0-integration": "2.0.0-alpha.42",
|
|
15
|
+
"@getpara/web-sdk": "2.0.0-alpha.42"
|
|
16
|
+
},
|
|
16
17
|
"devDependencies": {
|
|
17
|
-
"@cosmjs/amino": "^0.32.
|
|
18
|
-
"@cosmjs/
|
|
19
|
-
"@cosmjs/
|
|
20
|
-
"cosmjs
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"@
|
|
18
|
+
"@cosmjs/amino": "^0.32.4",
|
|
19
|
+
"@cosmjs/cosmwasm-stargate": "^0.32.4",
|
|
20
|
+
"@cosmjs/encoding": "^0.32.4",
|
|
21
|
+
"@cosmjs/launchpad": "0.27.1",
|
|
22
|
+
"@cosmjs/proto-signing": "^0.32.4",
|
|
23
|
+
"@cosmjs/stargate": "^0.32.4",
|
|
24
|
+
"@cosmjs/tendermint-rpc": "^0.32.4",
|
|
25
|
+
"@keplr-wallet/types": "0.12.156",
|
|
26
|
+
"cosmjs-types": "^0.9.0",
|
|
27
|
+
"graz": "^0.3.3",
|
|
28
|
+
"long": "5.3.2",
|
|
29
|
+
"typescript": "5.1.6"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@cosmjs/amino": "*",
|
|
33
|
+
"@cosmjs/cosmwasm-stargate": "*",
|
|
34
|
+
"@cosmjs/encoding": "*",
|
|
35
|
+
"@cosmjs/launchpad": ">=0.27.1",
|
|
36
|
+
"@cosmjs/proto-signing": "*",
|
|
37
|
+
"@cosmjs/stargate": "*",
|
|
38
|
+
"@cosmjs/tendermint-rpc": "*",
|
|
39
|
+
"@keplr-wallet/types": ">=0.12.156",
|
|
40
|
+
"cosmjs-types": ">=0.8.0",
|
|
41
|
+
"graz": ">=0.3.3"
|
|
24
42
|
}
|
|
25
43
|
}
|
package/src/connector.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { fromBech32 } from '@cosmjs/encoding';
|
|
2
|
+
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
|
|
3
|
+
import { Wallet, SignDoc as GrazSignDoc } from 'graz';
|
|
4
|
+
import { ParaAminoSigner, ParaProtoSigner } from '@getpara/cosmjs-v0-integration';
|
|
5
|
+
import { ParaWeb, Wallet as ParaWallet } from '@getpara/web-sdk';
|
|
6
|
+
import { Algo, DirectSignResponse, OfflineDirectSigner } from '@cosmjs/proto-signing';
|
|
7
|
+
import { AccountData, AminoSignResponse, OfflineAminoSigner, StdSignature, StdSignDoc } from '@cosmjs/amino';
|
|
8
|
+
import { ChainInfo, KeplrSignOptions } from '@keplr-wallet/types';
|
|
9
|
+
|
|
10
|
+
export type ParaGrazConnectorEvents = {
|
|
11
|
+
onEnabled?: (chainIds: string[], connector: ParaGrazConnector) => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface ParaGrazConfig {
|
|
15
|
+
paraWeb: ParaWeb;
|
|
16
|
+
events?: ParaGrazConnectorEvents;
|
|
17
|
+
noModal?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function toArray<T>(v: T | T[]): T[] {
|
|
21
|
+
return Array.isArray(v) ? v : [v];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class ParaOfflineSigner implements OfflineDirectSigner {
|
|
25
|
+
constructor(
|
|
26
|
+
protected readonly chainId: string,
|
|
27
|
+
protected readonly connector: ParaGrazConnector,
|
|
28
|
+
) {}
|
|
29
|
+
protected get para() {
|
|
30
|
+
return this.connector.getParaWebClient();
|
|
31
|
+
}
|
|
32
|
+
protected get prefix() {
|
|
33
|
+
return this.connector.getBech32Prefix(this.chainId);
|
|
34
|
+
}
|
|
35
|
+
protected async wallet() {
|
|
36
|
+
return this.connector.getFirstWallet();
|
|
37
|
+
}
|
|
38
|
+
async getAccounts(): Promise<readonly AccountData[]> {
|
|
39
|
+
const key = await this.connector.getKey(this.chainId);
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
address: key.bech32Address,
|
|
43
|
+
algo: key.algo as Algo,
|
|
44
|
+
pubkey: key.pubKey,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
async signDirect(signerAddress: string, signDoc: SignDoc): Promise<DirectSignResponse> {
|
|
49
|
+
if (this.chainId !== signDoc.chainId) {
|
|
50
|
+
throw new Error(`Para connector: signDirect chainId mismatch (expected ${this.chainId}, got ${signDoc.chainId})`);
|
|
51
|
+
}
|
|
52
|
+
const accounts = await this.getAccounts();
|
|
53
|
+
if (accounts.every(a => a.address !== signerAddress)) {
|
|
54
|
+
throw new Error(`Para connector: signerAddress ${signerAddress} not found in wallet`);
|
|
55
|
+
}
|
|
56
|
+
const signer = new ParaProtoSigner(this.para, this.prefix, (await this.wallet()).id);
|
|
57
|
+
const result = await signer.signDirect(signerAddress, signDoc);
|
|
58
|
+
return {
|
|
59
|
+
signed: {
|
|
60
|
+
bodyBytes: result.signed.bodyBytes,
|
|
61
|
+
authInfoBytes: result.signed.authInfoBytes,
|
|
62
|
+
chainId: result.signed.chainId,
|
|
63
|
+
accountNumber: result.signed.accountNumber,
|
|
64
|
+
},
|
|
65
|
+
signature: result.signature,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class ParaGrazConnector implements Omit<Wallet, 'experimentalSuggestChain'> {
|
|
71
|
+
protected paraWebClient: ParaWeb;
|
|
72
|
+
protected enabledChainIds = new Set<string>();
|
|
73
|
+
protected readonly events?: ParaGrazConnectorEvents;
|
|
74
|
+
protected noModal?: boolean;
|
|
75
|
+
constructor(
|
|
76
|
+
protected readonly config: ParaGrazConfig,
|
|
77
|
+
protected readonly chains: ChainInfo[] | null = null,
|
|
78
|
+
) {
|
|
79
|
+
if (!config?.paraWeb) {
|
|
80
|
+
throw new Error('Para connector: missing paraWeb instance in config');
|
|
81
|
+
}
|
|
82
|
+
this.events = config.events;
|
|
83
|
+
this.paraWebClient = config.paraWeb;
|
|
84
|
+
this.noModal = config.noModal;
|
|
85
|
+
}
|
|
86
|
+
protected async ensureChainEnabled(chainId: string): Promise<void> {
|
|
87
|
+
if (!this.enabledChainIds.has(chainId)) {
|
|
88
|
+
throw new Error(`Para connector: chain ${chainId} was not enabled via wallet.enable()`);
|
|
89
|
+
}
|
|
90
|
+
if (!(await this.paraWebClient.isFullyLoggedIn())) {
|
|
91
|
+
throw new Error('Para connector: wallet is not connected – call enable() first');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
protected async waitForLogin(timeoutMs = 60_000): Promise<void> {
|
|
95
|
+
const deadline = Date.now() + timeoutMs;
|
|
96
|
+
let delay = 500;
|
|
97
|
+
const MAX_DELAY = 5_000;
|
|
98
|
+
while (true) {
|
|
99
|
+
if (await this.paraWebClient.isFullyLoggedIn()) return;
|
|
100
|
+
if (Date.now() >= deadline) throw new Error('Para connector: login timeout');
|
|
101
|
+
await new Promise(r => setTimeout(r, delay));
|
|
102
|
+
delay = Math.min(delay * 1.5, MAX_DELAY);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected async waitForAccounts(timeoutMs = 5_000): Promise<ParaWallet[]> {
|
|
107
|
+
const deadline = Date.now() + timeoutMs;
|
|
108
|
+
let delay = 250;
|
|
109
|
+
const MAX_DELAY = 1_000;
|
|
110
|
+
while (true) {
|
|
111
|
+
const wallets = this.paraWebClient.getWalletsByType('COSMOS');
|
|
112
|
+
if (wallets.length) return wallets;
|
|
113
|
+
if (Date.now() >= deadline) throw new Error('Para connector: no COSMOS wallets found after login');
|
|
114
|
+
await new Promise(r => setTimeout(r, delay));
|
|
115
|
+
delay = Math.min(delay * 1.5, MAX_DELAY);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
protected async hasCosmosWallet(): Promise<boolean> {
|
|
119
|
+
return (await this.paraWebClient.isFullyLoggedIn()) && this.paraWebClient.getWalletsByType('COSMOS').length > 0;
|
|
120
|
+
}
|
|
121
|
+
async enable(chainIdsInput: string | string[]): Promise<void> {
|
|
122
|
+
const chainIds = toArray(chainIdsInput);
|
|
123
|
+
const previousEnabled = new Set(this.enabledChainIds);
|
|
124
|
+
try {
|
|
125
|
+
chainIds.forEach(id => this.enabledChainIds.add(id));
|
|
126
|
+
if (await this.hasCosmosWallet()) {
|
|
127
|
+
this.events?.onEnabled?.(chainIds, this);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!this.noModal) {
|
|
131
|
+
throw new Error('Modal rendering not supported in core library. Use @getpara/graz-integration for modal support.');
|
|
132
|
+
}
|
|
133
|
+
await this.waitForLogin();
|
|
134
|
+
await this.waitForAccounts();
|
|
135
|
+
this.events?.onEnabled?.(chainIds, this);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
this.enabledChainIds = previousEnabled;
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async disconnect(): Promise<void> {
|
|
143
|
+
await this.paraWebClient.logout();
|
|
144
|
+
this.enabledChainIds.clear();
|
|
145
|
+
}
|
|
146
|
+
async getFirstWallet(): Promise<ParaWallet> {
|
|
147
|
+
const [wallet] = await this.waitForAccounts();
|
|
148
|
+
return wallet;
|
|
149
|
+
}
|
|
150
|
+
getBech32Prefix(chainId: string): string {
|
|
151
|
+
return this.chains?.find(c => c.chainId === chainId)?.bech32Config?.bech32PrefixAccAddr || 'cosmos';
|
|
152
|
+
}
|
|
153
|
+
getParaWebClient(): ParaWeb {
|
|
154
|
+
return this.paraWebClient;
|
|
155
|
+
}
|
|
156
|
+
getConfig(): ParaGrazConfig {
|
|
157
|
+
return this.config;
|
|
158
|
+
}
|
|
159
|
+
protected buildHybridSigner(chainId: string): OfflineAminoSigner & OfflineDirectSigner {
|
|
160
|
+
const aminoSigner = this.getOfflineSignerOnlyAmino(chainId);
|
|
161
|
+
const directSigner = new ParaOfflineSigner(chainId, this);
|
|
162
|
+
return {
|
|
163
|
+
getAccounts: () => directSigner.getAccounts(),
|
|
164
|
+
signAmino: (signer: string, signDoc: StdSignDoc) => aminoSigner.signAmino(signer, signDoc),
|
|
165
|
+
signDirect: (signer: string, signDoc: SignDoc) => directSigner.signDirect(signer, signDoc),
|
|
166
|
+
} as unknown as OfflineAminoSigner & OfflineDirectSigner;
|
|
167
|
+
}
|
|
168
|
+
async getKey(chainId: string) {
|
|
169
|
+
await this.ensureChainEnabled(chainId);
|
|
170
|
+
const wallet = await this.getFirstWallet();
|
|
171
|
+
const signer = new ParaProtoSigner(this.paraWebClient, this.getBech32Prefix(chainId), wallet.id);
|
|
172
|
+
const [account] = await signer.getAccounts();
|
|
173
|
+
if (!account) throw new Error(`Para connector: wallet ${wallet.id} has no Cosmos accounts`);
|
|
174
|
+
return {
|
|
175
|
+
name: 'Para Wallet',
|
|
176
|
+
algo: account.algo,
|
|
177
|
+
pubKey: account.pubkey,
|
|
178
|
+
address: fromBech32(account.address).data,
|
|
179
|
+
bech32Address: account.address,
|
|
180
|
+
isKeystone: false,
|
|
181
|
+
isNanoLedger: false,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
getOfflineSignerOnlyAmino(chainId: string): OfflineAminoSigner {
|
|
185
|
+
void this.ensureChainEnabled(chainId); // fire & forget – returns void
|
|
186
|
+
const wallet = this.paraWebClient.getWalletsByType('COSMOS')[0];
|
|
187
|
+
if (!wallet) {
|
|
188
|
+
throw new Error(`Para connector: no wallets found when requesting Amino signer for ${chainId}`);
|
|
189
|
+
}
|
|
190
|
+
return new ParaAminoSigner(this.paraWebClient, this.getBech32Prefix(chainId), wallet.id);
|
|
191
|
+
}
|
|
192
|
+
getOfflineSigner(chainId: string): OfflineAminoSigner & OfflineDirectSigner {
|
|
193
|
+
void this.ensureChainEnabled(chainId);
|
|
194
|
+
return this.buildHybridSigner(chainId);
|
|
195
|
+
}
|
|
196
|
+
async getOfflineSignerAuto(chainId: string): Promise<OfflineAminoSigner | OfflineDirectSigner> {
|
|
197
|
+
void this.ensureChainEnabled(chainId);
|
|
198
|
+
return this.buildHybridSigner(chainId);
|
|
199
|
+
}
|
|
200
|
+
async signAmino(
|
|
201
|
+
chainId: string,
|
|
202
|
+
signer: string,
|
|
203
|
+
signDoc: StdSignDoc,
|
|
204
|
+
_signOptions?: KeplrSignOptions,
|
|
205
|
+
): Promise<AminoSignResponse> {
|
|
206
|
+
await this.ensureChainEnabled(chainId);
|
|
207
|
+
try {
|
|
208
|
+
const wallet = await this.getFirstWallet();
|
|
209
|
+
const signerImpl = new ParaAminoSigner(this.paraWebClient, this.getBech32Prefix(chainId), wallet.id);
|
|
210
|
+
return await signerImpl.signAmino(signer, signDoc);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
throw new Error(`Para connector: signAmino failed – ${(err as Error).message}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async signDirect(
|
|
216
|
+
chainId: string,
|
|
217
|
+
signer: string,
|
|
218
|
+
signDoc: GrazSignDoc,
|
|
219
|
+
_signOptions?: KeplrSignOptions,
|
|
220
|
+
): Promise<DirectSignResponse> {
|
|
221
|
+
await this.ensureChainEnabled(chainId);
|
|
222
|
+
try {
|
|
223
|
+
const wallet = await this.getFirstWallet();
|
|
224
|
+
const signerImpl = new ParaProtoSigner(this.paraWebClient, this.getBech32Prefix(chainId), wallet.id);
|
|
225
|
+
const convertedSignDoc: SignDoc = {
|
|
226
|
+
bodyBytes: signDoc.bodyBytes ?? new Uint8Array(),
|
|
227
|
+
authInfoBytes: signDoc.authInfoBytes ?? new Uint8Array(),
|
|
228
|
+
chainId: signDoc.chainId,
|
|
229
|
+
accountNumber: typeof signDoc.accountNumber === 'bigint' ? signDoc.accountNumber : BigInt(signDoc.accountNumber),
|
|
230
|
+
};
|
|
231
|
+
const result = await signerImpl.signDirect(signer, convertedSignDoc);
|
|
232
|
+
return {
|
|
233
|
+
signed: {
|
|
234
|
+
bodyBytes: result.signed.bodyBytes,
|
|
235
|
+
authInfoBytes: result.signed.authInfoBytes,
|
|
236
|
+
chainId: result.signed.chainId,
|
|
237
|
+
accountNumber: result.signed.accountNumber,
|
|
238
|
+
},
|
|
239
|
+
signature: result.signature,
|
|
240
|
+
};
|
|
241
|
+
} catch (err) {
|
|
242
|
+
throw new Error(`Para connector: signDirect failed – ${(err as Error).message}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async signArbitrary(chainId: string, signer: string, data: string | Uint8Array): Promise<StdSignature> {
|
|
246
|
+
await this.ensureChainEnabled(chainId);
|
|
247
|
+
const encodedData =
|
|
248
|
+
typeof data === 'string' ? Buffer.from(data, 'utf-8').toString('base64') : Buffer.from(data).toString('base64');
|
|
249
|
+
const signDoc = {
|
|
250
|
+
chain_id: '',
|
|
251
|
+
account_number: '0',
|
|
252
|
+
sequence: '0',
|
|
253
|
+
fee: { gas: '0', amount: [] },
|
|
254
|
+
msgs: [
|
|
255
|
+
{
|
|
256
|
+
type: 'sign/MsgSignData',
|
|
257
|
+
value: { signer, data: encodedData },
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
memo: '',
|
|
261
|
+
};
|
|
262
|
+
try {
|
|
263
|
+
const response = await this.signAmino(chainId, signer, signDoc);
|
|
264
|
+
return response.signature;
|
|
265
|
+
} catch (err) {
|
|
266
|
+
throw new Error(`Para connector: signArbitrary failed – ${(err as Error).message}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export type { ParaGrazConfig, ParaGrazModalProps } from './ParaGrazProvider.js';
|
|
1
|
+
export { toArray, ParaGrazConnector } from './connector.js';
|
|
2
|
+
export type { ParaGrazConfig } from './connector.js';
|