Skip to content

Commit

Permalink
Merge pull request #718 from xmtp/rygine/update-signing-api
Browse files Browse the repository at this point in the history
Refactor signing in browser and node SDKs
  • Loading branch information
rygine authored Nov 15, 2024
2 parents aed240f + a1a16a0 commit 6418271
Show file tree
Hide file tree
Showing 19 changed files with 464 additions and 305 deletions.
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
"ignore": ["@xmtp/react-vite-browser-sdk-example"]
}
6 changes: 6 additions & 0 deletions .changeset/lovely-swans-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@xmtp/browser-sdk": patch
"@xmtp/node-sdk": patch
---

Refactor signing in browser and node SDKs
34 changes: 11 additions & 23 deletions examples/react-vite-browser-sdk/src/createClient.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
import { Client, SignatureRequestType } from "@xmtp/browser-sdk";
import { Client, type Signer } from "@xmtp/browser-sdk";
import { toBytes } from "viem/utils";
import { createWallet } from "./wallets";

type Wallet = ReturnType<typeof createWallet>;

export const getSignature = async (client: Client, wallet: Wallet) => {
const signatureText = await client.getCreateInboxSignatureText();
if (signatureText) {
const signature = await wallet.signMessage({
message: signatureText,
});
return toBytes(signature);
}
return null;
};

export const createClient = async (walletKey: string) => {
const encryptionKeyHex = import.meta.env.VITE_ENCRYPTION_KEY;
if (!encryptionKeyHex) {
throw new Error("VITE_ENCRYPTION_KEY must be set in the environment");
}
const encryptionBytes = toBytes(encryptionKeyHex);
const wallet = createWallet(walletKey);
const client = await Client.create(wallet.account.address, encryptionBytes, {
const signer: Signer = {
getAddress: () => wallet.account.address,
signMessage: async (message: string) => {
const signature = await wallet.signMessage({
message,
});
return toBytes(signature);
},
};
const client = await Client.create(signer, encryptionBytes, {
env: "local",
});
const isRegistered = await client.isRegistered();
if (!isRegistered) {
const signature = await getSignature(client, wallet);
if (signature) {
await client.addSignature(SignatureRequestType.CreateInbox, signature);
}
await client.registerIdentity();
}
return client;
};
191 changes: 146 additions & 45 deletions sdks/browser-sdk/src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,34 @@ import type {
import { TextCodec } from "@xmtp/content-type-text";
import {
GroupMessageKind,
SignatureRequestType,
type ConsentEntityType,
type SignatureRequestType,
} from "@xmtp/wasm-bindings";
import { ClientWorkerClass } from "@/ClientWorkerClass";
import { Conversations } from "@/Conversations";
import type { ClientOptions } from "@/types";
import type { ClientOptions, XmtpEnv } from "@/types";
import {
fromSafeEncodedContent,
toSafeEncodedContent,
type SafeConsent,
type SafeMessage,
} from "@/utils/conversions";
import { isSmartContractSigner, type Signer } from "@/utils/signer";

export class Client extends ClientWorkerClass {
address: string;

options?: ClientOptions;

#isReady = false;

#inboxId: string | undefined;

#installationId: string | undefined;

#conversations: Conversations;

#accountAddress: string;
#codecs: Map<string, ContentCodec>;

#conversations: Conversations;
#encryptionKey: Uint8Array;
#inboxId: string | undefined;
#installationId: string | undefined;
#isReady = false;
#signer: Signer;
options?: ClientOptions;

constructor(
address: string,
signer: Signer,
accountAddress: string,
encryptionKey: Uint8Array,
options?: ClientOptions,
) {
Expand All @@ -51,9 +47,10 @@ export class Client extends ClientWorkerClass {
worker,
options?.loggingLevel !== undefined && options.loggingLevel !== "off",
);
this.address = address;
this.#accountAddress = accountAddress;
this.options = options;
this.#encryptionKey = encryptionKey;
this.#signer = signer;
this.#conversations = new Conversations(this);
const codecs = [
new GroupUpdatedCodec(),
Expand All @@ -65,9 +62,13 @@ export class Client extends ClientWorkerClass {
);
}

get accountAddress() {
return this.#accountAddress;
}

async init() {
const result = await this.sendMessage("init", {
address: this.address,
address: this.accountAddress,
encryptionKey: this.#encryptionKey,
options: this.options,
});
Expand All @@ -77,12 +78,19 @@ export class Client extends ClientWorkerClass {
}

static async create(
address: string,
signer: Signer,
encryptionKey: Uint8Array,
options?: ClientOptions,
) {
const client = new Client(address, encryptionKey, options);
const address = await signer.getAddress();
const client = new Client(signer, address, encryptionKey, options);

await client.init();

if (!options?.disableAutoRegister) {
await client.register();
}

return client;
}

Expand All @@ -98,48 +106,124 @@ export class Client extends ClientWorkerClass {
return this.#installationId;
}

async getCreateInboxSignatureText() {
return this.sendMessage("getCreateInboxSignatureText", undefined);
async #createInboxSignatureText() {
return this.sendMessage("createInboxSignatureText", undefined);
}

async getAddWalletSignatureText(accountAddress: string) {
return this.sendMessage("getAddWalletSignatureText", { accountAddress });
}

async getRevokeWalletSignatureText(accountAddress: string) {
return this.sendMessage("getRevokeWalletSignatureText", { accountAddress });
async #addAccountSignatureText(newAccountAddress: string) {
return this.sendMessage("addAccountSignatureText", {
newAccountAddress,
});
}

async getRevokeInstallationsSignatureText() {
return this.sendMessage("getRevokeInstallationsSignatureText", undefined);
async #removeAccountSignatureText(accountAddress: string) {
return this.sendMessage("removeAccountSignatureText", { accountAddress });
}

async addSignature(type: SignatureRequestType, bytes: Uint8Array) {
return this.sendMessage("addSignature", { type, bytes });
async #revokeInstallationsSignatureText() {
return this.sendMessage("revokeInstallationsSignatureText", undefined);
}

async addScwSignature(
type: SignatureRequestType,
bytes: Uint8Array,
chainId: bigint,
blockNumber?: bigint,
async #addSignature(
signatureType: SignatureRequestType,
signatureText: string,
signer: Signer,
) {
return this.sendMessage("addScwSignature", {
type,
bytes,
chainId,
blockNumber,
});
const signature = await signer.signMessage(signatureText);

if (isSmartContractSigner(signer)) {
await this.sendMessage("addScwSignature", {
type: signatureType,
bytes: signature,
chainId: signer.getChainId(),
blockNumber: signer.getBlockNumber(),
});
} else {
await this.sendMessage("addSignature", {
type: signatureType,
bytes: signature,
});
}
}

async applySignatures() {
async #applySignatures() {
return this.sendMessage("applySignatures", undefined);
}

async registerIdentity() {
async register() {
const signatureText = await this.#createInboxSignatureText();

// if the signature text is not available, the client is already registered
if (!signatureText) {
return;
}

await this.#addSignature(
SignatureRequestType.CreateInbox,
signatureText,
this.#signer,
);

return this.sendMessage("registerIdentity", undefined);
}

async addAccount(newAccountSigner: Signer) {
const signatureText = await this.#addAccountSignatureText(
await newAccountSigner.getAddress(),
);

if (!signatureText) {
throw new Error("Unable to generate add account signature text");
}

await this.#addSignature(
SignatureRequestType.AddWallet,
signatureText,
this.#signer,
);

await this.#addSignature(
SignatureRequestType.AddWallet,
signatureText,
newAccountSigner,
);

await this.#applySignatures();
}

async removeAccount(accountAddress: string) {
const signatureText =
await this.#removeAccountSignatureText(accountAddress);

if (!signatureText) {
throw new Error("Unable to generate remove account signature text");
}

await this.#addSignature(
SignatureRequestType.RevokeWallet,
signatureText,
this.#signer,
);

await this.#applySignatures();
}

async revokeInstallations() {
const signatureText = await this.#revokeInstallationsSignatureText();

if (!signatureText) {
throw new Error("Unable to generate revoke installations signature text");
}

await this.#addSignature(
SignatureRequestType.RevokeInstallations,
signatureText,
this.#signer,
);

await this.#applySignatures();
}

async isRegistered() {
return this.sendMessage("isRegistered", undefined);
}
Expand All @@ -148,6 +232,23 @@ export class Client extends ClientWorkerClass {
return this.sendMessage("canMessage", { accountAddresses });
}

static async canMessage(accountAddresses: string[], env?: XmtpEnv) {
const accountAddress = "0x0000000000000000000000000000000000000000";
const signer: Signer = {
getAddress: () => accountAddress,
signMessage: () => new Uint8Array(),
};
const client = await Client.create(
signer,
window.crypto.getRandomValues(new Uint8Array(32)),
{
disableAutoRegister: true,
env,
},
);
return client.canMessage(accountAddresses);
}

async findInboxIdByAddress(address: string) {
return this.sendMessage("findInboxIdByAddress", { address });
}
Expand Down
8 changes: 4 additions & 4 deletions sdks/browser-sdk/src/WorkerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ export class WorkerClient {
return this.#client.isRegistered;
}

async getCreateInboxSignatureText() {
async createInboxSignatureText() {
try {
return await this.#client.createInboxSignatureText();
} catch {
return undefined;
}
}

async getAddWalletSignatureText(accountAddress: string) {
async addAccountSignatureText(accountAddress: string) {
try {
return await this.#client.addWalletSignatureText(
this.#accountAddress,
Expand All @@ -65,15 +65,15 @@ export class WorkerClient {
}
}

async getRevokeWalletSignatureText(accountAddress: string) {
async removeAccountSignatureText(accountAddress: string) {
try {
return await this.#client.revokeWalletSignatureText(accountAddress);
} catch {
return undefined;
}
}

async getRevokeInstallationsSignatureText() {
async revokeInstallationsSignatureText() {
try {
return await this.#client.revokeInstallationsSignatureText();
} catch {
Expand Down
5 changes: 5 additions & 0 deletions sdks/browser-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ export {
Consent,
ContentTypeId,
} from "@xmtp/wasm-bindings";
export {
isSmartContractSigner,
type Signer,
type SmartContractSigner,
} from "./utils/signer";
Loading

0 comments on commit 6418271

Please sign in to comment.