Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: PoC of the SDK #1

Merged
merged 3 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ bin
# Dev environment metadata
.idea
.DS_Store
*.log
*.log

# Local development package
/packages/sdk/adena-wallet-sdk-*.tgz
10 changes: 9 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"author": "Onbloc, Inc.",
"homepage": "https://www.adena.app",
"main": "./bin/index.js",
"module": "./bin/index.mjs",
"types": "./bin/index.d.ts",
"files": [
"bin/**/*"
],
Expand All @@ -26,12 +28,17 @@
"js"
],
"scripts": {
"build": "yarn tsc",
"build": "tsup",
"lint": "eslint",
"lint:fix": "yarn lint --fix",
"format": "prettier src/ --check",
"format:fix": "prettier src/ --write",
jinoosss marked this conversation as resolved.
Show resolved Hide resolved
"test": "jest",
"test:ci": "jest --coverage --passWithNoTests "
},
"dependencies": {
"@gnolang/gno-js-client": "^1.3.0"
},
"devDependencies": {
"@types/eslint": "^9",
"@types/jest": "^29.5.12",
Expand All @@ -44,6 +51,7 @@
"prettier": "^3.3.3",
"ts-jest": "^29.2.3",
"ts-node": "^10.9.2",
"tsup": "^8.2.3",
"typescript": "^5.5.4"
}
}
4 changes: 4 additions & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { EMessageType } from "./types";

export { EMessageType };
export * from "./methods";
26 changes: 26 additions & 0 deletions packages/sdk/src/methods/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { OnAccountChangeFunc, OnEventType, OnNetworkChangeFunc } from "../types/methods/events";
import { getAdena } from "../utils";

/**
* Add a listener on connected account changes
* @async
* @param {OnAccountChangeFunc} func - Function to call on a new event
* @returns Nothing, throws an error if it fails
*/
export const onAccountChange = async (func: OnAccountChangeFunc): Promise<void> => {
const adena = getAdena();

adena.On(OnEventType.CHANGED_ACCOUNT, func);
};

/**
* Add a listener on network changes
* @async
* @param {OnNetworkChangeFunc} func - Function to call on a new event
* @returns Nothing, throws an error if it fails
*/
export const onNetworkChange = async (func: OnNetworkChangeFunc): Promise<void> => {
const adena = getAdena();

adena.On(OnEventType.CHANGED_NETWORK, func);
};
45 changes: 45 additions & 0 deletions packages/sdk/src/methods/general.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { EAdenaResponseStatus } from "../types/common";
import { getAdena } from "../utils";
import { AddEstablishResponse, AddEstablishResponseType, GetAccountResponseData } from "../types/methods/general";

/**
* Establish a connection to your site from Adena
* @async
* @param {string} name - The name of the website requesting to connect
* @returns Original Adena response, useful to check if the site was already connected
*/
export const establishConnection = async (name: string): Promise<AddEstablishResponse> => {
const adena = getAdena();

// Establish a connection to the wallet
const response = await adena.AddEstablish(name);

if (
response.status === EAdenaResponseStatus.SUCCESS &&
(response.type === AddEstablishResponseType.ALREADY_CONNECTED ||
response.type === AddEstablishResponseType.CONNECTION_SUCCESS)
) {
// Adena establishes a connection if:
// - the app was not connected before, and the user approves
// - the app was connected before
return response;
}

throw new Error(`Unable to establish connection: ${response.message}`);
};

/**
* Fetch information about the current connected account
* @async
* @returns Original Adena response with the account information
*/
export const getAccountInfo = async (): Promise<GetAccountResponseData> => {
const adena = getAdena();

const response = await adena.GetAccount();
if (response.status !== EAdenaResponseStatus.SUCCESS) {
throw new Error(`Unable to fetch account info: ${response.message}`);
}

return response.data;
};
4 changes: 4 additions & 0 deletions packages/sdk/src/methods/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { addNetwork, switchNetwork } from "./network";
export { signAndSendTransaction, signTransaction } from "./transactions";
export { establishConnection, getAccountInfo } from "./general";
export { onAccountChange, onNetworkChange } from "./events";
45 changes: 45 additions & 0 deletions packages/sdk/src/methods/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { getAdena } from "../utils";
import { EAdenaResponseStatus } from "../types/common";
import { SwitchNetworkResponseType } from "../types/methods/network";

/**
* Switches the Adena network to the given chain ID
* @async
* @param {string} chainId - Chain ID
* @returns Nothing, throws an error if it fails
*/
export const switchNetwork = async (chainId: string): Promise<void> => {
const adena = getAdena();

const response = await adena.SwitchNetwork(chainId);
if (
(response.status === EAdenaResponseStatus.SUCCESS &&
response.type === SwitchNetworkResponseType.SWITCH_NETWORK_SUCCESS) ||
(response.status === EAdenaResponseStatus.FAILURE &&
response.type === SwitchNetworkResponseType.REDUNDANT_CHANGE_REQUEST)
) {
return;
}

throw new Error(`Unable to switch Adena network: ${response.message}`);
};

/**
* Add a custom network to Adena
* @async
* @param {string} chainId - Chain ID
* @param {string} chainName - Chain name
* @param {string} rpcUrl - Network RPC URL
* @returns Nothing, throws an error if it fails
*/
export const addNetwork = async (chainId: string, chainName: string, rpcUrl: string): Promise<void> => {
const adena = getAdena();

const response = await adena.AddNetwork({ chainId, chainName, rpcUrl });

if (response.status === EAdenaResponseStatus.SUCCESS) {
return;
}

throw new Error(`Unable to add network ${response.message}`);
};
72 changes: 72 additions & 0 deletions packages/sdk/src/methods/transactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { BroadcastTxCommitResult } from "@gnolang/tm2-js-client";

import { getAdena } from "../utils";
import { EAdenaResponseStatus } from "../types/common";
import { ContractMessage } from "../types/methods/transactions";

const DEFAULT_GAS_FEE = 1000000;
const DEFAULT_GAS_LIMIT = 5000000;

/**
* Sign, then send a transaction crafted by a web-app
* @async
* @param {ContractMessage[]} messages - Messages to send in the transaction
* @param {number} gasFee - Actual network fee to pay (in ugnot)
* @param {number} gasWanted - Gas limit (in ugnot)
* @param {string} memo - Transaction memo (tag)
* @returns {BroadcastTxCommitResult} Result of the broadcast transaction
*/
export const signAndSendTransaction = async (
messages: ContractMessage[],
gasFee: number = DEFAULT_GAS_FEE,
gasWanted: number = DEFAULT_GAS_LIMIT,
memo?: string,
): Promise<BroadcastTxCommitResult> => {
const adena = getAdena();

// Sign and send the transaction
const response = await adena.DoContract({
messages,
gasFee,
gasWanted,
memo,
});

if (response.status !== EAdenaResponseStatus.SUCCESS) {
throw new Error(`Unable to send transaction: ${response.message}`);
}

return response.data;
};

/**
* Sign a transaction crafted by a web-app
* @async
* @param {ContractMessage[]} messages - Messages to send in the transaction
* @param {number} gasFee - Actual network fee to pay (in ugnot)
* @param {number} gasWanted - Gas limit (in ugnot)
* @param {string} memo - Transaction memo (tag)
* @returns {string} Encoded transaction
*/
export const signTransaction = async (
messages: ContractMessage[],
gasFee: number = DEFAULT_GAS_FEE,
gasWanted: number = DEFAULT_GAS_LIMIT,
memo?: string,
): Promise<string> => {
const adena = getAdena();

// Sign the transaction
const response = await adena.SignTx({
messages,
gasFee,
gasWanted,
memo,
});

if (response.status !== EAdenaResponseStatus.SUCCESS) {
throw new Error(`Unable to sign transaction ${response.message}`);
}

return response.data.encodedTransaction;
};
12 changes: 12 additions & 0 deletions packages/sdk/src/types/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export enum EAdenaResponseStatus {
SUCCESS = "success",
FAILURE = "failure",
}

export interface IAdenaResponse<T, D> {
code: number;
status: EAdenaResponseStatus;
type: T;
message: string;
data: D;
}
28 changes: 28 additions & 0 deletions packages/sdk/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { AdenaDoContract, AdenaSignTx } from "./methods/transactions";
import { AdenaAddNetwork, AdenaSwitchNetwork } from "./methods/network";
import { AdenaAddEstablish, AdenaGetAccount } from "./methods/general";
import { AdenaOnEvent } from "./methods/events";

export type AdenaWallet = {
// General
AddEstablish: AdenaAddEstablish;
GetAccount: AdenaGetAccount;

// Network
SwitchNetwork: AdenaSwitchNetwork;
AddNetwork: AdenaAddNetwork;

// Transactions
SignTx: AdenaSignTx;
DoContract: AdenaDoContract;

// Events
On: AdenaOnEvent;
};

export enum EMessageType {
MSG_SEND = "/bank.MsgSend",
MSG_CALL = "/vm.m_call",
MSG_ADD_PKG = "/vm.m_addpkg",
MSG_RUN = "/vm.m_run",
}
11 changes: 11 additions & 0 deletions packages/sdk/src/types/methods/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type OnAccountChangeFunc = (address: string) => void;
export type OnNetworkChangeFunc = (network: string) => void;

export enum OnEventType {
CHANGED_ACCOUNT = "changedAccount",
CHANGED_NETWORK = "changedNetwork",
}

type OnEventFunc = OnAccountChangeFunc | OnNetworkChangeFunc;

export type AdenaOnEvent = (event: OnEventType, func: OnEventFunc) => void;
38 changes: 38 additions & 0 deletions packages/sdk/src/types/methods/general.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { IAdenaResponse } from "../common";

export enum AddEstablishResponseType {
CONNECTION_SUCCESS = "CONNECTION_SUCCESS",
ALREADY_CONNECTED = "ALREADY_CONNECTED",
}

export type AddEstablishResponse = IAdenaResponse<AddEstablishResponseType, Record<string, never>>;

export type AdenaAddEstablish = (name: string) => Promise<AddEstablishResponse>;

enum GetAccountResponseType {
GET_ACCOUNT = "GET_ACCOUNT",
NO_ACCOUNT = "NO_ACCOUNT",
WALLET_LOCKED = "WALLET_LOCKED",
}

enum EAccountStatus {
ACTIVE = "ACTIVE",
INACTIVE = "IN_ACTIVE",
}

export type GetAccountResponseData = {
accountNumber: string;
address: string;
coins: string;
chainId: string;
sequence: string;
status: EAccountStatus;
public_key: {
"@type": string;
value: string;
};
};

type GetAccountResponse = IAdenaResponse<GetAccountResponseType, GetAccountResponseData>;

export type AdenaGetAccount = () => Promise<GetAccountResponse>;
29 changes: 29 additions & 0 deletions packages/sdk/src/types/methods/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { IAdenaResponse } from "../common";

type AddNetworkParams = {
chainId: string;
chainName: string;
rpcUrl: string;
};

enum AddNetworkResponseType {
ADD_NETWORK_SUCCESS = "ADD_NETWORK_SUCCESS",
}

type AddNetworkResponse = IAdenaResponse<AddNetworkResponseType, AddNetworkParams>;

export type AdenaAddNetwork = (network: AddNetworkParams) => Promise<AddNetworkResponse>;

export enum SwitchNetworkResponseType {
SWITCH_NETWORK_SUCCESS = "SWITCH_NETWORK_SUCCESS",
REDUNDANT_CHANGE_REQUEST = "REDUNDANT_CHANGE_REQUEST",
UNADDED_NETWORK = "UNADDED_NETWORK",
}

type SwitchNetworkResponseData = {
chainId: string;
};

type SwitchNetworkResponse = IAdenaResponse<SwitchNetworkResponseType, SwitchNetworkResponseData>;

export type AdenaSwitchNetwork = (chainId: string) => Promise<SwitchNetworkResponse>;
Loading