Skip to content

Commit 5cf3ea5

Browse files
authored
Merge pull request #26 from nathan-f11/feat/add-gate-wallet-support
Feat/add gate wallet support
2 parents f3ad890 + 87b9b83 commit 5cf3ea5

File tree

3 files changed

+236
-1
lines changed

3 files changed

+236
-1
lines changed

lib/components/ConnectWallet/index.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ const list = [
4040
{
4141
wallet: WalletName.Metamask,
4242
logo: 'https://ping.pub/logos/metamask.png'
43+
},
44+
{
45+
wallet: WalletName.GateWallet,
46+
logo: 'data:image/png;base64,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'
4347
}
4448
];
4549

lib/wallet/Wallet.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Registry } from '@cosmjs/proto-signing';
22
import { defaultRegistryTypes } from '@cosmjs/stargate';
33
import { Transaction } from '../utils/type';
44
import { KeplerWallet } from './wallets/KeplerWallet';
5+
import { GateWallet } from './wallets/GateWallet';
56
import { LedgerWallet } from './wallets/LedgerWallet';
67
import { MetamaskWallet } from './wallets/MetamaskWallet';
78
import { MetamaskSnapWallet } from './wallets/MetamaskSnapWallet';
@@ -11,6 +12,7 @@ import { UnisatWallet } from './wallets/UnisatWallet';
1112

1213
export enum WalletName {
1314
Keplr = 'Keplr',
15+
GateWallet = 'Gate Wallet',
1416
Ledger = 'LedgerUSB',
1517
LedgerBLE = 'LedgerBLE',
1618
Metamask = 'Metamask',
@@ -93,7 +95,7 @@ export function readWallet(hdPath?: string) {
9395
export function writeWallet(connected: ConnectedWallet, hdPath?: string) {
9496
localStorage.setItem(
9597
hdPath ||
96-
(connected.wallet === WalletName.Keplr
98+
(connected.wallet === WalletName.Keplr || connected.wallet === WalletName.GateWallet
9799
? DEFAULT_HDPATH
98100
: DEFAULT_LEDGER_HDPATH),
99101
JSON.stringify(connected)
@@ -130,6 +132,8 @@ export function createWallet(
130132
return new UnisatWallet(arg, <IChain>chain, reg);
131133
case WalletName.Keplr:
132134
return new KeplerWallet(arg, reg);
135+
case WalletName.GateWallet:
136+
return new GateWallet(arg, reg);
133137
case WalletName.Ledger:
134138
return new LedgerWallet(arg, reg);
135139
case WalletName.Leap:

lib/wallet/wallets/GateWallet.ts

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import { fromBase64, fromBech32, toHex } from '@cosmjs/encoding';
2+
import {
3+
Registry,
4+
TxBodyEncodeObject,
5+
makeAuthInfoBytes,
6+
makeSignDoc,
7+
} from '@cosmjs/proto-signing';
8+
import {
9+
AbstractWallet,
10+
Account,
11+
WalletArgument,
12+
WalletName,
13+
keyType,
14+
} from '../Wallet';
15+
import { Transaction } from '../../utils/type';
16+
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
17+
import { Any } from 'cosmjs-types/google/protobuf/any';
18+
import { PubKey } from 'cosmjs-types/cosmos/crypto/secp256k1/keys';
19+
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing';
20+
import { AminoTypes, createDefaultAminoConverters } from '@cosmjs/stargate';
21+
import {
22+
makeSignDoc as makeSignDocAmino,
23+
} from '@cosmjs/amino';
24+
import { createWasmAminoConverters } from '@cosmjs/cosmwasm-stargate';
25+
26+
/**
27+
* Gate Wallet implementation for Cosmos chains
28+
*
29+
* Gate Wallet is fully compatible with Keplr API through window.gatewallet.keplr
30+
*
31+
* @see https://www.gate.io/web3
32+
*/
33+
export class GateWallet implements AbstractWallet {
34+
name: WalletName.GateWallet;
35+
chainId: string;
36+
registry: Registry;
37+
conf: WalletArgument;
38+
aminoTypes = new AminoTypes({
39+
...createDefaultAminoConverters(),
40+
...createWasmAminoConverters(),
41+
});
42+
43+
constructor(arg: WalletArgument, registry: Registry) {
44+
console.log('🦛 [GateWallet] Constructor called', {
45+
chainId: arg.chainId,
46+
hasWindow: typeof window !== 'undefined',
47+
hasGateWallet: typeof window !== 'undefined' && !!window.gatewallet,
48+
hasKeplr: typeof window !== 'undefined' && !!window.gatewallet?.keplr
49+
});
50+
51+
this.chainId = arg.chainId || 'cosmoshub';
52+
// @ts-ignore
53+
if (!window.gatewallet?.keplr) {
54+
console.error('❌ [GateWallet] Gate Wallet not found!', {
55+
gatewallet: window.gatewallet,
56+
keplr: window.keplr
57+
});
58+
throw new Error('Please install Gate Wallet extension');
59+
}
60+
this.registry = registry;
61+
this.conf = arg;
62+
console.log('✅ [GateWallet] Constructor completed');
63+
}
64+
65+
async getAccounts(): Promise<Account[]> {
66+
console.log('🦛 [GateWallet] getAccounts called', {
67+
chainId: this.chainId
68+
});
69+
70+
try {
71+
// @ts-ignore
72+
console.log('🦛 [GateWallet] Calling enable...');
73+
await window.gatewallet.keplr.enable(this.chainId);
74+
console.log('✅ [GateWallet] Enable successful');
75+
76+
// @ts-ignore
77+
console.log('🦛 [GateWallet] Getting offline signer...');
78+
const offlineSigner = window.gatewallet.keplr.getOfflineSigner(this.chainId);
79+
console.log('✅ [GateWallet] Got offline signer');
80+
81+
console.log('🦛 [GateWallet] Getting accounts...');
82+
const accounts = await offlineSigner.getAccounts();
83+
console.log('✅ [GateWallet] Got accounts:', accounts);
84+
85+
return accounts;
86+
} catch (error) {
87+
console.error('❌ [GateWallet] Error in getAccounts:', error);
88+
throw error;
89+
}
90+
}
91+
92+
supportCoinType(coinType?: string | undefined): Promise<boolean> {
93+
return Promise.resolve(true);
94+
}
95+
96+
isEthermint() {
97+
return this.conf.hdPath && this.conf.hdPath.startsWith("m/44'/60");
98+
}
99+
100+
async sign(transaction: Transaction, direct?: boolean): Promise<TxRaw> {
101+
// sign wasm tx with signDirect
102+
if (
103+
transaction.messages.findIndex((x) =>
104+
x.typeUrl.startsWith('/cosmwasm.wasm')
105+
) > -1 ||
106+
direct
107+
) {
108+
return this.signDirect(transaction);
109+
}
110+
return this.signAmino(transaction);
111+
}
112+
113+
// @deprecated use signAmino instead
114+
// because signDirect is not supported ledger wallet
115+
async signDirect(transaction: Transaction): Promise<TxRaw> {
116+
const accouts = await this.getAccounts();
117+
const hex = toHex(fromBech32(transaction.signerAddress).data);
118+
const accountFromSigner = accouts.find(
119+
(account) => toHex(fromBech32(account.address).data) === hex
120+
);
121+
if (!accountFromSigner) {
122+
throw new Error('Failed to retrieve account from signer');
123+
}
124+
const pubkey = Any.fromPartial({
125+
typeUrl: keyType(transaction.chainId),
126+
value: PubKey.encode({
127+
key: accountFromSigner.pubkey,
128+
}).finish(),
129+
});
130+
const txBodyEncodeObject: TxBodyEncodeObject = {
131+
typeUrl: '/cosmos.tx.v1beta1.TxBody',
132+
value: {
133+
messages: transaction.messages,
134+
memo: transaction.memo,
135+
},
136+
};
137+
const txBodyBytes = this.registry.encode(txBodyEncodeObject);
138+
const gasLimit = Number(transaction.fee.gas);
139+
const authInfoBytes = makeAuthInfoBytes(
140+
[{ pubkey, sequence: transaction.signerData.sequence }],
141+
transaction.fee.amount,
142+
gasLimit,
143+
transaction.fee.granter,
144+
transaction.fee.payer
145+
);
146+
const signDoc = makeSignDoc(
147+
txBodyBytes,
148+
authInfoBytes,
149+
transaction.chainId,
150+
transaction.signerData.accountNumber
151+
);
152+
153+
// @ts-ignore
154+
const offlineSigner = window.gatewallet.keplr.getOfflineSigner(this.chainId);
155+
const { signature, signed } = await offlineSigner.signDirect(
156+
transaction.signerAddress,
157+
signDoc
158+
);
159+
return TxRaw.fromPartial({
160+
bodyBytes: signed.bodyBytes,
161+
authInfoBytes: signed.authInfoBytes,
162+
signatures: [fromBase64(signature.signature)],
163+
});
164+
}
165+
166+
async signAmino(tx: Transaction): Promise<TxRaw> {
167+
const accounts = await this.getAccounts();
168+
const hex = toHex(fromBech32(tx.signerAddress).data);
169+
const accountFromSigner = accounts.find(
170+
(account) => toHex(fromBech32(account.address).data) === hex
171+
);
172+
if (!accountFromSigner) {
173+
throw new Error('Failed to retrieve account from signer');
174+
}
175+
const pubkey = Any.fromPartial({
176+
typeUrl: keyType(tx.chainId),
177+
value: PubKey.encode({
178+
key: accountFromSigner.pubkey,
179+
}).finish(),
180+
});
181+
const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON;
182+
const msgs = tx.messages.map((msg) => this.aminoTypes.toAmino(msg));
183+
const signDoc = makeSignDocAmino(
184+
msgs,
185+
tx.fee,
186+
tx.chainId,
187+
tx.memo,
188+
tx.signerData.accountNumber,
189+
tx.signerData.sequence
190+
);
191+
192+
// @ts-ignore
193+
const offlineSigner = window.gatewallet.keplr.getOfflineSigner(this.chainId);
194+
const { signature, signed } = await offlineSigner.signAmino(
195+
tx.signerAddress,
196+
signDoc
197+
);
198+
199+
const signedTxBody = {
200+
messages: signed.msgs.map((msg) => this.aminoTypes.fromAmino(msg)),
201+
memo: signed.memo,
202+
};
203+
const signedTxBodyEncodeObject: TxBodyEncodeObject = {
204+
typeUrl: '/cosmos.tx.v1beta1.TxBody',
205+
value: signedTxBody,
206+
};
207+
const signedTxBodyBytes = this.registry.encode(
208+
signedTxBodyEncodeObject
209+
);
210+
211+
const signedGasLimit = Number(signed.fee.gas);
212+
const signedSequence = Number(signed.sequence);
213+
const signedAuthInfoBytes = makeAuthInfoBytes(
214+
[{ pubkey, sequence: signedSequence }],
215+
signed.fee.amount,
216+
signedGasLimit,
217+
signed.fee.granter,
218+
signed.fee.payer,
219+
signMode
220+
);
221+
return TxRaw.fromPartial({
222+
bodyBytes: signedTxBodyBytes,
223+
authInfoBytes: signedAuthInfoBytes,
224+
signatures: [fromBase64(signature.signature)],
225+
});
226+
}
227+
}

0 commit comments

Comments
 (0)