Skip to content

Commit 862de2a

Browse files
authored
feat/1012: Improve Tx API in SDK (#1033)
* feat: add Tx schema and type for inspecting transactions from SDK * feat: continue hooking up new Tx type * fix: serialize and deserialize properly * feat: support all existing Tx * feat: improve signing & broadcast API * docs: regenerate docs for types & sdk
1 parent 4f090ea commit 862de2a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+1003
-1185
lines changed

apps/extension/src/Setup/Ledger/LedgerConfirmation.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const LedgerConfirmation = (): JSX.Element => {
2222
<ViewKeys
2323
publicKeyAddress={account.publicKey}
2424
transparentAccountAddress={account.address}
25+
trimCharacters={35}
2526
/>
2627
<ActionButton size="lg" onClick={closeCurrentTab}>
2728
Finish Setup

apps/extension/src/background/approvals/handler.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { WrapperTxMsgValue } from "@namada/types";
3+
import BigNumber from "bignumber.js";
24
import createMockInstance from "jest-create-mock-instance";
35
import {
46
ApproveConnectInterfaceMsg,
@@ -46,7 +48,19 @@ describe("approvals handler", () => {
4648
};
4749

4850
const approveTxMsg = new ApproveSignTxMsg(
49-
[{ txBytes: "", signingDataBytes: [""] }],
51+
[
52+
{
53+
args: new WrapperTxMsgValue({
54+
token: "",
55+
feeAmount: BigNumber(0),
56+
gasLimit: BigNumber(0),
57+
chainId: "",
58+
}),
59+
hash: "",
60+
bytes: "",
61+
signingData: [],
62+
},
63+
],
5064
"signer"
5165
);
5266

apps/extension/src/background/approvals/service.test.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { WrapperTxMsgValue } from "@namada/types";
23
import { paramsToUrl } from "@namada/utils";
34
import { ChainsService } from "background/chains";
45
import { KeyRingService } from "background/keyring";
56
import { SdkService } from "background/sdk";
67
import { VaultService } from "background/vault";
8+
import BigNumber from "bignumber.js";
79
import { ExtensionBroadcaster } from "extension";
810
import createMockInstance from "jest-create-mock-instance";
911
import { LocalStorage } from "storage";
@@ -210,16 +212,22 @@ describe("approvals service", () => {
210212
it("should reject resolver", async () => {
211213
const tabId = 1;
212214
const signer = "signer";
213-
// data expected to be base64-encoded
214-
const txBytes = "dHhEYXRh"; // "txData"
215-
const signingDataBytes = "c2lnbmluZ0RhdGE="; // "signingData"
215+
// tx bytes expected to be base64-encoded
216+
const bytes = "dHhEYXRh"; // "txData"
216217

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

219220
const signaturePromise = service.approveSignTx(signer, [
220221
{
221-
txBytes,
222-
signingDataBytes: [signingDataBytes],
222+
args: new WrapperTxMsgValue({
223+
token: "",
224+
feeAmount: BigNumber(0),
225+
gasLimit: BigNumber(0),
226+
chainId: "",
227+
}),
228+
hash: "",
229+
bytes,
230+
signingData: [],
223231
},
224232
]);
225233

apps/extension/src/background/approvals/service.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { fromBase64, toBase64 } from "@cosmjs/encoding";
1+
import { toBase64 } from "@cosmjs/encoding";
22
import { v4 as uuid } from "uuid";
33
import browser, { Windows } from "webextension-polyfill";
44

5-
import { BuiltTx } from "@heliax/namada-sdk/web";
65
import { KVStore } from "@namada/storage";
76
import { SignArbitraryResponse, TxDetails } from "@namada/types";
87
import { paramsToUrl } from "@namada/utils";
@@ -14,6 +13,7 @@ import { SdkService } from "background/sdk";
1413
import { VaultService } from "background/vault";
1514
import { ExtensionBroadcaster } from "extension";
1615
import { LocalStorage } from "storage";
16+
import { fromEncodedTx } from "utils";
1717
import { EncodedTxData, PendingTx } from "./types";
1818

1919
export class ApprovalsService {
@@ -50,12 +50,10 @@ export class ApprovalsService {
5050

5151
const pendingTx: PendingTx = {
5252
signer,
53-
txs: txs.map(({ txBytes, signingDataBytes }) => ({
54-
txBytes: fromBase64(txBytes),
55-
signingDataBytes: signingDataBytes.map((bytes) => fromBase64(bytes)),
56-
})),
53+
txs: txs.map((encodedTx) => fromEncodedTx(encodedTx)),
5754
checksums,
5855
};
56+
5957
await this.txStore.set(msgId, pendingTx);
6058

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

125-
const txs = pendingTx.txs.map(({ txBytes, signingDataBytes }) => {
126-
return new BuiltTx(
127-
txBytes,
128-
signingDataBytes.map((sdBytes) => [...sdBytes])
129-
);
130-
});
131-
132123
try {
133124
const signedBytes: Uint8Array[] = [];
134-
for await (const tx of txs) {
125+
for await (const tx of pendingTx.txs) {
135126
signedBytes.push(await this.keyRingService.sign(tx, signer));
136127
}
137128
resolvers.resolve(signedBytes);
@@ -165,8 +156,8 @@ export class ApprovalsService {
165156
const { tx } = this.sdkService.getSdk();
166157

167158
try {
168-
const signedTxs = pendingTx.txs.map(({ txBytes }, i) => {
169-
return tx.appendSignature(txBytes, responseSign[i]);
159+
const signedTxs = pendingTx.txs.map(({ bytes }, i) => {
160+
return tx.appendSignature(bytes, responseSign[i]);
170161
});
171162
resolvers.resolve(signedTxs);
172163
} catch (e) {
@@ -303,8 +294,8 @@ export class ApprovalsService {
303294
}
304295

305296
const { tx } = this.sdkService.getSdk();
306-
return pendingTx.txs.map(({ txBytes }) =>
307-
tx.deserialize(txBytes, pendingTx.checksums || {})
297+
return pendingTx.txs.map(({ bytes }) =>
298+
tx.deserialize(bytes, pendingTx.checksums || {})
308299
);
309300
}
310301

@@ -316,7 +307,7 @@ export class ApprovalsService {
316307
}
317308

318309
if (pendingTx.txs) {
319-
return pendingTx.txs.map(({ txBytes }) => toBase64(txBytes));
310+
return pendingTx.txs.map(({ bytes }) => toBase64(bytes));
320311
}
321312
}
322313

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1-
import { TxData } from "@namada/types";
1+
import { SigningDataProps, TxProps } from "@namada/types";
22

33
export type ApprovedOriginsStore = string[];
44

55
export type PendingTx = {
6-
txs: TxData[];
6+
txs: TxProps[];
77
signer: string;
88
checksums?: Record<string, string>;
99
};
1010

1111
export type PendingSignArbitrary = string;
1212

13-
// base64 encoded Tx data for use with postMessage
14-
export type EncodedTxData = {
15-
txBytes: string;
16-
signingDataBytes: string[];
13+
// base64 encoded Uint8Arrays for use with postMessage
14+
export type EncodedSigningData = Pick<
15+
SigningDataProps,
16+
"publicKeys" | "threshold" | "feePayer" | "owner"
17+
> & {
18+
accountPublicKeysMap?: string;
19+
};
20+
21+
export type EncodedTxData = Pick<TxProps, "args" | "hash"> & {
22+
bytes: string;
23+
signingData: EncodedSigningData[];
1724
};

apps/extension/src/background/keyring/keyring.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Bip44Path,
66
DerivedAccount,
77
SignArbitraryResponse,
8+
TxProps,
89
} from "@namada/types";
910
import { Result, assertNever, truncateInMiddle } from "@namada/utils";
1011

@@ -19,7 +20,6 @@ import {
1920
UtilityStore,
2021
} from "./types";
2122

22-
import { BuiltTx } from "@namada/shared";
2323
import { SdkService } from "background/sdk";
2424
import { VaultService } from "background/vault";
2525
import { KeyStore, KeyStoreType, SensitiveType, VaultStorage } from "storage";
@@ -544,14 +544,14 @@ export class KeyRing {
544544
}
545545

546546
async sign(
547-
builtTx: BuiltTx,
547+
txProps: TxProps,
548548
signer: string,
549549
chainId: string
550550
): Promise<Uint8Array> {
551551
await this.vaultService.assertIsUnlocked();
552552
const key = await this.getSigningKey(signer);
553553
const { signing } = this.sdkService.getSdk();
554-
return await signing.sign(builtTx, key, chainId);
554+
return await signing.sign(txProps, key, chainId);
555555
}
556556

557557
async signArbitrary(

apps/extension/src/background/keyring/service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import {
55
Bip44Path,
66
DerivedAccount,
77
SignArbitraryResponse,
8+
TxProps,
89
} from "@namada/types";
910
import { Result, truncateInMiddle } from "@namada/utils";
1011

11-
import { BuiltTx } from "@namada/shared";
1212
import { ChainsService } from "background/chains";
1313
import { SdkService } from "background/sdk/service";
1414
import { VaultService } from "background/vault";
@@ -167,9 +167,9 @@ export class KeyRingService {
167167
return await IndexedDBKVStore.durabilityCheck();
168168
}
169169

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

175175
async signArbitrary(

apps/extension/src/provider/Namada.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { toBase64 } from "@cosmjs/encoding";
21
import {
32
Chain,
43
DerivedAccount,
@@ -10,6 +9,7 @@ import {
109
} from "@namada/types";
1110
import { MessageRequester, Ports } from "router";
1211

12+
import { toEncodedTx } from "utils";
1313
import {
1414
ApproveConnectInterfaceMsg,
1515
ApproveSignArbitraryMsg,
@@ -65,10 +65,8 @@ export class Namada implements INamada {
6565
return await this.requester?.sendMessage(
6666
Ports.Background,
6767
new ApproveSignTxMsg(
68-
txs.map(({ txBytes, signingDataBytes }) => ({
69-
txBytes: toBase64(txBytes),
70-
signingDataBytes: signingDataBytes.map((bytes) => toBase64(bytes)),
71-
})),
68+
// Encode all transactions for use with postMessage
69+
txs.map((txProps) => toEncodedTx(txProps)),
7270
signer,
7371
checksums
7472
)

apps/extension/src/provider/Signer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
Signer as ISigner,
66
Namada,
77
SignArbitraryResponse,
8-
TxData,
8+
TxProps,
99
} from "@namada/types";
1010

1111
export class Signer implements ISigner {
@@ -44,7 +44,7 @@ export class Signer implements ISigner {
4444
}
4545

4646
public async sign(
47-
tx: TxData | TxData[],
47+
tx: TxProps | TxProps[],
4848
signer: string,
4949
checksums?: Record<string, string>
5050
): Promise<Uint8Array[] | undefined> {

apps/extension/src/utils/index.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { fromBase64, toBase64 } from "@cosmjs/encoding";
2+
import { TxProps } from "@namada/types";
13
import { v5 as uuid } from "uuid";
24
import browser from "webextension-polyfill";
35

46
import { Result } from "@namada/utils";
7+
import { EncodedTxData } from "background/approvals";
58

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

6164
// Remove prefix from private key, which may be present when exporting keys from CLI
6265
export const filterPrivateKeyPrefix = (privateKey: string): string =>
6366
privateKey.length === PRIVATE_KEY_MAX_LENGTH + 2 ?
6467
privateKey.replace(/^00/, "")
65-
: privateKey;
68+
: privateKey;
69+
70+
// Convert any Uint8Arrays in TxProps to string, and construct EncodedTxData
71+
export const toEncodedTx = (txProps: TxProps): EncodedTxData => ({
72+
...txProps,
73+
bytes: toBase64(txProps.bytes),
74+
signingData: txProps.signingData.map((sd) => ({
75+
...sd,
76+
accountPublicKeysMap:
77+
sd.accountPublicKeysMap ? toBase64(sd.accountPublicKeysMap) : undefined,
78+
})),
79+
});
80+
81+
// Convert base64 strings back to Uint8Arrays in EncodedTxData to restore TxProps
82+
export const fromEncodedTx = (encodedTxData: EncodedTxData): TxProps => ({
83+
...encodedTxData,
84+
bytes: fromBase64(encodedTxData.bytes),
85+
signingData: encodedTxData.signingData.map((sd) => ({
86+
...sd,
87+
accountPublicKeysMap:
88+
sd.accountPublicKeysMap ? fromBase64(sd.accountPublicKeysMap) : undefined,
89+
})),
90+
});

0 commit comments

Comments
 (0)