Skip to content

feat: handling of get-starknet v5 #1382

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

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@
"@noble/hashes": "1.6.0",
"@scure/base": "1.2.1",
"@scure/starknet": "1.1.0",
"@wallet-standard/features": "^1.1.0",
"abi-wan-kanabi": "2.2.4",
"@starknet-io/get-starknet/wallet-standard/features": "^5.0.0",
"isows": "^1.0.6",
"lossless-json": "^4.0.1",
"pako": "^2.0.4",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export * from './utils/contract';
export * from './utils/transactionReceipt/transactionReceipt';
export * from './utils/units';
export * as wallet from './wallet/connect';
export * as walletV5 from './wallet/connectV5';
export * from './global/config';
export * from './global/logger';
export * from './global/logger.type';
183 changes: 183 additions & 0 deletions src/wallet/accountV5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import type {
AddStarknetChainParameters,
Signature,
WatchAssetParameters,
} from 'starknet-types-08';

import type { WalletWithStarknetFeatures } from '@starknet-io/get-starknet/wallet-standard/features';
import type { StandardEventsChangeProperties } from '@wallet-standard/features';

import { Account, AccountInterface } from '../account';
import { StarknetChainId } from '../global/constants';
import { ProviderInterface } from '../provider';
import {
AllowArray,
CairoVersion,
Call,
CompiledSierra,
DeclareContractPayload,
MultiDeployContractResponse,
ProviderOptions,
TypedData,
UniversalDeployerContractPayload,
} from '../types';
import { extractContractHashes } from '../utils/contract';
import { stringify } from '../utils/json';
import { buildUDCCall } from '../utils/transaction';
import {
addDeclareTransaction,
addInvokeTransaction,
addStarknetChain,
getPermissions,
subscribeWalletEvent,
requestAccounts,
signMessage,
switchStarknetChain,
watchAsset,
} from './connectV5';

/**
* WalletAccountV5 class.
* This class is used to create a wallet account that can be used to interact with a Starknet wallet browser extension, using get-starknet v5.
*/
export class WalletAccountV5 extends Account implements AccountInterface {
public walletProvider: WalletWithStarknetFeatures;

/**
* The function to use to unsubscribe from the wallet events.
* To call before the instance is deleted.
*/
private unsubscribe: () => void;

constructor(
/* Node that will be used to READ Starknet */
providerOrOptions: ProviderOptions | ProviderInterface,
/* the get-starknet v5 wallet that will WRITE Starknet */
walletProvider: WalletWithStarknetFeatures,
/* Optional. To use when address is known */
address: string,
/* Optional cairo version of the account ("0" | "1") */
cairoVersion?: CairoVersion
) {
super(providerOrOptions, address, '', cairoVersion); // At this point unknown address
this.walletProvider = walletProvider;

// Update Address/network on change
this.unsubscribe = this.walletProvider.features['standard:events'].on(
'change',
(change: StandardEventsChangeProperties) => {
if (!change.accounts?.length) return;
if (change.accounts[0].address) this.address = change.accounts[0].address;
if (change.accounts[0].chains)
this.channel.setChainId(change.accounts[0].chains[0].slice(9) as StarknetChainId);
}
);
}

/**
* WALLET EVENTS
*/
public onChange(callback: (change: StandardEventsChangeProperties) => void): void {
subscribeWalletEvent(this.walletProvider, callback);
}

public unsubscribeChange(): void {
this.unsubscribe();
}

/**
* WALLET SPECIFIC METHODS
*/
public requestAccounts(silentMode = false) {
return requestAccounts(this.walletProvider, silentMode);
}

public getPermissions() {
return getPermissions(this.walletProvider);
}

public switchStarknetChain(chainId: StarknetChainId) {
return switchStarknetChain(this.walletProvider, chainId);
}

public watchAsset(asset: WatchAssetParameters) {
return watchAsset(this.walletProvider, asset);
}

public addStarknetChain(chain: AddStarknetChainParameters) {
return addStarknetChain(this.walletProvider, chain);
}

/**
* ACCOUNT METHODS
*/
override execute(calls: AllowArray<Call>) {
const txCalls = [].concat(calls as any).map((it) => {
const { contractAddress, entrypoint, calldata } = it;
return {
contract_address: contractAddress,
entry_point: entrypoint,
calldata,
};
});

const params = {
calls: txCalls,
};

return addInvokeTransaction(this.walletProvider, params);
}

override declare(payload: DeclareContractPayload) {
const declareContractPayload = extractContractHashes(payload);
// DISCUSS: HOTFIX: Adapt Abi format
const pContract = payload.contract as CompiledSierra;
const cairo1Contract = {
...pContract,
abi: stringify(pContract.abi),
};
if (!declareContractPayload.compiledClassHash) {
throw Error('compiledClassHash is required');
}
const params = {
compiled_class_hash: declareContractPayload.compiledClassHash,
contract_class: cairo1Contract,
};
return addDeclareTransaction(this.walletProvider, params);
}

override async deploy(
payload: UniversalDeployerContractPayload | UniversalDeployerContractPayload[]
): Promise<MultiDeployContractResponse> {
const { calls, addresses } = buildUDCCall(payload, this.address);
const invokeResponse = await this.execute(calls);
return {
...invokeResponse,
contract_address: addresses,
};
}

override signMessage(typedData: TypedData): Promise<Signature> {
return signMessage(this.walletProvider, typedData);
}

static async connect(
provider: ProviderInterface,
walletProvider: WalletWithStarknetFeatures,
cairoVersion?: CairoVersion,
silentMode: boolean = false
) {
const [accountAddress] = await requestAccounts(walletProvider, silentMode);
return new WalletAccountV5(provider, walletProvider, accountAddress, cairoVersion);
}

static async connectSilent(
provider: ProviderInterface,
walletProvider: WalletWithStarknetFeatures,
cairoVersion?: CairoVersion
) {
return WalletAccountV5.connect(provider, walletProvider, cairoVersion, true);
}

// TODO: MISSING ESTIMATES
}
181 changes: 181 additions & 0 deletions src/wallet/connectV5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import type { StandardEventsChangeProperties } from '@wallet-standard/features';
import type { WalletWithStarknetFeatures } from 'getSnStandard/features';
import {
type WatchAssetParameters,
type AddDeclareTransactionParameters,
type AddInvokeTransactionParameters,
type AddStarknetChainParameters,
type ChainId,
type TypedData,
type Permission,
type Address,
AddInvokeTransactionResult,
AddDeclareTransactionResult,
AccountDeploymentData,
Signature,
SpecVersion,
} from 'starknet-types-08';

/**
* Request Permission for wallet account, return addresses that are allowed by user
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @param {boolean} [silent_mode=false] false: request user interaction allowance. true: return only pre-allowed
* @returns {Address[]} allowed accounts addresses
*/
export function requestAccounts(
walletWSF: WalletWithStarknetFeatures,
silent_mode: boolean = false
): Promise<Address[]> {
return walletWSF.features['starknet:walletApi'].request({
type: 'wallet_requestAccounts',
params: { silent_mode },
});
}

/**
* Request if DAPP is connected to wallet.
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @returns {Permission[]} "accounts" if permission granted
*/
export function getPermissions(walletWSF: WalletWithStarknetFeatures): Promise<Permission[]> {
return walletWSF.features['starknet:walletApi'].request({ type: 'wallet_getPermissions' });
}

/**
* Request adding an ERC20 Token to the Wallet List
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @param {WatchAssetParameters} asset description of the token to add.
* @returns {boolean} true if the token was added successfully
*/
export function watchAsset(
walletWSF: WalletWithStarknetFeatures,
asset: WatchAssetParameters
): Promise<boolean> {
return walletWSF.features['starknet:walletApi'].request({
type: 'wallet_watchAsset',
params: asset,
});
}

/**
* Request adding custom Starknet chain
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @param {AddStarknetChainParameters} chain description of the chain to add.
* @returns {boolean} true if the chain was added successfully
*/
export function addStarknetChain(
walletWSF: WalletWithStarknetFeatures,
chain: AddStarknetChainParameters
): Promise<boolean> {
return walletWSF.features['starknet:walletApi'].request({
type: 'wallet_addStarknetChain',
params: chain,
});
}

/**
* Request Wallet Network change
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @param {ChainId} chainId encoded name of the chain requested.
* @returns {boolean} true if the chain was changed successfully
*/
export function switchStarknetChain(
walletWSF: WalletWithStarknetFeatures,
chainId: ChainId
): Promise<boolean> {
return walletWSF.features['starknet:walletApi'].request({
type: 'wallet_switchStarknetChain',
params: { chainId },
});
}

/**
* Request the current chain ID from the wallet.
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @returns {ChainId} The current Starknet chain ID.
*/
export function requestChainId(walletWSF: WalletWithStarknetFeatures): Promise<ChainId> {
return walletWSF.features['starknet:walletApi'].request({ type: 'wallet_requestChainId' });
}

/**
* Get deployment data for a contract.
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @returns {AccountDeploymentData} The deployment data result.
*/
export function deploymentData(
walletWSF: WalletWithStarknetFeatures
): Promise<AccountDeploymentData> {
return walletWSF.features['starknet:walletApi'].request({ type: 'wallet_deploymentData' });
}

/**
* Add an invoke transaction to the wallet.
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @param {AddInvokeTransactionParameters} params The parameters required for the invoke transaction.
* @returns {AddInvokeTransactionResult} The result of adding the invoke transaction.
*/
export function addInvokeTransaction(
walletWSF: WalletWithStarknetFeatures,
params: AddInvokeTransactionParameters
): Promise<AddInvokeTransactionResult> {
return walletWSF.features['starknet:walletApi'].request({
type: 'wallet_addInvokeTransaction',
params,
});
}

/**
* Add a declare transaction to the wallet.
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @param {AddDeclareTransactionParameters} params The parameters required for the declare transaction.
* @returns {AddDeclareTransactionResult} The result of adding the declare transaction.
*/
export function addDeclareTransaction(
walletWSF: WalletWithStarknetFeatures,
params: AddDeclareTransactionParameters
): Promise<AddDeclareTransactionResult> {
return walletWSF.features['starknet:walletApi'].request({
type: 'wallet_addDeclareTransaction',
params,
});
}

/**
* Sign typed data using the wallet.
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @param {TypedData} typedData The typed data to sign.
* @returns {Signature} An array of signatures as strings.
*/
export function signMessage(
walletWSF: WalletWithStarknetFeatures,
typedData: TypedData
): Promise<Signature> {
return walletWSF.features['starknet:walletApi'].request({
type: 'wallet_signTypedData',
params: typedData,
});
}

/**
* Get the list of supported Wallet API specifications.
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @returns {SpecVersion[]} An array of wallet API supported specification strings.
*/
export function supportedSpecs(walletWSF: WalletWithStarknetFeatures): Promise<SpecVersion[]> {
return walletWSF.features['starknet:walletApi'].request({ type: 'wallet_supportedSpecs' });
}

/**
* Attaches an event handler function for the changes of network and account.
* When the account/network are changed, the specified callback function will be called.
* @param {WalletWithStarknetFeatures} walletWSF - The get-starknet V5 wallet object to use.
* @param {StandardEventsChangeProperties} callback - The function to be called when the account/network are changed.
* @returns {() => void} function to execute to unsubscribe events.
*/
export function subscribeWalletEvent(
walletWSF: WalletWithStarknetFeatures,
callback: (change: StandardEventsChangeProperties) => void
): () => void {
return walletWSF.features['standard:events'].on('change', callback);
}
1 change: 1 addition & 0 deletions src/wallet/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './account';
export * from './accountV5';
Binary file removed www/docs/guides/pictures/SelectWallet.png
Binary file not shown.
Binary file added www/docs/guides/pictures/SelectWalletV4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added www/docs/guides/pictures/SelectWalletV5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified www/docs/guides/pictures/WalletAccountArchitecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading