From 77167fdc32c2f0791b9510419468d6e3b16e2775 Mon Sep 17 00:00:00 2001 From: Zetazzz Date: Sun, 1 Dec 2024 14:36:47 +0800 Subject: [PATCH 1/5] add tutorial draft and update types --- docs/tutorial-draft.md | 195 ++++++++++++++++++ networks/cosmos/src/base/base-wallet.ts | 6 +- networks/cosmos/src/types/signer.ts | 1 - networks/cosmos/src/types/wallet.ts | 10 +- networks/cosmos/src/wallets/secp256k1hd.ts | 2 +- networks/injective/src/signing-client.ts | 2 +- .../injective/src/wallets/ethSecp256k1hd.ts | 2 +- packages/types/src/signer.ts | 6 +- 8 files changed, 210 insertions(+), 14 deletions(-) create mode 100644 docs/tutorial-draft.md diff --git a/docs/tutorial-draft.md b/docs/tutorial-draft.md new file mode 100644 index 00000000..1ea16e0e --- /dev/null +++ b/docs/tutorial-draft.md @@ -0,0 +1,195 @@ +# Tutorial + +## Implement Signers + +### Cosmos Signers + +A tutorial for creating cosmos signers: + +For cosmos signers, several base classes are provided make it easier to build the signing process. + +Here's steps: + +1. extend the signer type based on UniSigner. + 1.1. figuring out the types of the following in the signing process: + + - @template SignArgs - arguments for sign method + - @template Tx - transaction type + - @template Doc - sign doc type + - @template AddressResponse - address type + - @template BroadcastResponse - response type after broadcasting a transaction + - @template BroadcastOpts - options for broadcasting a transaction + - @template SignDocResp - response type after signing a document + + e.g. In cosmos amino signing process: + + ```ts + SignArgs = CosmosSignArgs = { + messages: Message[]; + fee?: StdFee; + memo?: string; + options?: Option; + } + + Tx = TxRaw //cosmos.tx.v1beta1.TxRaw + + Doc = StdSignDoc + + AddressResponse = string + + BroadcastResponse = { hash: string } + ``` + + 1.2. Then we have a CosmosAminoSigner interface: + + ```ts + export type CosmosAminoSigner = UniSigner< + CosmosSignArgs, + TxRaw, + StdSignDoc, + string, + BroadcastResponse + >; + ``` + +2. Before implementing the CosmosAminoSigner, it's important to decide between using ByteAuth or DocAuth or both for handling signatures. + + #### ByteAuth + + ByteAuth offers flexibility by allowing the signing of arbitrary byte arrays using algorithms like secp256k1 or eth_secp256k1, making it suitable for low-level or protocol-agnostic use cases. + + Implement the ByteAuth interface: + + ```ts + export class Secp256k1Auth implements ByteAuth { + ``` + + #### DocAuth + + DocAuth is tailored for signing structured documents like AminoSignDoc, providing a streamlined workflow for blockchain transactions with offline signers, while also ensuring compatibility with the Cosmos SDK's predefined document formats. + + ##### generic offline signer + + A generic offline signer is needed for wrapping an offline signer up to make a standard interface for offline signers: + + ```ts + export interface IAminoGenericOfflineSigner + extends IGenericOfflineSigner< + string, + CosmosAminoDoc, + AminoSignResponse, + IAminoGenericOfflineSignArgs, + AccountData + > {} + + export class AminoGenericOfflineSigner + implements IAminoGenericOfflineSigner + { + constructor(public offlineSigner: OfflineAminoSigner) {} + + readonly signMode: string = SIGN_MODE.AMINO; + getAccounts(): Promise { + return this.offlineSigner.getAccounts(); + } + sign({ signerAddress, signDoc }: IAminoGenericOfflineSignArgs) { + return this.offlineSigner.signAmino(signerAddress, signDoc); + } + } + ``` + + For details of ByteAuth and DocAuth, please see [here](/docs/auth.md#ByteAuth). + +3. For building signed docs or signed transactions, we also need to implement the tx builder: + + 3.1. Extends the BaseCosmosTxBuilder and set the sign mode. + 3.2. Implement the way building docs + 3.3. Implement the way serializing docs + 3.4. Implement the way syncing info from signed docs + + ```ts + export class AminoTxBuilder extends BaseCosmosTxBuilder { + constructor( + protected ctx: BaseCosmosTxBuilderContext< + AminoSignerBase + > + ) { + // Extends the BaseCosmosTxBuilder and set the sign mode. + super(SignMode.SIGN_MODE_LEGACY_AMINO_JSON, ctx); + } + + // Implement the way building docs + async buildDoc({ + messages, + fee, + memo, + options, + }: CosmosSignArgs): Promise {} + + // Implement the way serializing docs + async buildDocBytes(doc: CosmosAminoDoc): Promise {} + + // Implement the way syncing info from signed docs + async syncSignedDoc( + txRaw: TxRaw, + signResp: SignDocResponse + ): Promise {} + } + ``` + +4. Then we implement the AminoSigner. A signer's initiated by an Auth or offline signer. If an offlin signer is supported, a static method of fromWallet should be implemented to convert an offline signer to a DocAuth. + + ```ts + export class AminoSigner + extends AminoSignerBase + implements CosmosAminoSigner + { + // a signer will be initiated by an Auth, ByteAuth or DocAuth + constructor( + auth: Auth, + encoders: Encoder[], + converters: AminoConverter[], + endpoint?: string | HttpEndpoint, + options?: SignerOptions + ) { + super(auth, encoders, converters, endpoint, options); + } + + // Implement the way a signer getting tx builder to build signing process. + getTxBuilder(): BaseCosmosTxBuilder { + return new AminoTxBuilder(new BaseCosmosTxBuilderContext(this)); + } + + /** + * get account + */ + async getAccount() {} + + /** + * create AminoSigner from wallet. + * if there're multiple accounts in the wallet, it will return the first one by default. + */ + static async fromWallet( + signer: OfflineAminoSigner | IAminoGenericOfflineSigner, + encoders: Encoder[], + converters: AminoConverter[], + endpoint?: string | HttpEndpoint, + options?: SignerOptions + ) {} + + /** + * create AminoSigners from wallet. + * if there're multiple accounts in the wallet, it will return all of the signers. + */ + static async fromWalletToSigners( + signer: OfflineAminoSigner | IAminoGenericOfflineSigner, + encoders: Encoder[], + converters: AminoConverter[], + endpoint?: string | HttpEndpoint, + options?: SignerOptions + ) {} + } + ``` + +### Non-cosmos Signers + +For non-cosmos signers, there won't be many base classes for the signing process. Developers have to implement the interfaces on themselves. diff --git a/networks/cosmos/src/base/base-wallet.ts b/networks/cosmos/src/base/base-wallet.ts index cdb1e005..0997538f 100644 --- a/networks/cosmos/src/base/base-wallet.ts +++ b/networks/cosmos/src/base/base-wallet.ts @@ -1,5 +1,5 @@ import { Secp256k1Auth } from '@interchainjs/auth/secp256k1'; -import { AccountData, AddrDerivation, Auth, SignerConfig, SIGN_MODE, IGeneralOfflineSignArgs, IDocSigner } from '@interchainjs/types'; +import { AccountData, AddrDerivation, Auth, SignerConfig, SIGN_MODE, IGenericOfflineSignArgs, IDocSigner } from '@interchainjs/types'; import { AminoDocSigner } from '../signers/amino'; import { defaultSignerConfig } from '../defaults'; @@ -151,14 +151,14 @@ implements ICosmosWallet, OfflineAminoSigner, OfflineDirectSigner return { signMode: signMode, getAccounts: async () => this.getAccounts(), - sign: async ({ signerAddress, signDoc }: IGeneralOfflineSignArgs) => + sign: async ({ signerAddress, signDoc }: IGenericOfflineSignArgs) => this.signDirect(signerAddress, signDoc), }; case SIGN_MODE.AMINO: return { signMode: signMode, getAccounts: async () => this.getAccounts(), - sign: async ({ signerAddress, signDoc }: IGeneralOfflineSignArgs) => + sign: async ({ signerAddress, signDoc }: IGenericOfflineSignArgs) => this.signAmino(signerAddress, signDoc), } diff --git a/networks/cosmos/src/types/signer.ts b/networks/cosmos/src/types/signer.ts index 27d89e05..ae66bac7 100644 --- a/networks/cosmos/src/types/signer.ts +++ b/networks/cosmos/src/types/signer.ts @@ -14,7 +14,6 @@ import { CreateDocResponse, HttpEndpoint, IAccount, - IGenericOfflineSigner, IKey, Price, SignerConfig, diff --git a/networks/cosmos/src/types/wallet.ts b/networks/cosmos/src/types/wallet.ts index f506b9fd..a3e34ed4 100644 --- a/networks/cosmos/src/types/wallet.ts +++ b/networks/cosmos/src/types/wallet.ts @@ -1,4 +1,4 @@ -import { AccountData, IGeneralOfflineSignArgs, IGenericOfflineSigner, SIGN_MODE, SignerConfig } from '@interchainjs/types'; +import { AccountData, IGenericOfflineSignArgs, IGenericOfflineSigner, SIGN_MODE, SignerConfig } from '@interchainjs/types'; import { CosmosAminoDoc, CosmosDirectDoc } from './signer'; @@ -103,8 +103,8 @@ export interface IDirectGenericOfflineSigner extends IGenericOfflineSigner; -export type IDirectGeneralOfflineSignArgs = IGeneralOfflineSignArgs; +export type IAminoGenericOfflineSignArgs = IGenericOfflineSignArgs; +export type IDirectGenericOfflineSignArgs = IGenericOfflineSignArgs; /** * Amino general offline signer. @@ -119,7 +119,7 @@ export class AminoGenericOfflineSigner implements IAminoGenericOfflineSigner { getAccounts(): Promise { return this.offlineSigner.getAccounts(); } - sign({ signerAddress, signDoc }: IAminoGeneralOfflineSignArgs) { + sign({ signerAddress, signDoc }: IAminoGenericOfflineSignArgs) { return this.offlineSigner.signAmino(signerAddress, signDoc); } } @@ -137,7 +137,7 @@ export class DirectGenericOfflineSigner implements IDirectGenericOfflineSigner { getAccounts(): Promise { return this.offlineSigner.getAccounts(); } - sign({ signerAddress, signDoc }: IDirectGeneralOfflineSignArgs) { + sign({ signerAddress, signDoc }: IDirectGenericOfflineSignArgs) { return this.offlineSigner.signDirect(signerAddress, signDoc); } } \ No newline at end of file diff --git a/networks/cosmos/src/wallets/secp256k1hd.ts b/networks/cosmos/src/wallets/secp256k1hd.ts index 2f2a6309..80badc31 100644 --- a/networks/cosmos/src/wallets/secp256k1hd.ts +++ b/networks/cosmos/src/wallets/secp256k1hd.ts @@ -1,5 +1,5 @@ import { Secp256k1Auth } from '@interchainjs/auth/secp256k1'; -import { AddrDerivation, Auth, SignerConfig, SIGN_MODE, IGeneralOfflineSignArgs } from '@interchainjs/types'; +import { AddrDerivation, Auth, SignerConfig, SIGN_MODE, IGenericOfflineSignArgs } from '@interchainjs/types'; import { AminoDocSigner } from '../signers/amino'; import { defaultSignerConfig } from '../defaults'; diff --git a/networks/injective/src/signing-client.ts b/networks/injective/src/signing-client.ts index ff4f6ae2..a29bde65 100644 --- a/networks/injective/src/signing-client.ts +++ b/networks/injective/src/signing-client.ts @@ -1,5 +1,5 @@ import { IAminoGenericOfflineSigner, IDirectGenericOfflineSigner, isOfflineAminoSigner, isOfflineDirectSigner, OfflineSigner } from "@interchainjs/cosmos/types/wallet"; -import { HttpEndpoint, IGenericOfflineSigner, SIGN_MODE } from "@interchainjs/types"; +import { HttpEndpoint, SIGN_MODE } from "@interchainjs/types"; import { SigningClient } from "@interchainjs/cosmos/signing-client" import { SignerOptions } from "@interchainjs/cosmos/types/signing-client"; import { RpcClient } from '@interchainjs/cosmos/query/rpc'; diff --git a/networks/injective/src/wallets/ethSecp256k1hd.ts b/networks/injective/src/wallets/ethSecp256k1hd.ts index abb0a6a8..7baedfdf 100644 --- a/networks/injective/src/wallets/ethSecp256k1hd.ts +++ b/networks/injective/src/wallets/ethSecp256k1hd.ts @@ -1,5 +1,5 @@ import { EthSecp256k1Auth } from '@interchainjs/auth/ethSecp256k1'; -import { AccountData, AddrDerivation, Auth, IGeneralOfflineSignArgs, SIGN_MODE, SignerConfig } from '@interchainjs/types'; +import { AccountData, AddrDerivation, Auth, IGenericOfflineSignArgs, SIGN_MODE, SignerConfig } from '@interchainjs/types'; import { AminoDocSigner } from '../signers/amino'; import { defaultSignerOptions } from '../defaults'; diff --git a/packages/types/src/signer.ts b/packages/types/src/signer.ts index f8166f18..8ab4a4cb 100644 --- a/packages/types/src/signer.ts +++ b/packages/types/src/signer.ts @@ -129,6 +129,8 @@ export interface IDocSigner * @template Doc - sign doc type * @template AddressResponse - address type * @template BroadcastResponse - response type after broadcasting a transaction + * @template BroadcastOpts - options for broadcasting a transaction + * @template SignDocResp - response type after signing a document */ export interface UniSigner< SignArgs, @@ -248,7 +250,7 @@ export const SIGN_MODE = { /** * IGenericOfflineSigner is an interface for offline signers. */ -export interface IGenericOfflineSigner, TAcctData = AccountData > { +export interface IGenericOfflineSigner, TAcctData = AccountData > { /** * sign mode */ @@ -271,7 +273,7 @@ export interface IGenericOfflineSigner Promise; } -export interface IGeneralOfflineSignArgs { +export interface IGenericOfflineSignArgs { signerAddress: TAddr; signDoc: TDoc; } \ No newline at end of file From 6355a39d919afc9ac8be1ca79c35710c5f2a10f7 Mon Sep 17 00:00:00 2001 From: Zetazzz Date: Sun, 1 Dec 2024 21:01:45 +0800 Subject: [PATCH 2/5] tutorial --- docs/tutorial-draft.md | 423 ++++++++++++++++++++++++++++++++++------- 1 file changed, 351 insertions(+), 72 deletions(-) diff --git a/docs/tutorial-draft.md b/docs/tutorial-draft.md index 1ea16e0e..81dc83c8 100644 --- a/docs/tutorial-draft.md +++ b/docs/tutorial-draft.md @@ -1,48 +1,72 @@ # Tutorial -## Implement Signers +In this tutorial, we'll explore how to implement signers using the InterchainJS library. We'll focus on both Cosmos signers and non-Cosmos signers, covering the necessary steps to create, configure, and use them effectively. + +## Implementing Signers + +Implementing signers involves creating classes that adhere to specific interfaces provided by InterchainJS. This ensures that your signers are compatible with the rest of the library and can handle transactions appropriately. + +### Overview + +Signers are responsible for authorizing transactions by providing cryptographic signatures. They can be categorized into two main types: + +- **Cosmos Signers**: Utilize standard interfaces and base classes tailored for the Cosmos ecosystem. +- **Non-Cosmos Signers**: Require custom implementation of interfaces due to the lack of predefined base classes. + +### General Steps to Implement Signers + +1. **Define Signer Interfaces**: Outline the methods and properties your signer needs based on the transaction types it will handle. +2. **Choose Authentication Method**: Decide whether to use `ByteAuth`, `DocAuth`, or both, depending on the signing requirements. +3. **Implement Auth Classes**: Create classes for authentication that implement the necessary interfaces. +4. **Extend Base Signer Classes** (if available): For Cosmos signers, extend the provided base classes to streamline development. +5. **Build Transaction Builders**: Implement methods to construct transaction documents and serialize them for signing. +6. **Instantiate Signers**: Create instances of your signer classes with the appropriate authentication mechanisms. +7. **Test Your Signer**: Ensure your signer correctly signs transactions and interacts with the network as expected. + +Proceed to the next sections for detailed guidance on implementing Cosmos and non-Cosmos signers. ### Cosmos Signers -A tutorial for creating cosmos signers: +When working with Cosmos signers, InterchainJS offers a suite of base classes that significantly streamline the development process. These classes provide foundational implementations of common functionalities required for signing transactions on the Cosmos network. By extending these base classes, you can inherit methods for message encoding, transaction building, and signature handling without rewriting boilerplate code. + +Utilizing these base classes not only accelerates development but also ensures that your signer adheres to the standard practices and interfaces expected within the Cosmos ecosystem. This leads to better compatibility and easier integration with other tools and services that interact with Cosmos-based blockchains. -For cosmos signers, several base classes are provided make it easier to build the signing process. +#### Steps to Implement Cosmos Signers -Here's steps: +1. **Extend the Signer Type Based on `UniSigner`**: -1. extend the signer type based on UniSigner. - 1.1. figuring out the types of the following in the signing process: + 1.1 **Determine the Types Used in the Signing Process**: - - @template SignArgs - arguments for sign method - - @template Tx - transaction type - - @template Doc - sign doc type - - @template AddressResponse - address type - - @template BroadcastResponse - response type after broadcasting a transaction - - @template BroadcastOpts - options for broadcasting a transaction - - @template SignDocResp - response type after signing a document + - `@template SignArgs`: Arguments for the `sign` method. + - `@template Tx`: Transaction type. + - `@template Doc`: Sign document type. + - `@template AddressResponse`: Address type. + - `@template BroadcastResponse`: Response type after broadcasting a transaction. + - `@template BroadcastOpts`: Options for broadcasting a transaction. + - `@template SignDocResp`: Response type after signing a document. - e.g. In cosmos amino signing process: + For example, in the Cosmos Amino signing process: - ```ts + ```typescript SignArgs = CosmosSignArgs = { messages: Message[]; fee?: StdFee; memo?: string; options?: Option; - } + }; - Tx = TxRaw //cosmos.tx.v1beta1.TxRaw + Tx = TxRaw; // cosmos.tx.v1beta1.TxRaw - Doc = StdSignDoc + Doc = StdSignDoc; - AddressResponse = string + AddressResponse = string; - BroadcastResponse = { hash: string } + BroadcastResponse = { hash: string }; ``` - 1.2. Then we have a CosmosAminoSigner interface: + 1.2 **Define the `CosmosAminoSigner` Interface**: - ```ts + ```typescript export type CosmosAminoSigner = UniSigner< CosmosSignArgs, TxRaw, @@ -52,27 +76,29 @@ Here's steps: >; ``` -2. Before implementing the CosmosAminoSigner, it's important to decide between using ByteAuth or DocAuth or both for handling signatures. +2. **Choose Between `ByteAuth` or `DocAuth` for Handling Signatures**: #### ByteAuth - ByteAuth offers flexibility by allowing the signing of arbitrary byte arrays using algorithms like secp256k1 or eth_secp256k1, making it suitable for low-level or protocol-agnostic use cases. + `ByteAuth` offers flexibility by allowing the signing of arbitrary byte arrays using algorithms like `secp256k1` or `eth_secp256k1`, making it suitable for low-level or protocol-agnostic use cases. - Implement the ByteAuth interface: + Implement the `ByteAuth` interface: - ```ts + ```typescript export class Secp256k1Auth implements ByteAuth { + // Implementation details... + } ``` #### DocAuth - DocAuth is tailored for signing structured documents like AminoSignDoc, providing a streamlined workflow for blockchain transactions with offline signers, while also ensuring compatibility with the Cosmos SDK's predefined document formats. + `DocAuth` is tailored for signing structured documents like `AminoSignDoc`, providing a streamlined workflow for blockchain transactions with offline signers, while also ensuring compatibility with the Cosmos SDK's predefined document formats. - ##### generic offline signer + ##### Generic Offline Signer - A generic offline signer is needed for wrapping an offline signer up to make a standard interface for offline signers: + A generic offline signer is needed to wrap an offline signer and create a standard interface: - ```ts + ```typescript export interface IAminoGenericOfflineSigner extends IGenericOfflineSigner< string, @@ -88,62 +114,76 @@ Here's steps: constructor(public offlineSigner: OfflineAminoSigner) {} readonly signMode: string = SIGN_MODE.AMINO; + getAccounts(): Promise { return this.offlineSigner.getAccounts(); } + sign({ signerAddress, signDoc }: IAminoGenericOfflineSignArgs) { return this.offlineSigner.signAmino(signerAddress, signDoc); } } ``` - For details of ByteAuth and DocAuth, please see [here](/docs/auth.md#ByteAuth). + For details of `ByteAuth` and `DocAuth`, please see [the authentication documentation](/docs/auth.md#ByteAuth). -3. For building signed docs or signed transactions, we also need to implement the tx builder: +3. **Implement the Transaction Builder**: - 3.1. Extends the BaseCosmosTxBuilder and set the sign mode. - 3.2. Implement the way building docs - 3.3. Implement the way serializing docs - 3.4. Implement the way syncing info from signed docs + 3.1 **Extend `BaseCosmosTxBuilder` and Set the Sign Mode**: - ```ts + ```typescript export class AminoTxBuilder extends BaseCosmosTxBuilder { constructor( protected ctx: BaseCosmosTxBuilderContext< AminoSignerBase > ) { - // Extends the BaseCosmosTxBuilder and set the sign mode. + // Set the sign mode super(SignMode.SIGN_MODE_LEGACY_AMINO_JSON, ctx); } + } + ``` - // Implement the way building docs - async buildDoc({ - messages, - fee, - memo, - options, - }: CosmosSignArgs): Promise {} + 3.2 **Implement Methods to Build and Serialize Documents**: + + ```typescript + // Build the signing document + async buildDoc({ + messages, + fee, + memo, + options, + }: CosmosSignArgs): Promise { + // Implementation details... + } - // Implement the way serializing docs - async buildDocBytes(doc: CosmosAminoDoc): Promise {} + // Serialize the signing document + async buildDocBytes(doc: CosmosAminoDoc): Promise { + // Implementation details... + } + ``` - // Implement the way syncing info from signed docs - async syncSignedDoc( - txRaw: TxRaw, - signResp: SignDocResponse - ): Promise {} + 3.3 **Sync Information from Signed Documents**: + + ```typescript + async syncSignedDoc( + txRaw: TxRaw, + signResp: SignDocResponse + ): Promise { + // Implementation details... } ``` -4. Then we implement the AminoSigner. A signer's initiated by an Auth or offline signer. If an offlin signer is supported, a static method of fromWallet should be implemented to convert an offline signer to a DocAuth. +4. **Implement the `AminoSigner`**: + + The signer is initiated by an `Auth` or offline signer. If an offline signer is supported, a static method `fromWallet` should be implemented to convert it to a `DocAuth`. - ```ts + ```typescript export class AminoSigner extends AminoSignerBase implements CosmosAminoSigner { - // a signer will be initiated by an Auth, ByteAuth or DocAuth + // Initiated by an Auth, ByteAuth, or DocAuth constructor( auth: Auth, encoders: Encoder[], @@ -154,42 +194,281 @@ Here's steps: super(auth, encoders, converters, endpoint, options); } - // Implement the way a signer getting tx builder to build signing process. + // Get the transaction builder getTxBuilder(): BaseCosmosTxBuilder { return new AminoTxBuilder(new BaseCosmosTxBuilderContext(this)); } - /** - * get account - */ - async getAccount() {} + // Get account information + async getAccount() { + // Implementation details... + } - /** - * create AminoSigner from wallet. - * if there're multiple accounts in the wallet, it will return the first one by default. - */ + // Create AminoSigner from a wallet (returns the first account by default) static async fromWallet( signer: OfflineAminoSigner | IAminoGenericOfflineSigner, encoders: Encoder[], converters: AminoConverter[], endpoint?: string | HttpEndpoint, options?: SignerOptions - ) {} + ) { + // Implementation details... + } - /** - * create AminoSigners from wallet. - * if there're multiple accounts in the wallet, it will return all of the signers. - */ + // Create AminoSigners from a wallet (returns all accounts) static async fromWalletToSigners( signer: OfflineAminoSigner | IAminoGenericOfflineSigner, encoders: Encoder[], converters: AminoConverter[], endpoint?: string | HttpEndpoint, options?: SignerOptions - ) {} + ) { + // Implementation details... + } } ``` -### Non-cosmos Signers +### Non-Cosmos Signers + +For non-Cosmos signers, there are fewer base classes available for the signing process. Developers need to implement the required interfaces themselves, ensuring compatibility with their specific blockchain or protocol. + +#### Steps to Implement Non-Cosmos Signers + +1. **Extend the Signer Type Based on `UniSigner`**: + + 1.1 **Determine the Types Used in the Signing Process**: + + - `@template SignArgs`: Arguments for the `sign` method. + - `@template Tx`: Transaction type. + - `@template Doc`: Sign document type. + - `@template AddressResponse`: Address type. + - `@template BroadcastResponse`: Response type after broadcasting a transaction. + - `@template BroadcastOpts`: Options for broadcasting a transaction. + - `@template SignDocResp`: Response type after signing a document. + + For example, in the EIP-712 signing process: + + ```typescript + SignArgs = TransactionRequest; + + Tx = string; // Serialized signed transaction as a hex string. + + Doc = TransactionRequest; + + AddressResponse = string; + + BroadcastResponse = TransactionResponse; + + BroadcastOpts = unknown; + + SignDocResp = string; // Signature string of the signed document. + ``` + + 1.2 **Define the `UniEip712Signer` Interface**: + + ```typescript + import { UniSigner } from "@interchainjs/types"; + import { TransactionRequest, TransactionResponse } from "ethers"; + + export type UniEip712Signer = UniSigner< + TransactionRequest, + string, + TransactionRequest, + string, + TransactionResponse, + unknown, + string + >; + ``` + +2. **Handle Authentication for Getting Signatures** + + 2.1 **Implement the `Eip712DocAuth` Class** + + - **Purpose**: The `Eip712DocAuth` class extends `BaseDocAuth` to handle authentication and signing of documents using the EIP-712 standard in Ethereum. + + - **Constructor Parameters**: + + - `offlineSigner: IEthereumGenericOfflineSigner`: An interface for the Ethereum offline signer. + - `address: string`: The Ethereum address associated with the signer. + + - **Static Method**: + + - `fromOfflineSigner(offlineSigner: IEthereumGenericOfflineSigner)`: Asynchronously creates an instance of `Eip712DocAuth` by retrieving the account address from the offline signer. + + - **Methods**: + - `getPublicKey(): IKey`: Throws an error because, in EIP-712 signing, the public key is not typically required. + - `signDoc(doc: TransactionRequest): Promise`: Uses the `offlineSigner` to sign the transaction request document and returns the signature as a string. + + ```typescript + import { BaseDocAuth, IKey, SignDocResponse } from "@interchainjs/types"; + import { IEthereumGenericOfflineSigner } from "./wallet"; + import { TransactionRequest } from "ethers"; + + // Eip712DocAuth Class: Extends BaseDocAuth to provide authentication and document signing capabilities specific to EIP-712. + export class Eip712DocAuth extends BaseDocAuth< + IEthereumGenericOfflineSigner, + TransactionRequest, + unknown, + string, + string, + string + > { + // Calls the parent BaseDocAuth constructor with the provided offlineSigner and address. + constructor( + offlineSigner: IEthereumGenericOfflineSigner, + address: string + ) { + super(offlineSigner, address); + } + + // Retrieves the accounts from the offlineSigner and creates a new instance of Eip712DocAuth with the first account's address. + static async fromOfflineSigner( + offlineSigner: IEthereumGenericOfflineSigner + ) { + const [account] = await offlineSigner.getAccounts(); + + return new Eip712DocAuth(offlineSigner, account); + } + + // Throws an error because EIP-712 does not require a public key for signing operations. + getPublicKey(): IKey { + throw new Error("For EIP712, public key is not needed"); + } + + // Calls the sign method of the offlineSigner to sign the TransactionRequest document and returns a promise that resolves to the signature string. + signDoc(doc: TransactionRequest): Promise { + return this.offlineSigner.sign(doc); + } + } + ``` + + By implementing the `Eip712DocAuth` class as shown, you can handle authentication and document signing for Ethereum transactions using the EIP-712 standard. + +3. **Implement the Signer**: + + Let's take Eip712Signer as an example: + + 3.1 **Define the `Eip712Signer` Class** + + - **Purpose**: The `Eip712Signer` class implements the `UniEip712Signer` interface to provide signing and broadcasting capabilities for Ethereum transactions using the EIP-712 standard. + + - **Constructor Parameters**: + + - `auth: Auth`: An authentication object, expected to be an instance of `Eip712DocAuth`. + - `endpoint: string`: The JSON-RPC endpoint URL of the Ethereum node. + + - **Properties**: + + - `provider: Provider`: An Ethereum provider connected to the specified endpoint. + - `docAuth: Eip712DocAuth`: An instance of `Eip712DocAuth` for document authentication and signing. + + - **Static Methods**: + + - `static async fromWallet(signer: IEthereumGenericOfflineSigner, endpoint?: string)`: Creates an instance of `Eip712Signer` from an offline signer and an optional endpoint. + + ```typescript + import { + IKey, + SignDocResponse, + SignResponse, + BroadcastOptions, + Auth, + isDocAuth, + HttpEndpoint, + } from "@interchainjs/types"; + import { + JsonRpcProvider, + Provider, + TransactionRequest, + TransactionResponse, + } from "ethers"; + import { UniEip712Signer } from "../types"; + import { Eip712DocAuth } from "../types/docAuth"; + import { IEthereumGenericOfflineSigner } from "../types/wallet"; + + // Eip712Signer Class: Implements the UniEip712Signer interface to handle signing and broadcasting Ethereum transactions using EIP-712. + export class Eip712Signer implements UniEip712Signer { + provider: Provider; + docAuth: Eip712DocAuth; + + // Constructor: Initializes the provider and docAuth properties. + constructor(auth: Auth, public endpoint: string) { + this.provider = new JsonRpcProvider(endpoint); + this.docAuth = auth as Eip712DocAuth; + } + + // Creates an Eip712Signer from a wallet. + // If there are multiple accounts in the wallet, it will return the first one by default. + static async fromWallet( + signer: IEthereumGenericOfflineSigner, + endpoint?: string + ) { + const auth = await Eip712DocAuth.fromOfflineSigner(signer); + + return new Eip712Signer(auth, endpoint); + } + + // Retrieves the Ethereum address from the docAuth instance. + async getAddress(): Promise { + return this.docAuth.address; + } + + // Not supported in this implementation; throws an error. + signArbitrary(data: Uint8Array): IKey | Promise { + throw new Error("Method not supported."); + } + + // Uses docAuth.signDoc to sign the TransactionRequest document. + async signDoc(doc: TransactionRequest): Promise { + return this.docAuth.signDoc(doc); + } + + // Not supported in this implementation; throws an error. + broadcastArbitrary( + data: Uint8Array, + options?: unknown + ): Promise { + throw new Error("Method not supported."); + } + + // Calls signDoc to get the signed transaction (tx). + // Returns a SignResponse object containing the signed transaction, original document, and a broadcast function. + async sign( + args: TransactionRequest + ): Promise< + SignResponse< + string, + TransactionRequest, + TransactionResponse, + BroadcastOptions + > + > { + const result = await this.signDoc(args); + + return { + tx: result, + doc: args, + broadcast: async () => { + return this.provider.broadcastTransaction(result); + }, + }; + } + + // Calls signDoc to sign the transaction and broadcasts it using provider.broadcastTransaction. + async signAndBroadcast( + args: TransactionRequest + ): Promise { + const result = await this.signDoc(args); + + return this.provider.broadcastTransaction(result); + } + + // Broadcasts a signed transaction (hex string) using provider.broadcastTransaction. + broadcast(tx: string): Promise { + return this.provider.broadcastTransaction(tx); + } + } + ``` -For non-cosmos signers, there won't be many base classes for the signing process. Developers have to implement the interfaces on themselves. + By implementing the `Eip712Signer` class as shown, you can facilitate Ethereum transaction signing and broadcasting in applications that require EIP-712 compliance. From 179aa17156a48ea53dfd1a41191f9af4b59618ff Mon Sep 17 00:00:00 2001 From: Zetazzz Date: Sun, 1 Dec 2024 21:02:38 +0800 Subject: [PATCH 3/5] change the file name --- docs/{tutorial-draft.md => tutorial.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{tutorial-draft.md => tutorial.md} (100%) diff --git a/docs/tutorial-draft.md b/docs/tutorial.md similarity index 100% rename from docs/tutorial-draft.md rename to docs/tutorial.md From 53ac78f122954e5a919e889f2f4937974f28b00b Mon Sep 17 00:00:00 2001 From: Zetazzz Date: Mon, 2 Dec 2024 21:07:45 +0800 Subject: [PATCH 4/5] Fix README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0384d5bd..418eb586 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,6 @@ graph LR injective_signer --> injective_amino["Amino Signer"] injective_signer --> injective_direct["Direct Signer"] - injective_signer --> injective_eip712["EIP712 Signer"] implement_signer --> any_signer["Any Signer"] @@ -62,6 +61,10 @@ graph LR style utils fill:#ccf,stroke:#333,stroke-width:2px ``` +## Tutorial for building a custom signer + +- [Tutorial](/docs/tutorial.md) + ## Auth Universally applied across different networks From a1d5a495f625aba9d08e8482ce7fc979bc4bd433 Mon Sep 17 00:00:00 2001 From: Zetazzz Date: Mon, 2 Dec 2024 21:08:59 +0800 Subject: [PATCH 5/5] remove warning --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 418eb586..64c48a74 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,6 @@ A single, universal signing interface for any network. Birthed from the interchain ecosystem for builders. Create adapters for any web3 network. -⚠️ **This software is currently in a Development Preview Alpha stage.** It is not ready for production use. The features and functionality are subject to change, and there may be significant issues. We welcome feedback and contributions, but please use with caution and at your own risk. - - [Advanced Docs](/docs/) ## Overview