From a2fdefa57af31987fe0e2b42ffbebf103b03d70c Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Tue, 25 Jul 2023 10:52:35 -0400 Subject: [PATCH] Hook up unbond to Approvals and Ledger --- apps/extension/.prettierrc | 3 +- .../Approvals/ApproveTx/ConfirmLedgerTx.tsx | 6 +++ .../src/background/approvals/init.ts | 11 +++- .../src/background/approvals/messages.ts | 30 +++++++++++ .../src/background/approvals/service.ts | 41 ++++++++------ .../src/background/ledger/handler.ts | 16 +++++- .../src/background/ledger/messages.ts | 7 +-- .../src/background/ledger/service.ts | 39 ++++++++++++++ apps/extension/src/provider/InjectedNamada.ts | 50 +++++++++-------- apps/extension/src/provider/Namada.ts | 17 ++++-- apps/extension/src/provider/Signer.ts | 30 +++++++---- apps/extension/src/provider/messages.ts | 15 +++++- .../slices/StakingAndGovernance/actions.ts | 54 ++++++++++++------- packages/types/src/namada.ts | 17 +++--- packages/types/src/signer.ts | 4 +- 15 files changed, 238 insertions(+), 102 deletions(-) diff --git a/apps/extension/.prettierrc b/apps/extension/.prettierrc index 193626a11..f0eb61e0f 100644 --- a/apps/extension/.prettierrc +++ b/apps/extension/.prettierrc @@ -2,6 +2,5 @@ "trailingComma": "es5", "tabWidth": 2, "semi": true, - "singleQuote": false, - "bracketSpacing": false + "singleQuote": false } diff --git a/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx index 55c21f550..364172e18 100644 --- a/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx +++ b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx @@ -15,6 +15,7 @@ import { SubmitSignedBondMsg, SubmitSignedRevealPKMsg, SubmitSignedTransferMsg, + SubmitSignedUnbondMsg, } from "background/ledger/messages"; import { Ports } from "router"; import { closeCurrentTab } from "utils"; @@ -124,6 +125,11 @@ export const ConfirmLedgerTx: React.FC = ({ details }) => { Ports.Background, new SubmitSignedBondMsg(msgId, toBase64(bytes), signatures) ); + case TxType.Unbond: + return await requester.sendMessage( + Ports.Background, + new SubmitSignedUnbondMsg(msgId, toBase64(bytes), signatures) + ); case TxType.Transfer: return await requester.sendMessage( Ports.Background, diff --git a/apps/extension/src/background/approvals/init.ts b/apps/extension/src/background/approvals/init.ts index 60bc02c6a..2fbc98a2e 100644 --- a/apps/extension/src/background/approvals/init.ts +++ b/apps/extension/src/background/approvals/init.ts @@ -1,10 +1,16 @@ import { Router } from "router"; -import { ApproveBondMsg, ApproveTransferMsg } from "provider"; +import { + ApproveBondMsg, + ApproveTransferMsg, + ApproveUnbondMsg, + ApproveWithdrawMsg, +} from "provider"; import { RejectTxMsg, SubmitApprovedBondMsg, SubmitApprovedUnbondMsg, SubmitApprovedTransferMsg, + SubmitApprovedWithdrawMsg, } from "./messages"; import { ROUTE } from "./constants"; @@ -14,10 +20,13 @@ import { getHandler } from "./handler"; export function init(router: Router, service: ApprovalsService): void { router.registerMessage(ApproveBondMsg); router.registerMessage(ApproveTransferMsg); + router.registerMessage(ApproveUnbondMsg); + router.registerMessage(ApproveWithdrawMsg); router.registerMessage(RejectTxMsg); router.registerMessage(SubmitApprovedBondMsg); router.registerMessage(SubmitApprovedUnbondMsg); router.registerMessage(SubmitApprovedTransferMsg); + router.registerMessage(SubmitApprovedWithdrawMsg); router.addHandler(ROUTE, getHandler(service)); } diff --git a/apps/extension/src/background/approvals/messages.ts b/apps/extension/src/background/approvals/messages.ts index 0f7e359a5..fa142a43c 100644 --- a/apps/extension/src/background/approvals/messages.ts +++ b/apps/extension/src/background/approvals/messages.ts @@ -6,6 +6,7 @@ enum MessageType { SubmitApprovedTransfer = "submit-approved-transfer", SubmitApprovedBond = "submit-approved-bond", SubmitApprovedUnbond = "submit-approved-unbond", + SubmitApprovedWithdraw = "submit-approved-withdraw", } export class RejectTxMsg extends Message { @@ -121,3 +122,32 @@ export class SubmitApprovedUnbondMsg extends Message { return SubmitApprovedUnbondMsg.type(); } } + +export class SubmitApprovedWithdrawMsg extends Message { + public static type(): MessageType { + return MessageType.SubmitApprovedWithdraw; + } + + constructor(public readonly msgId: string, public readonly password: string) { + super(); + } + + validate(): void { + if (!this.msgId) { + throw new Error("msgId must not be empty!"); + } + if (!this.password) { + throw new Error("Password is required to submit unbond tx!"); + } + + return; + } + + route(): string { + return ROUTE; + } + + type(): string { + return SubmitApprovedWithdrawMsg.type(); + } +} diff --git a/apps/extension/src/background/approvals/service.ts b/apps/extension/src/background/approvals/service.ts index 00ac2d347..d3df98425 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -1,16 +1,21 @@ import browser from "webextension-polyfill"; -import {fromBase64} from "@cosmjs/encoding"; -import {v4 as uuid} from "uuid"; +import { fromBase64 } from "@cosmjs/encoding"; +import { v4 as uuid } from "uuid"; import BigNumber from "bignumber.js"; -import {deserialize} from "@dao-xyz/borsh"; +import { deserialize } from "@dao-xyz/borsh"; -import {AccountType, SubmitBondMsgValue, TransferMsgValue} from "@namada/types"; -import {TxType} from "@namada/shared"; -import {KVStore} from "@namada/storage"; +import { + AccountType, + SubmitBondMsgValue, + SubmitUnbondMsgValue, + TransferMsgValue, +} from "@namada/types"; +import { TxType } from "@namada/shared"; +import { KVStore } from "@namada/storage"; -import {KeyRingService, TabStore} from "background/keyring"; -import {LedgerService} from "background/ledger"; -import {paramsToUrl} from "@namada/utils"; +import { KeyRingService, TabStore } from "background/keyring"; +import { LedgerService } from "background/ledger"; +import { paramsToUrl } from "@namada/utils"; export class ApprovalsService { constructor( @@ -33,7 +38,7 @@ export class ApprovalsService { target, token, amount: amountBN, - tx: {publicKey = ""}, + tx: { publicKey = "" }, } = txDetails; const amount = new BigNumber(amountBN.toString()); const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ @@ -66,7 +71,7 @@ export class ApprovalsService { source, nativeToken: token, amount: amountBN, - tx: {publicKey = ""}, + tx: { publicKey = "" }, } = txDetails; const amount = new BigNumber(amountBN.toString()); const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ @@ -85,16 +90,20 @@ export class ApprovalsService { this._launchApprovalWindow(url); } - // Deserialize bond details and prompt user - async approveUnbond(txMsg: string, type?: AccountType): Promise { + // Deserialize unbond details and prompt user + async approveUnbond(txMsg: string, type: AccountType): Promise { const txMsgBuffer = Buffer.from(fromBase64(txMsg)); const id = uuid(); await this.txStore.set(id, txMsg); // Decode tx details and launch approval screen - const txDetails = deserialize(txMsgBuffer, SubmitBondMsgValue); + const txDetails = deserialize(txMsgBuffer, SubmitUnbondMsgValue); - const {source, nativeToken, amount: amountBN} = txDetails; + const { + source, + amount: amountBN, + tx: { publicKey = "" }, + } = txDetails; const amount = new BigNumber(amountBN.toString()); const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ TxType.Unbond @@ -103,9 +112,9 @@ export class ApprovalsService { const url = paramsToUrl(baseUrl, { id, source, - token: nativeToken, amount: amount.toString(), accountType: type as string, + publicKey, }); this._launchApprovalWindow(url); diff --git a/apps/extension/src/background/ledger/handler.ts b/apps/extension/src/background/ledger/handler.ts index b03793873..30f19082a 100644 --- a/apps/extension/src/background/ledger/handler.ts +++ b/apps/extension/src/background/ledger/handler.ts @@ -7,6 +7,7 @@ import { SubmitSignedBondMsg, SubmitSignedRevealPKMsg, GetRevealPKBytesMsg, + SubmitSignedUnbondMsg, } from "./messages"; export const getHandler: (service: LedgerService) => Handler = (service) => { @@ -37,7 +38,11 @@ export const getHandler: (service: LedgerService) => Handler = (service) => { env, msg as SubmitSignedBondMsg ); - + case SubmitSignedUnbondMsg: + return handleSubmitSignedUnbondMsg(service)( + env, + msg as SubmitSignedBondMsg + ); default: throw new Error("Unknown msg type"); } @@ -71,6 +76,15 @@ const handleSubmitSignedBondMsg: ( }; }; +const handleSubmitSignedUnbondMsg: ( + service: LedgerService +) => InternalHandler = (service) => { + return async (_, msg) => { + const { bytes, msgId, signatures } = msg; + return await service.submitUnbond(msgId, bytes, signatures); + }; +}; +``; const handleGetTxBytesMsg: ( service: LedgerService ) => InternalHandler = (service) => { diff --git a/apps/extension/src/background/ledger/messages.ts b/apps/extension/src/background/ledger/messages.ts index c0427fabe..00e6d6c69 100644 --- a/apps/extension/src/background/ledger/messages.ts +++ b/apps/extension/src/background/ledger/messages.ts @@ -241,8 +241,7 @@ export class SubmitSignedUnbondMsg extends Message { constructor( public readonly msgId: string, public readonly bytes: string, - public readonly signatures: ResponseSign, - public readonly publicKey: string + public readonly signatures: ResponseSign ) { super(); } @@ -259,10 +258,6 @@ export class SubmitSignedUnbondMsg extends Message { if (!this.signatures) { throw new Error("No signatures were provided!"); } - - if (!this.publicKey) { - throw new Error("No publicKey provided!"); - } } route(): string { diff --git a/apps/extension/src/background/ledger/service.ts b/apps/extension/src/background/ledger/service.ts index d16cc431b..bb19930e9 100644 --- a/apps/extension/src/background/ledger/service.ts +++ b/apps/extension/src/background/ledger/service.ts @@ -5,6 +5,7 @@ import { AccountType, Bip44Path, SubmitBondMsgValue, + SubmitUnbondMsgValue, TransferMsgValue, } from "@namada/types"; import { ResponseSign } from "@namada/ledger-namada"; @@ -233,6 +234,44 @@ export class LedgerService { await this.keyring.broadcastUpdateBalance(); } + /* Submit a bond with provided signatures */ + async submitUnbond( + msgId: string, + bytes: string, + signatures: ResponseSign + ): Promise { + const txMsg = await this.txStore.get(msgId); + + if (!txMsg) { + throw new Error(`Bond Transaction ${msgId} not found!`); + } + + const { tx } = deserialize(fromBase64(txMsg), SubmitUnbondMsgValue); + const encodedTx = encodeTx(tx); + + const { rawSignature, wrapperSignature } = signatures; + + try { + const rawSig = encodeSignature(rawSignature); + const wrapperSig = encodeSignature(wrapperSignature); + await this.sdk.submit_signed_tx( + encodedTx, + fromBase64(bytes), + rawSig, + wrapperSig + ); + + await this.broadcastUpdateStaking(); + + // Clear pending tx if successful + await this.txStore.set(msgId, null); + } catch (e) { + console.warn(e); + } + + await this.keyring.broadcastUpdateBalance(); + } + /** * Append a new address record for use with Ledger */ diff --git a/apps/extension/src/provider/InjectedNamada.ts b/apps/extension/src/provider/InjectedNamada.ts index b566377a0..f2fc5b07c 100644 --- a/apps/extension/src/provider/InjectedNamada.ts +++ b/apps/extension/src/provider/InjectedNamada.ts @@ -4,12 +4,13 @@ import { DerivedAccount, Namada as INamada, Signer as ISigner, + TxMsgProps, } from "@namada/types"; -import { InjectedProxy } from "./InjectedProxy"; -import { Signer } from "./Signer"; +import {InjectedProxy} from "./InjectedProxy"; +import {Signer} from "./Signer"; export class InjectedNamada implements INamada { - constructor(private readonly _version: string) { } + constructor(private readonly _version: string) {} public async connect(chainId: string): Promise { return await InjectedProxy.requestMethod("connect", chainId); @@ -39,10 +40,10 @@ export class InjectedNamada implements INamada { public async balances( owner: string - ): Promise<{ token: string; amount: string }[]> { + ): Promise<{token: string; amount: string}[]> { return await InjectedProxy.requestMethod< string, - { token: string; amount: string }[] + {token: string; amount: string}[] >("balances", owner); } @@ -50,43 +51,40 @@ export class InjectedNamada implements INamada { return new Signer(chainId, this); } - public async submitBond(props: { - txMsg: string; - type: AccountType; - publicKey?: string; - }): Promise { - const { txMsg, type, publicKey } = props; + public async submitBond(props: TxMsgProps): Promise { + const {txMsg, type} = props; return await InjectedProxy.requestMethod< - { txMsg: string; type: AccountType; publicKey?: string }, + {txMsg: string; type: AccountType}, void >("submitBond", { txMsg, type, - publicKey, }); } - public async submitUnbond(txMsg: string): Promise { - return await InjectedProxy.requestMethod( - "submitUnbond", - txMsg - ); + public async submitUnbond(props: TxMsgProps): Promise { + const {txMsg, type} = props; + return await InjectedProxy.requestMethod< + {txMsg: string; type: AccountType}, + void + >("submitUnbond", {txMsg, type}); } - public async submitWithdraw(txMsg: string): Promise { - return await InjectedProxy.requestMethod( - "submitWithdraw", - txMsg - ); + public async submitWithdraw(props: TxMsgProps): Promise { + const {txMsg, type} = props; + return await InjectedProxy.requestMethod< + {txMsg: string; type: AccountType}, + void + >("submitWithdraw", {txMsg, type}); } public async submitTransfer(props: { txMsg: string; type: AccountType; }): Promise { - const { txMsg, type } = props; + const {txMsg, type} = props; return await InjectedProxy.requestMethod< - { txMsg: string; type: AccountType }, + {txMsg: string; type: AccountType}, void >("submitTransfer", { txMsg, @@ -106,7 +104,7 @@ export class InjectedNamada implements INamada { address: string; }): Promise { return await InjectedProxy.requestMethod< - { txMsg: string; address: string }, + {txMsg: string; address: string}, string >("encodeInitAccount", props); } diff --git a/apps/extension/src/provider/Namada.ts b/apps/extension/src/provider/Namada.ts index 123c5dc7b..d57b93eac 100644 --- a/apps/extension/src/provider/Namada.ts +++ b/apps/extension/src/provider/Namada.ts @@ -92,7 +92,6 @@ export class Namada implements INamada { public async submitBond(props: { txMsg: string; type: AccountType; - publicKey?: string; }): Promise { const { txMsg, type } = props; return await this.requester?.sendMessage( @@ -101,17 +100,25 @@ export class Namada implements INamada { ); } - public async submitUnbond(txMsg: string): Promise { + public async submitUnbond(props: { + txMsg: string; + type: AccountType; + }): Promise { + const { txMsg, type } = props; return await this.requester?.sendMessage( Ports.Background, - new ApproveUnbondMsg(txMsg) + new ApproveUnbondMsg(txMsg, type) ); } - public async submitWithdraw(txMsg: string): Promise { + public async submitWithdraw(props: { + txMsg: string; + type: AccountType; + }): Promise { + const { txMsg, type } = props; return await this.requester?.sendMessage( Ports.Background, - new ApproveWithdrawMsg(txMsg) + new ApproveWithdrawMsg(txMsg, type) ); } diff --git a/apps/extension/src/provider/Signer.ts b/apps/extension/src/provider/Signer.ts index 95875d944..fb642ef4e 100644 --- a/apps/extension/src/provider/Signer.ts +++ b/apps/extension/src/provider/Signer.ts @@ -16,12 +16,13 @@ import { SubmitUnbondMsgValue, SubmitWithdrawMsgValue, } from "@namada/types"; +import { ApproveWithdrawMsg } from "./messages"; export class Signer implements ISigner { constructor( protected readonly chainId: string, private readonly _namada: Namada - ) { } + ) {} public async accounts(): Promise { return (await this._namada.accounts(this.chainId))?.map( @@ -41,8 +42,7 @@ export class Signer implements ISigner { */ public async submitBond( args: SubmitBondProps, - type: AccountType, - publicKey?: string + type: AccountType ): Promise { const msgValue = new SubmitBondMsgValue(args); const msg = new Message(); @@ -51,32 +51,38 @@ export class Signer implements ISigner { return await this._namada.submitBond({ txMsg: toBase64(encoded), type, - publicKey, }); } /** * Submit unbond transaction */ - public async submitUnbond(args: SubmitBondProps): Promise { + public async submitUnbond( + args: SubmitBondProps, + type: AccountType + ): Promise { const msgValue = new SubmitUnbondMsgValue(args); - const msg = new Message(); const encoded = msg.encode(msgValue); - return await this._namada.submitUnbond(toBase64(encoded)); + return await this._namada.submitUnbond({ txMsg: toBase64(encoded), type }); } /** * Submit withdraw transaction */ - public async submitWithdraw(args: SubmitBondProps): Promise { + public async submitWithdraw( + args: SubmitBondProps, + type: AccountType + ): Promise { const msgValue = new SubmitWithdrawMsgValue(args); - const msg = new Message(); const encoded = msg.encode(msgValue); - return await this._namada.submitWithdraw(toBase64(encoded)); + return await this._namada.submitWithdraw({ + txMsg: toBase64(encoded), + type, + }); } /** @@ -105,7 +111,9 @@ export class Signer implements ISigner { const serializedIbcTransfer = ibcTransferMessage.encode(ibcTransferMsgValue); - return await this._namada.submitIbcTransfer(toBase64(serializedIbcTransfer)); + return await this._namada.submitIbcTransfer( + toBase64(serializedIbcTransfer) + ); } /** diff --git a/apps/extension/src/provider/messages.ts b/apps/extension/src/provider/messages.ts index 2e7fd9cea..9fccdcafe 100644 --- a/apps/extension/src/provider/messages.ts +++ b/apps/extension/src/provider/messages.ts @@ -301,7 +301,7 @@ export class ApproveUnbondMsg extends Message { constructor( public readonly txMsg: string, - public readonly accountType?: AccountType + public readonly accountType: AccountType ) { super(); } @@ -310,6 +310,10 @@ export class ApproveUnbondMsg extends Message { if (!this.txMsg) { throw new Error("txMsg was not provided!"); } + if (!this.accountType) { + throw new Error("accountType was not provided!"); + } + return; } @@ -327,7 +331,10 @@ export class ApproveWithdrawMsg extends Message { return MessageType.ApproveWithdraw; } - constructor(public readonly txMsg: string) { + constructor( + public readonly txMsg: string, + public readonly accountType: AccountType + ) { super(); } @@ -335,6 +342,10 @@ export class ApproveWithdrawMsg extends Message { if (!this.txMsg) { throw new Error("An encoded txMsg is required!"); } + if (!this.accountType) { + throw new Error("accountType was not provided!"); + } + return; } diff --git a/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts b/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts index e597d5294..6cb6493db 100644 --- a/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts +++ b/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts @@ -269,20 +269,29 @@ export const postNewUnbonding = createAsyncThunk< { state: RootState } >(POST_UNSTAKING, async (change, thunkApi) => { const { chainId } = thunkApi.getState().settings; + const { derived } = thunkApi.getState().accounts; const integration = getIntegration(chainId); const signer = integration.signer() as Signer; + const { owner } = change; + const { + details: { type, publicKey }, + } = derived[chainId][owner]; - await signer.submitUnbond({ - source: change.owner, - validator: change.validatorId, - amount: change.amount, - tx: { - token: Tokens.NAM.address || "", - feeAmount: new BigNumber(0), - gasLimit: new BigNumber(0), - chainId, + await signer.submitUnbond( + { + source: change.owner, + validator: change.validatorId, + amount: change.amount, + tx: { + token: Tokens.NAM.address || "", + feeAmount: new BigNumber(0), + gasLimit: new BigNumber(0), + chainId, + publicKey, + }, }, - }); + type + ); }); export const postNewWithdraw = createAsyncThunk< @@ -291,17 +300,24 @@ export const postNewWithdraw = createAsyncThunk< { state: RootState } >(POST_UNSTAKING, async ({ owner, validatorId }, thunkApi) => { const { chainId } = thunkApi.getState().settings; + const { derived } = thunkApi.getState().accounts; const integration = getIntegration(chainId); const signer = integration.signer() as Signer; + const { + details: { type }, + } = derived[chainId][owner]; - await signer.submitWithdraw({ - source: owner, - validator: validatorId, - tx: { - token: Tokens.NAM.address || "", - feeAmount: new BigNumber(0), - gasLimit: new BigNumber(0), - chainId, + await signer.submitWithdraw( + { + source: owner, + validator: validatorId, + tx: { + token: Tokens.NAM.address || "", + feeAmount: new BigNumber(0), + gasLimit: new BigNumber(0), + chainId, + }, }, - }); + type + ); }); diff --git a/packages/types/src/namada.ts b/packages/types/src/namada.ts index a4385bccd..7f9365a20 100644 --- a/packages/types/src/namada.ts +++ b/packages/types/src/namada.ts @@ -2,6 +2,8 @@ import { AccountType, DerivedAccount } from "./account"; import { Chain } from "./chain"; import { Signer } from "./signer"; +export type TxMsgProps = { txMsg: string; type: AccountType }; + export interface Namada { connect(chainId: string): Promise; accounts(chainId: string): Promise; @@ -11,17 +13,10 @@ export interface Namada { suggestChain(chainConfig: Chain): Promise; chain: (chainId: string) => Promise; chains: () => Promise; - submitBond: (props: { - txMsg: string; - type: AccountType; - publicKey?: string; - }) => Promise; - submitUnbond: (txMsg: string) => Promise; - submitWithdraw: (txMsg: string) => Promise; - submitTransfer: (props: { - txMsg: string; - type: AccountType; - }) => Promise; + submitBond: (props: TxMsgProps) => Promise; + submitUnbond: (props: TxMsgProps) => Promise; + submitWithdraw: (props: TxMsgProps) => Promise; + submitTransfer: (props: TxMsgProps) => Promise; submitIbcTransfer: (txMsg: string) => Promise; encodeInitAccount: (props: { txMsg: string; diff --git a/packages/types/src/signer.ts b/packages/types/src/signer.ts index b6c86fb4d..0ce0afb61 100644 --- a/packages/types/src/signer.ts +++ b/packages/types/src/signer.ts @@ -11,8 +11,8 @@ import { export interface Signer { accounts: () => Promise; submitBond(args: SubmitBondProps, type: AccountType): Promise; - submitUnbond(args: SubmitUnbondProps): Promise; - submitWithdraw(args: SubmitWithdrawProps): Promise; + submitUnbond(args: SubmitUnbondProps, type: AccountType): Promise; + submitWithdraw(args: SubmitWithdrawProps, type: AccountType): Promise; submitTransfer(args: TransferProps, type: AccountType): Promise; submitIbcTransfer(args: IbcTransferProps): Promise; encodeInitAccount(