diff --git a/expo/README.md b/expo/README.md
index 470f45b9..38c94203 100644
--- a/expo/README.md
+++ b/expo/README.md
@@ -108,11 +108,11 @@ before continuing.
You can run one of the following command:
```shell
-make pack
+make npm.pack
```
or
```shell
-make publish
+make npm.publish
```
diff --git a/expo/example/App.tsx b/expo/example/App.tsx
index 989b7f80..7576d7d2 100644
--- a/expo/example/App.tsx
+++ b/expo/example/App.tsx
@@ -1,14 +1,30 @@
-import * as Gnonative from '@gnolang/gnonative';
+import { GnokeyProvider, useGnokeyContext } from '@gnolang/gnonative';
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
+const config = {
+ remote: 'https://gno.berty.io',
+ chain_id: 'dev',
+};
+
export default function App() {
- const gno = Gnonative.useGno();
+ return (
+
+
+
+ );
+}
+
+const InnerApp = () => {
+ const gno = useGnokeyContext();
const [greeting, setGreeting] = useState('');
useEffect(() => {
- const greeting = async () => {
+ (async () => {
try {
+ const accounts = await gno.listKeyInfo();
+ console.log(accounts);
+
setGreeting(await gno.hello('Gno'));
for await (const res of await gno.helloStream('Gno')) {
@@ -17,16 +33,16 @@ export default function App() {
} catch (error) {
console.log(error);
}
- };
- greeting();
+ })();
}, []);
return (
- Hey {greeting}
+ Gnonative App
+ {greeting}
);
-}
+};
const styles = StyleSheet.create({
container: {
diff --git a/expo/src/hooks/use-gno.ts b/expo/src/hooks/use-gno.ts
index e91efa40..b7b64a42 100644
--- a/expo/src/hooks/use-gno.ts
+++ b/expo/src/hooks/use-gno.ts
@@ -101,12 +101,18 @@ export interface GnoResponse {
helloStream: (name: string) => Promise>;
}
+enum Status {
+ Stopped,
+ Starting,
+ Started,
+}
+
let clientInstance: PromiseClient | undefined = undefined;
-let bridgeInstance: boolean = false;
+let bridgeStatus: Status = Status.Stopped;
export const useGno = (): GnoResponse => {
const getClient = async () => {
- if (!bridgeInstance) {
+ if (bridgeStatus === Status.Stopped) {
await initBridge();
}
@@ -127,20 +133,22 @@ export const useGno = (): GnoResponse => {
};
const closeBridge = async () => {
- if (bridgeInstance) {
+ if (bridgeStatus !== Status.Stopped) {
console.log('Closing bridge...');
await GoBridge.closeBridge();
console.log('Bridge closed.');
- bridgeInstance = false;
+ bridgeStatus = Status.Stopped;
+ clientInstance = undefined;
}
};
const initBridge = async () => {
- if (!bridgeInstance) {
+ if (bridgeStatus === Status.Stopped) {
console.log('Initializing bridge...');
+ bridgeStatus = Status.Starting;
await GoBridge.initBridge();
console.log('Bridge initialized.');
- bridgeInstance = true;
+ bridgeStatus = Status.Started;
}
};
diff --git a/expo/src/index.ts b/expo/src/index.ts
index 063b7f0e..9eb57d39 100644
--- a/expo/src/index.ts
+++ b/expo/src/index.ts
@@ -17,4 +17,5 @@ export function addChangeListener(listener: (event: ChangeEventPayload) => void)
export { ChangeEventPayload, GnonativeView, GnonativeViewProps };
export { useGno } from './hooks/use-gno';
+export * from './provider/gnokey-provider';
export * from '@buf/gnolang_gnonative.bufbuild_es/gnonativetypes_pb';
diff --git a/expo/src/provider/gnokey-provider.tsx b/expo/src/provider/gnokey-provider.tsx
new file mode 100644
index 00000000..6b63dcd3
--- /dev/null
+++ b/expo/src/provider/gnokey-provider.tsx
@@ -0,0 +1,485 @@
+import {
+ AddressFromBech32Request,
+ AddressToBech32Request,
+ CallRequest,
+ CallResponse,
+ CreateAccountRequest,
+ DeleteAccountRequest,
+ DeleteAccountResponse,
+ GenerateRecoveryPhraseRequest,
+ GetActiveAccountRequest,
+ GetActiveAccountResponse,
+ GetChainIDRequest,
+ GetKeyInfoByAddressRequest,
+ GetKeyInfoByNameOrAddressRequest,
+ GetKeyInfoByNameRequest,
+ GetRemoteRequest,
+ HasKeyByAddressRequest,
+ HasKeyByNameOrAddressRequest,
+ HasKeyByNameRequest,
+ HelloRequest,
+ HelloStreamResponse,
+ ListKeyInfoRequest,
+ MsgCall,
+ MsgSend,
+ QEvalRequest,
+ QueryAccountRequest,
+ QueryAccountResponse,
+ QueryRequest,
+ QueryResponse,
+ RenderRequest,
+ SelectAccountRequest,
+ SelectAccountResponse,
+ SendRequest,
+ SendResponse,
+ SetChainIDRequest,
+ SetChainIDResponse,
+ SetPasswordRequest,
+ SetPasswordResponse,
+ SetRemoteRequest,
+ SetRemoteResponse,
+} from '@buf/gnolang_gnonative.bufbuild_es/gnonativetypes_pb';
+import { GnoNativeService } from '@buf/gnolang_gnonative.connectrpc_es/rpc_connect';
+import { PromiseClient } from '@connectrpc/connect';
+import { createContext, useContext, useEffect, useState } from 'react';
+
+import { GoBridge } from '../GoBridge';
+import * as Grpc from '../grpc/client';
+import { GnoAccount } from '../hooks/types';
+
+export interface GnokeyContextProps {
+ initGnokey: (config: ConfigProps) => Promise;
+
+ setRemote: (remote: string) => Promise;
+ getRemote: () => Promise;
+ setChainID: (chainId: string) => Promise;
+ getChainID: () => Promise;
+ createAccount: (
+ nameOrBech32: string,
+ mnemonic: string,
+ password: string,
+ ) => Promise;
+ generateRecoveryPhrase: () => Promise;
+ listKeyInfo: () => Promise;
+ hasKeyByName: (name: string) => Promise;
+ hasKeyByAddress: (address: Uint8Array) => Promise;
+ hasKeyByNameOrAddress: (nameOrBech32: string) => Promise;
+ getKeyInfoByName: (name: string) => Promise;
+ getKeyInfoByAddress: (address: Uint8Array) => Promise;
+ getKeyInfoByNameOrAddress: (nameOrBech32: string) => Promise;
+ selectAccount: (nameOrBech32: string) => Promise;
+ setPassword: (password: string) => Promise;
+ getActiveAccount: () => Promise;
+ queryAccount: (address: Uint8Array) => Promise;
+ deleteAccount: (
+ nameOrBech32: string,
+ password: string | undefined,
+ skipPassword: boolean,
+ ) => Promise;
+ query: (path: string, data: Uint8Array) => Promise;
+ render: (packagePath: string, args: string) => Promise;
+ qEval: (packagePath: string, expression: string) => Promise;
+ call: (
+ packagePath: string,
+ fnc: string,
+ args: string[],
+ gasFee: string,
+ gasWanted: number,
+ send?: string,
+ memo?: string,
+ ) => Promise>;
+ send: (
+ toAddress: Uint8Array,
+ send: string,
+ gasFee: string,
+ gasWanted: number,
+ memo?: string,
+ ) => Promise>;
+ addressToBech32: (address: Uint8Array) => Promise;
+ addressFromBech32: (bech32Address: string) => Promise;
+ // closeBridge: () => Promise;
+ // initBridge: () => Promise;
+ // debug
+ hello: (name: string) => Promise;
+ helloStream: (name: string) => Promise>;
+}
+
+interface ConfigProps {
+ remote: string;
+ chain_id: string;
+}
+
+interface GnokeyProviderProps {
+ config: ConfigProps;
+ children: React.ReactNode;
+}
+
+enum BridgeStatus {
+ Stopped,
+ Starting,
+ Started,
+}
+
+const GnokeyContext = createContext(null);
+
+const GnokeyProvider: React.FC = ({ children, config }) => {
+ const [initialized, setInitialized] = useState(false);
+ const [clientInstance, setClientInstance] = useState<
+ PromiseClient | undefined
+ >(undefined);
+ const [bridgeStatus, setBridgeStatus] = useState(BridgeStatus.Stopped);
+
+ useEffect(() => {
+ (async () => {
+ await initGnokey(config);
+ setInitialized(true);
+ })();
+ }, []);
+
+ async function initGnokey(config): Promise {
+ console.log(
+ '🍄 Initializing Gnokey on remote: %s chain_id: %s',
+ config.remote,
+ config.chain_id,
+ );
+
+ if (bridgeStatus === BridgeStatus.Stopped) {
+ console.log('Bridge stopped. Initializing...');
+ await initBridge();
+ }
+
+ if (clientInstance) {
+ console.error('GoBridge already initialized.');
+ return true;
+ }
+
+ const port = await GoBridge.getTcpPort();
+ console.log('GoBridge GRPC client instance port: %s', port);
+ const client = await Grpc.createClient(port);
+ setClientInstance(client);
+ console.log('GoBridge GRPC client instance. Done.');
+
+ try {
+ await client.setRemote(new SetRemoteRequest({ remote: 'gno.land:26657' }));
+ await client.setChainID(new SetChainIDRequest({ chainId: 'portal-loop' }));
+
+ console.log('✅ Gnokey bridge initialized.');
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+
+ return true;
+ }
+
+ const initBridge = async () => {
+ if (bridgeStatus === BridgeStatus.Stopped) {
+ console.log('Initializing bridge...');
+ setBridgeStatus(BridgeStatus.Starting);
+ await GoBridge.initBridge();
+ console.log('Bridge initialized.');
+ setBridgeStatus(BridgeStatus.Started);
+ }
+ };
+
+ const setRemote = async (remote: string) => {
+ const client = await getClient();
+ const response = await client.setRemote(new SetRemoteRequest({ remote }));
+ return response;
+ };
+
+ const getRemote = async () => {
+ const client = await getClient();
+ const response = await client.getRemote(new GetRemoteRequest());
+ return response.remote;
+ };
+
+ const setChainID = async (chainId: string) => {
+ const client = await getClient();
+ const response = await client.setChainID(new SetChainIDRequest({ chainId }));
+ return response;
+ };
+
+ const getChainID = async () => {
+ const client = await getClient();
+ const response = await client.getChainID(new GetChainIDRequest());
+ return response.chainId;
+ };
+
+ const createAccount = async (nameOrBech32: string, mnemonic: string, password: string) => {
+ const client = await getClient();
+ const reponse = await client.createAccount(
+ new CreateAccountRequest({
+ nameOrBech32,
+ mnemonic,
+ password,
+ }),
+ );
+ return reponse.key;
+ };
+
+ const generateRecoveryPhrase = async () => {
+ const client = await getClient();
+ const response = await client.generateRecoveryPhrase(new GenerateRecoveryPhraseRequest());
+ return response.phrase;
+ };
+
+ const hasKeyByName = async (name: string) => {
+ const client = await getClient();
+ const response = await client.hasKeyByName(new HasKeyByNameRequest({ name }));
+ return response.has;
+ };
+
+ const hasKeyByAddress = async (address: Uint8Array) => {
+ const client = await getClient();
+ const response = await client.hasKeyByAddress(new HasKeyByAddressRequest({ address }));
+ return response.has;
+ };
+
+ const hasKeyByNameOrAddress = async (nameOrBech32: string) => {
+ const client = await getClient();
+ const response = await client.hasKeyByNameOrAddress(
+ new HasKeyByNameOrAddressRequest({ nameOrBech32 }),
+ );
+ return response.has;
+ };
+
+ const getKeyInfoByName = async (name: string) => {
+ const client = await getClient();
+ const response = await client.getKeyInfoByName(new GetKeyInfoByNameRequest({ name }));
+ return response.key;
+ };
+
+ const getKeyInfoByAddress = async (address: Uint8Array) => {
+ const client = await getClient();
+ const response = await client.getKeyInfoByAddress(new GetKeyInfoByAddressRequest({ address }));
+ return response.key;
+ };
+
+ const getKeyInfoByNameOrAddress = async (nameOrBech32: string) => {
+ const client = await getClient();
+ const response = await client.getKeyInfoByNameOrAddress(
+ new GetKeyInfoByNameOrAddressRequest({ nameOrBech32 }),
+ );
+ return response.key;
+ };
+
+ const listKeyInfo = async () => {
+ const client = await getClient();
+ const response = await client.listKeyInfo(new ListKeyInfoRequest());
+ return response.keys;
+ };
+
+ const selectAccount = async (nameOrBech32: string) => {
+ const client = await getClient();
+ const response = await client.selectAccount(
+ new SelectAccountRequest({
+ nameOrBech32,
+ }),
+ );
+ return response;
+ };
+
+ const getClient = async () => {
+ if (!clientInstance) {
+ throw new Error('GoBridge client instance not initialized.');
+ }
+
+ return clientInstance;
+ };
+
+ const setPassword = async (password: string) => {
+ const client = await getClient();
+ const response = await client.setPassword(new SetPasswordRequest({ password }));
+ return response;
+ };
+
+ const getActiveAccount = async () => {
+ const client = await getClient();
+ const response = await client.getActiveAccount(new GetActiveAccountRequest());
+ return response;
+ };
+
+ const queryAccount = async (address: Uint8Array) => {
+ const client = await getClient();
+ const reponse = await client.queryAccount(new QueryAccountRequest({ address }));
+ return reponse;
+ };
+
+ const deleteAccount = async (
+ nameOrBech32: string,
+ password: string | undefined,
+ skipPassword: boolean,
+ ) => {
+ const client = await getClient();
+ const response = await client.deleteAccount(
+ new DeleteAccountRequest({
+ nameOrBech32,
+ password,
+ skipPassword,
+ }),
+ );
+ return response;
+ };
+
+ const query = async (path: string, data: Uint8Array) => {
+ const client = await getClient();
+ const reponse = await client.query(
+ new QueryRequest({
+ path,
+ data,
+ }),
+ );
+ return reponse;
+ };
+
+ const render = async (packagePath: string, args: string) => {
+ const client = await getClient();
+ const reponse = await client.render(
+ new RenderRequest({
+ packagePath,
+ args,
+ }),
+ );
+ return reponse.result;
+ };
+
+ const qEval = async (packagePath: string, expression: string) => {
+ const client = await getClient();
+ const reponse = await client.qEval(
+ new QEvalRequest({
+ packagePath,
+ expression,
+ }),
+ );
+ return reponse.result;
+ };
+
+ const call = async (
+ packagePath: string,
+ fnc: string,
+ args: string[],
+ gasFee: string,
+ gasWanted: number,
+ send?: string,
+ memo?: string,
+ ) => {
+ const client = await getClient();
+ const reponse = client.call(
+ new CallRequest({
+ gasFee,
+ gasWanted: BigInt(gasWanted),
+ memo,
+ msgs: [
+ new MsgCall({
+ packagePath,
+ fnc,
+ args,
+ send,
+ }),
+ ],
+ }),
+ );
+ return reponse;
+ };
+
+ const send = async (
+ toAddress: Uint8Array,
+ send: string,
+ gasFee: string,
+ gasWanted: number,
+ memo?: string,
+ ) => {
+ const client = await getClient();
+ const reponse = client.send(
+ new SendRequest({
+ gasFee,
+ gasWanted: BigInt(gasWanted),
+ memo,
+ msgs: [
+ new MsgSend({
+ toAddress,
+ send,
+ }),
+ ],
+ }),
+ );
+ return reponse;
+ };
+
+ const addressToBech32 = async (address: Uint8Array) => {
+ const client = await getClient();
+ const response = await client.addressToBech32(new AddressToBech32Request({ address }));
+ return response.bech32Address;
+ };
+
+ const addressFromBech32 = async (bech32Address: string) => {
+ const client = await getClient();
+ const response = await client.addressFromBech32(
+ new AddressFromBech32Request({ bech32Address }),
+ );
+ return response.address;
+ };
+
+ // debug
+ const hello = async (name: string) => {
+ const client = await getClient();
+ const response = await client.hello(new HelloRequest({ name }));
+ return response.greeting;
+ };
+
+ // debug
+ const helloStream = async (name: string) => {
+ const client = await getClient();
+ return client.helloStream(new HelloRequest({ name }));
+ };
+
+ const value = {
+ initGnokey,
+ setRemote,
+ getRemote,
+ setChainID,
+ getChainID,
+ createAccount,
+ generateRecoveryPhrase,
+ listKeyInfo,
+ hasKeyByName,
+ hasKeyByAddress,
+ hasKeyByNameOrAddress,
+ getKeyInfoByName,
+ getKeyInfoByAddress,
+ getKeyInfoByNameOrAddress,
+ selectAccount,
+ setPassword,
+ getActiveAccount,
+ queryAccount,
+ deleteAccount,
+ query,
+ render,
+ qEval,
+ call,
+ send,
+ addressToBech32,
+ addressFromBech32,
+ // debug
+ hello,
+ helloStream,
+ };
+
+ if (!initialized) {
+ return null;
+ }
+
+ return {children};
+};
+
+function useGnokeyContext() {
+ const context = useContext(GnokeyContext) as GnokeyContextProps;
+
+ if (context === undefined) {
+ throw new Error('useGnokeyContext must be used within a GnokeyProvider');
+ }
+ return context;
+}
+
+export { useGnokeyContext, GnokeyProvider };