diff --git a/src/wallet/account.ts b/src/wallet/account.ts new file mode 100644 index 000000000..765cc3641 --- /dev/null +++ b/src/wallet/account.ts @@ -0,0 +1,186 @@ +import { Account, AccountInterface } from '../account'; +import { StarknetChainId } from '../constants'; +import { ProviderInterface } from '../provider'; +import { + AllowArray, + CairoVersion, + Call, + CompiledSierra, + DeclareContractPayload, + DeployAccountContractPayload, + MultiDeployContractResponse, + ProviderOptions, + TypedData, + UniversalDeployerContractPayload, +} from '../types'; +import { CallData } from '../utils/calldata'; +import { extractContractHashes } from '../utils/contract'; +import { stringify } from '../utils/json'; +import { buildUDCCall } from '../utils/transaction'; +import { + addDeclareTransaction, + addDeployAccountTransaction, + addInvokeTransaction, + addStarknetChain, + getPermissions, + onAccountChange, + onNetworkChanged, + requestAccounts, + signMessage, + switchStarknetChain, + watchAsset, +} from './connect'; +import { + AccountChangeEventHandler, + AddStarknetChainParameters, + NetworkChangeEventHandler, + WatchAssetParameters, +} from './getst/main'; +import { StarknetWalletProvider } from './types'; + +// Represent 'Selected Active' Account inside Connected Wallet +export class WalletAccount extends Account implements AccountInterface { + public address: string = ''; + + public walletProvider: StarknetWalletProvider; + + constructor( + providerOrOptions: ProviderOptions | ProviderInterface, + walletProvider: StarknetWalletProvider, + cairoVersion?: CairoVersion + ) { + super(providerOrOptions, '', '', cairoVersion); // At this point unknown address + this.walletProvider = walletProvider; + + // Update Address on change + this.walletProvider.on('accountsChanged', (res) => { + if (!res) return; + this.address = res[0].toLowerCase(); + }); + + // Throw Error on Network change + this.walletProvider.on('networkChanged', (res) => { + if (!res) return; + throw Error('WalletAccount doest support switching chains'); + }); + + // Get and Set Address !!! Post constructor initial empty string + walletProvider + .request({ + type: 'wallet_requestAccounts', + params: { + silentMode: false, + }, + }) + .then((res) => { + this.address = res[0].toLowerCase(); + }); + } + + /** + * WALLET EVENTS + */ + public onAccountChange(callback: AccountChangeEventHandler) { + onAccountChange(this.walletProvider, callback); + } + + public onNetworkChanged(callback: NetworkChangeEventHandler) { + onNetworkChanged(this.walletProvider, callback); + } + + /** + * 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) { + const txCalls = [].concat(calls as any).map((it) => { + const { contractAddress, entrypoint, calldata } = it; + return { + contract_address: contractAddress, + 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), + }; + + // Check FIx + 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 { + const { calls, addresses } = buildUDCCall(payload, this.address); + const invokeResponse = await this.execute(calls); + + return { + ...invokeResponse, + contract_address: addresses, + }; + } + + override deployAccount(payload: DeployAccountContractPayload) { + const params = { + contract_address_salt: payload.addressSalt?.toString() || '0', + constructor_calldata: payload.constructorCalldata + ? CallData.compile(payload.constructorCalldata) + : [], + class_hash: payload.classHash, + }; + + return addDeployAccountTransaction(this.walletProvider, params); + } + + override signMessage(typedData: TypedData) { + return signMessage(this.walletProvider, typedData); + } + + // MISSING ESTIMATES +} diff --git a/src/wallet/connect.ts b/src/wallet/connect.ts new file mode 100644 index 000000000..2cb68bce5 --- /dev/null +++ b/src/wallet/connect.ts @@ -0,0 +1,162 @@ +import { + AccountChangeEventHandler, + AddDeclareTransactionParameters, + AddDeployAccountTransactionParameters, + AddInvokeTransactionParameters, + AddStarknetChainParameters, + NetworkChangeEventHandler, + StarknetChainId, + StarknetWindowObject, + TypedData, + WatchAssetParameters, +} from './getst/main'; + +/** + * Request Permission for wallet account, return addresses that's allowed by user + * @param silentMode false: request user interaction allowance. true: return only pre-allowed + * @returns allowed accounts addresses + */ +export function requestAccounts(swo: StarknetWindowObject, silentMode = false) { + return swo.request({ + type: 'wallet_requestAccounts', + params: { + silentMode, + }, + }); +} + +/** + * Request Permission for wallet account + * @returns allowed accounts addresses + */ +export function getPermissions(swo: StarknetWindowObject) { + return swo.request({ type: 'wallet_getPermissions' }); +} + +/** + * Request adding ERC20 Token to Wallet List + * @param asset WatchAssetParameters + * @returns boolean + */ +export function watchAsset(swo: StarknetWindowObject, asset: WatchAssetParameters) { + return swo.request({ + type: 'wallet_watchAsset', + params: asset, + }); +} + +/** + * Request adding custom Starknet chain + * @param chain AddStarknetChainParameters + * @returns boolean + */ +export function addStarknetChain(swo: StarknetWindowObject, chain: AddStarknetChainParameters) { + // Can this set custom RPC endpoint ? + return swo.request({ + type: 'wallet_addStarknetChain', + params: chain, + }); +} + +/** + * Request Wallet Network change + * @param chainId StarknetChainId + * @returns boolean + */ +export function switchStarknetChain(swo: StarknetWindowObject, chainId: StarknetChainId) { + return swo.request({ + type: 'wallet_switchStarknetChain', + params: { + chainId, + }, + }); +} + +/** + * Request the current chain ID from the wallet. + * @returns The current Starknet chain ID. + */ +export function requestChainId(swo: StarknetWindowObject) { + return swo.request({ type: 'wallet_requestChainId' }); +} + +/** + * Get deployment data for a contract. + * @returns The deployment data result. + */ +export function deploymentData(swo: StarknetWindowObject) { + return swo.request({ type: 'wallet_deploymentData' }); // TODO: test +} + +/** + * Add an invoke transaction to the wallet. + * @param params The parameters required for the invoke transaction. + * @returns The result of adding the invoke transaction. + */ +export function addInvokeTransaction( + swo: StarknetWindowObject, + params: AddInvokeTransactionParameters +) { + return swo.request({ + type: 'starknet_addInvokeTransaction', + params, + }); +} + +/** + * Add a declare transaction to the wallet. + * @param params The parameters required for the declare transaction. + * @returns The result of adding the declare transaction. + */ +export function addDeclareTransaction( + swo: StarknetWindowObject, + params: AddDeclareTransactionParameters +) { + return swo.request({ + type: 'starknet_addDeclareTransaction', + params, + }); +} + +/** + * Add a deploy account transaction to the wallet. + * @param params The parameters required for the deploy account transaction. + * @returns The result of adding the deploy account transaction. + */ +export function addDeployAccountTransaction( + swo: StarknetWindowObject, + params: AddDeployAccountTransactionParameters +) { + return swo.request({ + type: 'starknet_addDeployAccountTransaction', + params, + }); +} + +/** + * Sign typed data using the wallet. + * @param params The typed data to sign. + * @returns An array of signatures as strings. + */ +export function signMessage(swo: StarknetWindowObject, typedData: TypedData) { + return swo.request({ + type: 'starknet_signTypedData', + params: typedData, + }); +} + +/** + * Get the list of supported specifications. + * @returns An array of supported specification strings. + */ +export function supportedSpecs(swo: StarknetWindowObject) { + return swo.request({ type: 'starknet_supportedSpecs' }); +} + +export function onAccountChange(swo: StarknetWindowObject, callback: AccountChangeEventHandler) { + swo.on('accountsChanged', callback); +} + +export function onNetworkChanged(swo: StarknetWindowObject, callback: NetworkChangeEventHandler) { + swo.on('networkChanged', callback); +} diff --git a/src/wallet/default.ts b/src/wallet/default.ts deleted file mode 100644 index 20770b476..000000000 --- a/src/wallet/default.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { Account, AccountInterface } from '../account'; -import { - AccountChangeEventHandler, - AddDeclareTransactionResult, - AddDeployAccountTransactionResult, - AddInvokeTransactionResult, - AddStarknetChainParameters, - NetworkChangeEventHandler, - Permission, - RpcMessage, - StarknetWindowObject, - WatchAssetParameters, -} from '../account/getst/main'; -import { StarknetChainId } from '../constants'; -import { ProviderInterface } from '../provider'; -import { - AllowArray, - ArraySignatureType, - CairoVersion, - Call, - CompiledSierra, - DeclareContractPayload, - DeployAccountContractPayload, - MultiDeployContractResponse, - ProviderOptions, - TypedData, - UniversalDeployerContractPayload, -} from '../types'; -import { CallData } from '../utils/calldata'; -import { extractContractHashes } from '../utils/contract'; -import { stringify } from '../utils/json'; -import { buildUDCCall } from '../utils/transaction'; - -// ---- TT Request Handler -type RpcCall = Omit; - -// -- TT Better naming -// This is provider object expected by WalletAccount to communicate with wallet -interface StarknetWalletProvider extends StarknetWindowObject {} - -// Represent 'Selected Active' Account inside Connected Wallet -export class WalletAccount extends Account implements AccountInterface { - public address: string = ''; - - public walletProvider: StarknetWalletProvider; - - constructor( - providerOrOptions: ProviderOptions | ProviderInterface, - walletProvider: StarknetWalletProvider, - cairoVersion?: CairoVersion - ) { - super(providerOrOptions, '', '', cairoVersion); // At this point unknown address - this.walletProvider = walletProvider; - - // Address change Event Listeners - this.walletProvider.on('accountsChanged', (res) => { - if (!res) return; - this.address = res[0].toLowerCase(); - console.log('Setting new address', res[0].toLowerCase()); - }); - - // Network change Event Listeners - this.walletProvider.on('networkChanged', (res) => { - if (!res) return; - console.log('Setting new network', res.toLowerCase()); - throw Error('WalletAccount doest support switching chains'); - }); - - // Get and Set Address !!! Post constructor initial it is '' - walletProvider - .request({ - type: 'wallet_requestAccounts', - params: { - silentMode: false, - }, - }) - .then((res) => { - this.address = res[0].toLowerCase(); - }); - } - - /** - * WALLET EVENTS - */ - public onAccountChange(callback: AccountChangeEventHandler) { - this.walletProvider.on('accountsChanged', callback); - } - - public onNetworkChanged(callback: NetworkChangeEventHandler) { - this.walletProvider.on('networkChanged', callback); - } - - /** - * WALLET SPECIFIC METHODS - */ - - /** - * Request Permission for wallet account, return addresses that's allowed by user - * @param silentMode false: request user interaction allowance. true: return only pre-allowed - * @returns allowed accounts addresses - */ - public requestAccounts(silentMode = false) { - const rpcCall: RpcCall = { - type: 'wallet_requestAccounts', - params: { - silentMode, - }, - }; - return this.walletProvider.request(rpcCall) as Promise; - } - - /** - * Request Permission for wallet account - * @returns allowed accounts addresses - */ - public getPermissions() { - const rpcCall: RpcCall = { - type: 'wallet_getPermissions', - }; - return this.walletProvider.request(rpcCall) as Promise; - } - - /** - * Request Wallet Network change - * @param chainId StarknetChainId - * @returns boolean - */ - public switchStarknetChain(chainId: StarknetChainId) { - const rpcCall: RpcCall = { - type: 'wallet_switchStarknetChain', - params: { - chainId, - }, - }; - return this.walletProvider.request(rpcCall) as Promise; - } - - /** - * Request adding ERC20 Token to Wallet List - * @param asset WatchAssetParameters - * @returns boolean - */ - public watchAsset(asset: WatchAssetParameters) { - const rpcCall: RpcCall = { - type: 'wallet_watchAsset', - params: asset, - }; - return this.walletProvider.request(rpcCall) as Promise; - } - - /** - * Request adding custom Starknet chain - * @param chain AddStarknetChainParameters - * @returns boolean - */ - public addStarknetChain(chain: AddStarknetChainParameters) { - // Can this set custom RPC endpoint ? - const rpcCall: RpcCall = { - type: 'wallet_addStarknetChain', - params: chain, - }; - return this.walletProvider.request(rpcCall) as Promise; - } - - /** - * ACCOUNT METHODS - */ - override execute(calls: AllowArray) { - const txCalls = [].concat(calls as any).map((it) => { - const { contractAddress, entrypoint, calldata } = it; - return { - contract_address: contractAddress, - entrypoint, - calldata, - }; - }); - - const rpcCall: RpcCall = { - type: 'starknet_addInvokeTransaction', - params: { - calls: txCalls, - }, - }; - return this.walletProvider.request(rpcCall) as Promise; - } - - 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), - }; - - // Check FIx - if (!declareContractPayload.compiledClassHash) { - throw Error('compiledClassHash is required'); - } - - const rpcCall: RpcCall = { - type: 'starknet_addDeclareTransaction', - params: { - compiled_class_hash: declareContractPayload.compiledClassHash, - contract_class: cairo1Contract, - }, - }; - return this.walletProvider.request(rpcCall) as Promise; - } - - override async deploy( - payload: UniversalDeployerContractPayload | UniversalDeployerContractPayload[] - ): Promise { - const { calls, addresses } = buildUDCCall(payload, this.address); - const invokeResponse = await this.execute(calls); - - return { - ...invokeResponse, - contract_address: addresses, - }; - } - - override deployAccount(payload: DeployAccountContractPayload) { - const rpcCall: RpcCall = { - type: 'starknet_addDeployAccountTransaction', - params: { - contract_address_salt: payload.addressSalt?.toString() || '0', - constructor_calldata: payload.constructorCalldata - ? CallData.compile(payload.constructorCalldata) - : [], - class_hash: payload.classHash, - }, - }; - return this.walletProvider.request(rpcCall) as Promise; - } - - override signMessage(typedData: TypedData) { - const rpcCall: RpcCall = { - type: 'starknet_signTypedData', - params: typedData, - }; - return this.walletProvider.request(rpcCall) as Promise; - } - - // MISSING ESTIMATES -} diff --git a/src/account/getst/StarknetWindowObject.d.ts b/src/wallet/getst/StarknetWindowObject.d.ts similarity index 100% rename from src/account/getst/StarknetWindowObject.d.ts rename to src/wallet/getst/StarknetWindowObject.d.ts diff --git a/src/account/getst/core.d.ts b/src/wallet/getst/core.d.ts similarity index 100% rename from src/account/getst/core.d.ts rename to src/wallet/getst/core.d.ts diff --git a/src/account/getst/discovery.d.ts b/src/wallet/getst/discovery.d.ts similarity index 100% rename from src/account/getst/discovery.d.ts rename to src/wallet/getst/discovery.d.ts diff --git a/src/account/getst/localStorageStore.d.ts b/src/wallet/getst/localStorageStore.d.ts similarity index 100% rename from src/account/getst/localStorageStore.d.ts rename to src/wallet/getst/localStorageStore.d.ts diff --git a/src/account/getst/main.d.ts b/src/wallet/getst/main.d.ts similarity index 100% rename from src/account/getst/main.d.ts rename to src/wallet/getst/main.d.ts diff --git a/src/account/getst/rpcMessage.d.ts b/src/wallet/getst/rpcMessage.d.ts similarity index 100% rename from src/account/getst/rpcMessage.d.ts rename to src/wallet/getst/rpcMessage.d.ts diff --git a/src/account/getst/types.d.ts b/src/wallet/getst/types.d.ts similarity index 100% rename from src/account/getst/types.d.ts rename to src/wallet/getst/types.d.ts diff --git a/src/account/getst/utils.d.ts b/src/wallet/getst/utils.d.ts similarity index 100% rename from src/account/getst/utils.d.ts rename to src/wallet/getst/utils.d.ts diff --git a/src/account/getst/wallet/filter.d.ts b/src/wallet/getst/wallet/filter.d.ts similarity index 100% rename from src/account/getst/wallet/filter.d.ts rename to src/wallet/getst/wallet/filter.d.ts diff --git a/src/account/getst/wallet/isWalletObject.d.ts b/src/wallet/getst/wallet/isWalletObject.d.ts similarity index 100% rename from src/account/getst/wallet/isWalletObject.d.ts rename to src/wallet/getst/wallet/isWalletObject.d.ts diff --git a/src/account/getst/wallet/scan.d.ts b/src/wallet/getst/wallet/scan.d.ts similarity index 100% rename from src/account/getst/wallet/scan.d.ts rename to src/wallet/getst/wallet/scan.d.ts diff --git a/src/account/getst/wallet/sort.d.ts b/src/wallet/getst/wallet/sort.d.ts similarity index 100% rename from src/account/getst/wallet/sort.d.ts rename to src/wallet/getst/wallet/sort.d.ts diff --git a/src/account/getst/walletEvents.d.ts b/src/wallet/getst/walletEvents.d.ts similarity index 100% rename from src/account/getst/walletEvents.d.ts rename to src/wallet/getst/walletEvents.d.ts diff --git a/src/wallet/index.ts b/src/wallet/index.ts index acced897e..7c589241b 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -1 +1,2 @@ -export * from './default'; +export * from './account'; +export * from './connect'; diff --git a/src/wallet/types.ts b/src/wallet/types.ts new file mode 100644 index 000000000..89bd0dc8c --- /dev/null +++ b/src/wallet/types.ts @@ -0,0 +1,8 @@ +import { RpcMessage, StarknetWindowObject } from './getst/main'; + +// ---- TT Request Handler +export type RpcCall = Omit; + +// -- TT Better naming +// This is provider object expected by WalletAccount to communicate with wallet +export interface StarknetWalletProvider extends StarknetWindowObject {}