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

Web3 1146 wallet connection plus additional utils #8

Merged
merged 17 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 7 additions & 1 deletion DEV_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@ SEED_PHRASE="MY SEED PHRASE WORDS GO HERE"
npm install && npm run pack-and-install
```

## Install Package Locally
## Install Package Locally

1. Deploy the package with latest code

```shell
npm run pack-and-install
```

2. Install the tgz in frontend project, renaming the tgz in the command below as necessary

```shell
npm install ./path-to-package/zkverifyjs-0.2.0.tgz
```

## Add New Proof Types

1. Update `src/config/index.ts`
Expand Down
107 changes: 91 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ The `zkverifyjs` package is a TypeScript library designed to facilitate sending
- [zkVerifySession.verify](#zkverifysessionverify)
- [zkVerifySession.registerVerificationKey](#zkverifysessionregisterverificationkey)
- [zkVerifySession.poe](#zkverifysessionpoe)
- [zkVerifySession.format](#zkverifysessionformat)
- [zkVerifySession.createSubmittableExtrinsic](#zkverifysessioncreatesubmittableextrinsic)
- [zkVerifySession.createExtrinsicHex](#zkverifysessioncreateextrinsichex)
- [zkVerifySession.createExtrinsicFromHex](#zkverifysessioncreateextrinsicfromhex)
- [zkVerifySession.estimateCost](#zkverifysessionestimatecost)
- [zkVerifySession.accountInfo](#zkverifysessionaccountinfo)
- [zkVerifySession.addAccount](#zkverifysessionaddaccount)
- [zkVerifySession.removeAccount](#zkverifysessionremoveaccount)
Expand Down Expand Up @@ -71,13 +76,19 @@ const session = await zkVerifySession.start()
```typescript
const session = await zkVerifySession.start()
.Testnet()
.withWallet(); // Uses browser session context "window"
.withWallet({
source: selectedWallet,
accountAddress: selectedAccount,
}); // Uses browser session context "window"
rushby marked this conversation as resolved.
Show resolved Hide resolved
```
6. Full Frontend Browser Session (send transactions) with Custom WebSocket:
```typescript
const session = await zkVerifySession.start()
.Custom("wss://testnet-rpc.zkverify.io") // Custom network
.withWallet(); // Uses browser session context "window"
.withWallet({
source: selectedWallet,
accountAddress: selectedAccount,
}); // Uses browser session context "window"
```

Not specifying `withAccount()` or `withWallet()` will start a read-only session, transaction methods cannot be used, and only calls to read data are allowed:
Expand All @@ -99,15 +110,15 @@ const { events, transactionResult } = await session
.nonce(1) // Set the nonce (optional)
.waitForPublishedAttestation() // Wait for the attestation to be published (optional)
.withRegisteredVk() // Indicate that the verification key is already registered (optional)
.execute(proof, publicSignals, vk); // Execute the verification with the provided proof data
.execute({proofData: [proof, publicSignals, vk]}); // Execute the verification with the provided proof data

```

2. Frontend after establishing a session with `withWallet()`
```typescript
const { events, transactionResult } = await session.verify()
.groth16()
.execute(proofData, publicSignals, vkey);
.execute({proofData: [proof, publicSignals, vk]});

events.on('ErrorEvent', (eventData) => {
console.error(JSON.stringify(eventData));
Expand All @@ -132,7 +143,7 @@ const vkTransactionInfo: VKRegistrationTransactionInfo = await transactionResult
const {events: verifyEvents, transactionResult: verifyTransactionResult} = await session.verify()
.fflonk()
.withRegisteredVk() // Option needs to be specified as we're using the registered statement hash.
.execute(proof, publicSignals, vkTransactionInfo.statementHash);
.execute({proofData: [proof, publicSignals, vkTransactionInfo.statementHash]});

const verifyTransactionInfo: VerifyTransactionInfo = await verifyTransactionResult;
```
Expand Down Expand Up @@ -177,7 +188,7 @@ To await the final result of the transaction, use the transactionResult promise.
```typescript
const {events, transactionResult} = await session.verify()
.groth16()
.execute(proof, publicSignals, vk)
.execute({proofData: [proof, publicSignals, vk]})

const result = await transactionResult;
console.log('Final transaction result:', result);
Expand All @@ -190,11 +201,7 @@ Wait for the NewElement event to be published before the transaction info is ret
```typescript
const {events, transactionResult} = await session.verify().risc0()
.waitForPublishedAttestation()
.execute(
proof,
publicSignals,
vk
);
.execute({proofData: [proof, publicSignals, vk]});

const transactionInfo: VerifyTransactionInfo = await transactionResult;

Expand All @@ -216,7 +223,7 @@ async function executeVerificationTransaction(proof: unknown, publicSignals: unk
// Execute the verification transaction
const { events, transactionResult } = await session.verify().risc0()
.waitForPublishedAttestation()
.execute(proof, publicSignals, vk);
.execute({proofData: [proof, publicSignals, vk]});

// Listen for the 'includedInBlock' event
events.on(ZkVerifyEvents.IncludedInBlock, (eventData) => {
Expand Down Expand Up @@ -268,13 +275,16 @@ await zkVerifySession.start()
.Testnet() // 1. Either preconfigured network selection
.Custom('wss://custom') // 2. Or specify a custom network selection
.withAccount(process.env.SEED_PHRASE!) // Optional
.withWallet() // Optional
.withWallet({
source: selectedWallet,
accountAddress: selectedAccount,
}) // Optional
.readOnly() // Optional
```

- Network Selection: Preconfigured options such as `.Testnet()` or provide your own websocket url using `.Custom('wss://custom'')`.
- withAccount : Create a full session with ability send transactions get account info by using .withAccount('seed-phrase') and specifying your own seed phrase.
- withWallet : Establish connection to a browser based substrate wallet, cannot be used with `withAccount`;
- withWallet : Establish connection to a browser extension based substrate wallet like talisman or subwallet, cannot be used with `withAccount`;
- readOnly: Start the session in read-only mode, unable to send transactions or retrieve account info.

## `zkVerifySession.close`
Expand All @@ -292,14 +302,16 @@ const { events, transactionResult } = await session.verify()
.nonce(1)
.waitForPublishedAttestation()
.withRegisteredVk()
.execute(proof, publicSignals, vk);
.execute({ proofData: [proof, publicSignals, vk] }); // 1. Directly pass proof data
.execute({ extrinsic: submittableExtrinsic }); // 2. OR pass in a pre-built SubmittableExtrinsic
rushby marked this conversation as resolved.
Show resolved Hide resolved

```

- Proof Type: `.fflonk()` specifies the type of proof to be used. Options available for all supported proof types.
- Nonce: `.nonce(1)` sets the nonce for the transaction. This is optional and can be omitted if not required.
- Attestation Option: `.waitForPublishedAttestation()` specifies that the transaction should wait for the attestation to be published before completing. This is optional.
Registered Verification Key: `.withRegisteredVk()` indicates that the verification key being used is registered on the chain. This option is optional and defaults to false.
- Registered Verification Key: `.withRegisteredVk()` indicates that the verification key being used is registered on the chain. This option is optional and defaults to false.
- Execute: You can either send in the raw proof details using `{ proofData: ... }` or verify a prebuilt extrinsic `{ extrinsic: ... }`
- Returns: An object containing an EventEmitter for real-time events and a Promise that resolves with the final transaction result, including waiting for the `poe.NewElement` attestation confirmation if waitForPublishedAttestation is specified.

## `zkVerifySession.registerVerificationKey`
Expand All @@ -322,6 +334,69 @@ const proofDetails = await session.poe(attestationId, leafDigest, blockHash);
- `blockHash`: (Optional) A string representing the block hash at which the proof should be retrieved.
- Returns: A Promise that resolves to a MerkleProof object containing the proof path details.

## `zkVerifySession.format`

```typescript
const [formattedVk, formattedProof, formattedPubs] = await session.format(proofType, proof, publicSignals, vk, registeredVk);
rushby marked this conversation as resolved.
Show resolved Hide resolved

```
- `proofType`: An enum value representing the type of proof being formatted (e.g., ProofType.groth16).
- `proof`: The proof data that needs to be formatted.
- `publicSignals`: The public signals associated with the proof, which are also formatted.
- `vk`: The verification key that may be either registered or unregistered, depending on the context.
- `registeredVk`: (Optional) A boolean indicating if the verification key is already registered.
Returns: A Promise that resolves to an array containing:
rushby marked this conversation as resolved.
Show resolved Hide resolved
formattedVk: The formatted verification key.
formattedProof: The formatted proof data.
formattedPubs: The formatted public signals.

## `zkVerifySession.createSubmittableExtrinsic`

```shell
const extrinsic = await session.createSubmittableExtrinsic(api, pallet, params);
```

- `api`: An instance of the Polkadot API that provides the necessary methods for interacting with the blockchain.
- `pallet`: A string representing the name of the pallet that contains the proof submission method.
- `params`: An array of formatted proof parameters required for the extrinsic.
Returns: A Promise that resolves to a SubmittableExtrinsic<'promise'>, allowing you to submit the proof to the blockchain.

## `zkVerifySession.createExtrinsicHex`

```shell
const hex = await session.createExtrinsicHex(api, pallet, params);
```

- `api`: An instance of the Polkadot API used to create the extrinsic.
- `pallet`: A string representing the name of the pallet that contains the proof submission method.
- `params`: An array of formatted proof parameters needed for the extrinsic.
rushby marked this conversation as resolved.
Show resolved Hide resolved
Returns: A Promise that resolves to a hex-encoded string representing the SubmittableExtrinsic.

## `zkVerifySession.createExtrinsicFromHex`

```shell
const extrinsic = await session.createExtrinsicFromHex(api, extrinsicHex);
```

- `api`: An instance of the Polkadot API used for creating the extrinsic.
- `extrinsicHex`: A string representing the hex-encoded SubmittableExtrinsic to be reconstructed.
Returns: A Promise that resolves to a SubmittableExtrinsic<'promise'>, allowing you to interact with the reconstructed extrinsic.

## `zkVerifySession.estimateCost`

```shell
const extrinsic = await session.estimateCost(extrinsic);
```

- `extrinstic`: A submitProof SubmittableExtrinsic.
Returns: A Promise that resolves to an ExtrinsicCostEstimate:
```
partialFee: string;
estimatedFeeInTokens: string;
weight: string;
length: number;
```

## `zkVerifySession.accountInfo`

```typescript
Expand Down
2 changes: 1 addition & 1 deletion src/api/account/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { setupAccount } from './index';
import { getSeedPhrase } from "../../../tests/common/utils";
import { getSeedPhrase } from '../../../tests/common/utils';

describe('setupAccount', () => {
beforeAll(async () => {
Expand Down
76 changes: 76 additions & 0 deletions src/api/estimate/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ApiPromise } from '@polkadot/api';
import { KeyringPair } from '@polkadot/keyring/types';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { estimateCost, convertFeeToToken } from './index';
import { AccountConnection } from '../connection/types';

describe('estimateCost', () => {
let mockApi: jest.Mocked<ApiPromise>;
let mockExtrinsic: jest.Mocked<SubmittableExtrinsic<'promise'>>;
let mockKeyringPair: jest.Mocked<KeyringPair>;
let connection: AccountConnection;

beforeEach(() => {
mockApi = {
registry: {
chainDecimals: [18],
},
} as unknown as jest.Mocked<ApiPromise>;

mockExtrinsic = {
paymentInfo: jest.fn().mockResolvedValue({
partialFee: {
toString: () => '1000000000000000000',
},
weight: {
toString: () => '2000000000',
},
}),
length: 100,
} as unknown as jest.Mocked<SubmittableExtrinsic<'promise'>>;

mockKeyringPair = {
address: 'test-address',
} as unknown as jest.Mocked<KeyringPair>;

connection = {
api: mockApi,
provider: {} as jest.Mocked<any>,
account: mockKeyringPair,
};
});

it('should estimate the cost of an extrinsic successfully', async () => {
const result = await estimateCost(mockApi, mockExtrinsic, connection);

expect(result).toEqual({
partialFee: '1000000000000000000',
estimatedFeeInTokens: '1.000000000000000000',
weight: '2000000000',
length: 100,
});
expect(mockExtrinsic.paymentInfo).toHaveBeenCalledWith(mockKeyringPair);
});

it('should throw an error if account information is missing', async () => {
connection.account = undefined as unknown as typeof connection.account;

await expect(
estimateCost(mockApi, mockExtrinsic, connection),
).rejects.toThrow(
'Account information is required to estimate extrinsic cost.',
);
});
});

describe('convertFeeToToken', () => {
it('should correctly convert fee from smallest unit to token unit with 18 decimals', () => {
const result = convertFeeToToken('1000000000000000000', 18);
expect(result).toBe('1.000000000000000000');
});

it('should handle zero fee correctly', () => {
const result = convertFeeToToken('0', 18);
expect(result).toBe('0.000000000000000000');
});
});
53 changes: 53 additions & 0 deletions src/api/estimate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ApiPromise } from '@polkadot/api';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { AccountConnection } from '../connection/types';
import { ExtrinsicCostEstimate } from './types';

/**
* Converts a fee in the smallest unit to the base token unit.
*
* @param {string} feeInSmallestUnit - Fee in the blockchain's smallest unit.
* @param {number} decimals - The number of decimals in the blockchain's base token.
* @returns {string} - The fee in the base token unit.
*/
export function convertFeeToToken(
feeInSmallestUnit: string,
decimals: number,
): string {
const feeInTokens = parseFloat(feeInSmallestUnit) / Math.pow(10, decimals);
return feeInTokens.toFixed(decimals);
}

/**
* Estimates the cost of a given extrinsic for the specified user.
*
* @param {ApiPromise} api - The Polkadot API instance.
* @param {SubmittableExtrinsic<'promise', ISubmittableResult>} extrinsic - The extrinsic to estimate.
* @param {AccountConnection} connection - The user's account connection, containing account information.
* @returns {Promise<ExtrinsicCostEstimate>} - A promise that resolves to an object containing the estimated fee and extrinsic details.
*/
export async function estimateCost(
api: ApiPromise,
extrinsic: SubmittableExtrinsic<'promise'>,
connection: AccountConnection,
): Promise<ExtrinsicCostEstimate> {
if (!connection.account) {
throw new Error(
'Account information is required to estimate extrinsic cost.',
);
}

const paymentInfo = await extrinsic.paymentInfo(connection.account);
const tokenDecimals = api.registry.chainDecimals[0];
rushby marked this conversation as resolved.
Show resolved Hide resolved
const estimatedFeeInTokens = convertFeeToToken(
paymentInfo.partialFee.toString(),
tokenDecimals,
);

return {
partialFee: paymentInfo.partialFee.toString(),
estimatedFeeInTokens,
weight: paymentInfo.weight.toString(),
length: extrinsic.length,
};
}
6 changes: 6 additions & 0 deletions src/api/estimate/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface ExtrinsicCostEstimate {
partialFee: string;
estimatedFeeInTokens: string;
weight: string;
length: number;
}
Loading