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/1012: Improve Tx API in SDK #1033

Merged
merged 6 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions apps/extension/src/Setup/Ledger/LedgerConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const LedgerConfirmation = (): JSX.Element => {
<ViewKeys
publicKeyAddress={account.publicKey}
transparentAccountAddress={account.address}
trimCharacters={35}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this in a previous PR

/>
<ActionButton size="lg" onClick={closeCurrentTab}>
Finish Setup
Expand Down
16 changes: 15 additions & 1 deletion apps/extension/src/background/approvals/handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { WrapperTxMsgValue } from "@namada/types";
import BigNumber from "bignumber.js";
import createMockInstance from "jest-create-mock-instance";
import {
ApproveConnectInterfaceMsg,
Expand Down Expand Up @@ -46,7 +48,19 @@ describe("approvals handler", () => {
};

const approveTxMsg = new ApproveSignTxMsg(
[{ txBytes: "", signingDataBytes: [""] }],
[
{
args: new WrapperTxMsgValue({
token: "",
feeAmount: BigNumber(0),
gasLimit: BigNumber(0),
chainId: "",
}),
hash: "",
bytes: "",
signingData: [],
},
],
"signer"
);

Expand Down
18 changes: 13 additions & 5 deletions apps/extension/src/background/approvals/service.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { WrapperTxMsgValue } from "@namada/types";
import { paramsToUrl } from "@namada/utils";
import { ChainsService } from "background/chains";
import { KeyRingService } from "background/keyring";
import { SdkService } from "background/sdk";
import { VaultService } from "background/vault";
import BigNumber from "bignumber.js";
import { ExtensionBroadcaster } from "extension";
import createMockInstance from "jest-create-mock-instance";
import { LocalStorage } from "storage";
Expand Down Expand Up @@ -210,16 +212,22 @@ describe("approvals service", () => {
it("should reject resolver", async () => {
const tabId = 1;
const signer = "signer";
// data expected to be base64-encoded
const txBytes = "dHhEYXRh"; // "txData"
const signingDataBytes = "c2lnbmluZ0RhdGE="; // "signingData"
// tx bytes expected to be base64-encoded
const bytes = "dHhEYXRh"; // "txData"

(keyRingService.queryAccountDetails as any).mockResolvedValue(() => ({}));

const signaturePromise = service.approveSignTx(signer, [
{
txBytes,
signingDataBytes: [signingDataBytes],
args: new WrapperTxMsgValue({
token: "",
feeAmount: BigNumber(0),
gasLimit: BigNumber(0),
chainId: "",
}),
hash: "",
bytes,
signingData: [],
},
]);

Expand Down
29 changes: 10 additions & 19 deletions apps/extension/src/background/approvals/service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { fromBase64, toBase64 } from "@cosmjs/encoding";
import { toBase64 } from "@cosmjs/encoding";
import { v4 as uuid } from "uuid";
import browser, { Windows } from "webextension-polyfill";

import { BuiltTx } from "@heliax/namada-sdk/web";
import { KVStore } from "@namada/storage";
import { SignArbitraryResponse, TxDetails } from "@namada/types";
import { paramsToUrl } from "@namada/utils";
Expand All @@ -14,6 +13,7 @@ import { SdkService } from "background/sdk";
import { VaultService } from "background/vault";
import { ExtensionBroadcaster } from "extension";
import { LocalStorage } from "storage";
import { fromEncodedTx } from "utils";
import { EncodedTxData, PendingTx } from "./types";

export class ApprovalsService {
Expand Down Expand Up @@ -50,12 +50,10 @@ export class ApprovalsService {

const pendingTx: PendingTx = {
signer,
txs: txs.map(({ txBytes, signingDataBytes }) => ({
txBytes: fromBase64(txBytes),
signingDataBytes: signingDataBytes.map((bytes) => fromBase64(bytes)),
})),
txs: txs.map((encodedTx) => fromEncodedTx(encodedTx)),
checksums,
};

await this.txStore.set(msgId, pendingTx);

const url = `${browser.runtime.getURL(
Expand Down Expand Up @@ -122,16 +120,9 @@ export class ApprovalsService {
throw new Error(`Signing data for ${msgId} not found!`);
}

const txs = pendingTx.txs.map(({ txBytes, signingDataBytes }) => {
return new BuiltTx(
txBytes,
signingDataBytes.map((sdBytes) => [...sdBytes])
);
});

try {
const signedBytes: Uint8Array[] = [];
for await (const tx of txs) {
for await (const tx of pendingTx.txs) {
signedBytes.push(await this.keyRingService.sign(tx, signer));
}
resolvers.resolve(signedBytes);
Expand Down Expand Up @@ -165,8 +156,8 @@ export class ApprovalsService {
const { tx } = this.sdkService.getSdk();

try {
const signedTxs = pendingTx.txs.map(({ txBytes }, i) => {
return tx.appendSignature(txBytes, responseSign[i]);
const signedTxs = pendingTx.txs.map(({ bytes }, i) => {
return tx.appendSignature(bytes, responseSign[i]);
});
resolvers.resolve(signedTxs);
} catch (e) {
Expand Down Expand Up @@ -303,8 +294,8 @@ export class ApprovalsService {
}

const { tx } = this.sdkService.getSdk();
return pendingTx.txs.map(({ txBytes }) =>
tx.deserialize(txBytes, pendingTx.checksums || {})
return pendingTx.txs.map(({ bytes }) =>
tx.deserialize(bytes, pendingTx.checksums || {})
);
}

Expand All @@ -316,7 +307,7 @@ export class ApprovalsService {
}

if (pendingTx.txs) {
return pendingTx.txs.map(({ txBytes }) => toBase64(txBytes));
return pendingTx.txs.map(({ bytes }) => toBase64(bytes));
}
}

Expand Down
19 changes: 13 additions & 6 deletions apps/extension/src/background/approvals/types.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { TxData } from "@namada/types";
import { SigningDataProps, TxProps } from "@namada/types";

export type ApprovedOriginsStore = string[];

export type PendingTx = {
txs: TxData[];
txs: TxProps[];
signer: string;
checksums?: Record<string, string>;
};

export type PendingSignArbitrary = string;

// base64 encoded Tx data for use with postMessage
export type EncodedTxData = {
txBytes: string;
signingDataBytes: string[];
// base64 encoded Uint8Arrays for use with postMessage
export type EncodedSigningData = Pick<
SigningDataProps,
"publicKeys" | "threshold" | "feePayer" | "owner"
> & {
accountPublicKeysMap?: string;
};

export type EncodedTxData = Pick<TxProps, "args" | "hash"> & {
bytes: string;
signingData: EncodedSigningData[];
};
6 changes: 3 additions & 3 deletions apps/extension/src/background/keyring/keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Bip44Path,
DerivedAccount,
SignArbitraryResponse,
TxProps,
} from "@namada/types";
import { Result, assertNever, truncateInMiddle } from "@namada/utils";

Expand All @@ -19,7 +20,6 @@ import {
UtilityStore,
} from "./types";

import { BuiltTx } from "@namada/shared";
import { SdkService } from "background/sdk";
import { VaultService } from "background/vault";
import { KeyStore, KeyStoreType, SensitiveType, VaultStorage } from "storage";
Expand Down Expand Up @@ -544,14 +544,14 @@ export class KeyRing {
}

async sign(
builtTx: BuiltTx,
txProps: TxProps,
signer: string,
chainId: string
): Promise<Uint8Array> {
await this.vaultService.assertIsUnlocked();
const key = await this.getSigningKey(signer);
const { signing } = this.sdkService.getSdk();
return await signing.sign(builtTx, key, chainId);
return await signing.sign(txProps, key, chainId);
}

async signArbitrary(
Expand Down
6 changes: 3 additions & 3 deletions apps/extension/src/background/keyring/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
Bip44Path,
DerivedAccount,
SignArbitraryResponse,
TxProps,
} from "@namada/types";
import { Result, truncateInMiddle } from "@namada/utils";

import { BuiltTx } from "@namada/shared";
import { ChainsService } from "background/chains";
import { SdkService } from "background/sdk/service";
import { VaultService } from "background/vault";
Expand Down Expand Up @@ -167,9 +167,9 @@ export class KeyRingService {
return await IndexedDBKVStore.durabilityCheck();
}

async sign(builtTx: BuiltTx, signer: string): Promise<Uint8Array> {
async sign(txProps: TxProps, signer: string): Promise<Uint8Array> {
const { chainId } = await this.chainsService.getChain();
return await this._keyRing.sign(builtTx, signer, chainId);
return await this._keyRing.sign(txProps, signer, chainId);
}

async signArbitrary(
Expand Down
8 changes: 3 additions & 5 deletions apps/extension/src/provider/Namada.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { toBase64 } from "@cosmjs/encoding";
import {
Chain,
DerivedAccount,
Expand All @@ -10,6 +9,7 @@ import {
} from "@namada/types";
import { MessageRequester, Ports } from "router";

import { toEncodedTx } from "utils";
import {
ApproveConnectInterfaceMsg,
ApproveSignArbitraryMsg,
Expand Down Expand Up @@ -65,10 +65,8 @@ export class Namada implements INamada {
return await this.requester?.sendMessage(
Ports.Background,
new ApproveSignTxMsg(
txs.map(({ txBytes, signingDataBytes }) => ({
txBytes: toBase64(txBytes),
signingDataBytes: signingDataBytes.map((bytes) => toBase64(bytes)),
})),
// Encode all transactions for use with postMessage
txs.map((txProps) => toEncodedTx(txProps)),
signer,
checksums
)
Expand Down
4 changes: 2 additions & 2 deletions apps/extension/src/provider/Signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Signer as ISigner,
Namada,
SignArbitraryResponse,
TxData,
TxProps,
} from "@namada/types";

export class Signer implements ISigner {
Expand Down Expand Up @@ -44,7 +44,7 @@ export class Signer implements ISigner {
}

public async sign(
tx: TxData | TxData[],
tx: TxProps | TxProps[],
signer: string,
checksums?: Record<string, string>
): Promise<Uint8Array[] | undefined> {
Expand Down
31 changes: 28 additions & 3 deletions apps/extension/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { fromBase64, toBase64 } from "@cosmjs/encoding";
import { TxProps } from "@namada/types";
import { v5 as uuid } from "uuid";
import browser from "webextension-polyfill";

import { Result } from "@namada/utils";
import { EncodedTxData } from "background/approvals";

/**
* Query the current extension tab and close it
Expand Down Expand Up @@ -55,11 +58,33 @@ export const validatePrivateKey = (
): Result<null, PrivateKeyError> =>
privateKey.length > PRIVATE_KEY_MAX_LENGTH ?
Result.err({ t: "TooLong", maxLength: PRIVATE_KEY_MAX_LENGTH })
: !/^[0-9a-f]*$/.test(privateKey) ? Result.err({ t: "BadCharacter" })
: Result.ok(null);
: !/^[0-9a-f]*$/.test(privateKey) ? Result.err({ t: "BadCharacter" })
: Result.ok(null);

// Remove prefix from private key, which may be present when exporting keys from CLI
export const filterPrivateKeyPrefix = (privateKey: string): string =>
privateKey.length === PRIVATE_KEY_MAX_LENGTH + 2 ?
privateKey.replace(/^00/, "")
: privateKey;
: privateKey;

// Convert any Uint8Arrays in TxProps to string, and construct EncodedTxData
export const toEncodedTx = (txProps: TxProps): EncodedTxData => ({
...txProps,
bytes: toBase64(txProps.bytes),
signingData: txProps.signingData.map((sd) => ({
...sd,
accountPublicKeysMap:
sd.accountPublicKeysMap ? toBase64(sd.accountPublicKeysMap) : undefined,
})),
});

// Convert base64 strings back to Uint8Arrays in EncodedTxData to restore TxProps
export const fromEncodedTx = (encodedTxData: EncodedTxData): TxProps => ({
...encodedTxData,
bytes: fromBase64(encodedTxData.bytes),
signingData: encodedTxData.signingData.map((sd) => ({
...sd,
accountPublicKeysMap:
sd.accountPublicKeysMap ? fromBase64(sd.accountPublicKeysMap) : undefined,
})),
});
4 changes: 2 additions & 2 deletions apps/namadillo/src/App/Governance/SubmitVote.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { BuiltTx } from "@heliax/namada-sdk/web";
import {
ActionButton,
Modal,
Expand All @@ -7,6 +6,7 @@ import {
TickedRadioList,
} from "@namada/components";
import {
TxProps,
VoteProposalProps,
VoteType,
isVoteType,
Expand Down Expand Up @@ -80,7 +80,7 @@ export const WithProposalId: React.FC<{ proposalId: bigint }> = ({

const onCloseModal = (): void => navigate(-1);

const dispatchPendingNotification = (txs: BuiltTx[]): void => {
const dispatchPendingNotification = (txs: TxProps[]): void => {
dispatchNotification({
id: createNotificationId(txs),
type: "pending",
Expand Down
8 changes: 4 additions & 4 deletions apps/namadillo/src/atoms/notifications/functions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BuiltTx } from "@heliax/namada-sdk/web";
import { TxProps } from "@namada/types";

export const createNotificationId = (data?: BuiltTx | BuiltTx[]): string => {
export const createNotificationId = (data?: TxProps | TxProps[]): string => {
if (!data) return Date.now().toString();
if (Array.isArray(data)) {
return data.map((tx) => tx.tx_hash()).join(";");
return data.map((tx) => tx.hash).join(";");
}
return data.tx_hash();
return data.hash;
};
5 changes: 2 additions & 3 deletions apps/namadillo/src/hooks/useTransactionNotifications.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { BuiltTx } from "@heliax/namada-sdk/web";
import { Stack } from "@namada/components";
import { RedelegateMsgValue } from "@namada/types";
import { RedelegateMsgValue, TxProps } from "@namada/types";
import { shortenAddress } from "@namada/utils";
import { NamCurrency } from "App/Common/NamCurrency";
import {
Expand All @@ -22,7 +21,7 @@ const getTotalAmountFromTransactionList = (txs: TxWithAmount[]): BigNumber =>
}, new BigNumber(0));

const parseTxsData = <T extends TxWithAmount>(
tx: BuiltTx,
tx: TxProps,
data: T[]
): { id: string; total: BigNumber } => {
const id = createNotificationId(tx);
Expand Down
Loading
Loading