From de921af3eaabdcaf017184bd255f7b0f25046bb2 Mon Sep 17 00:00:00 2001 From: delivan Date: Fri, 11 Oct 2024 16:34:41 +0900 Subject: [PATCH 1/6] Show starknet rpc on endpoint change page --- .../pages/setting/advanced/endpoint/index.tsx | 66 +++++++++++++------ 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/apps/extension/src/pages/setting/advanced/endpoint/index.tsx b/apps/extension/src/pages/setting/advanced/endpoint/index.tsx index bc12c5eace..83064c434b 100644 --- a/apps/extension/src/pages/setting/advanced/endpoint/index.tsx +++ b/apps/extension/src/pages/setting/advanced/endpoint/index.tsx @@ -44,32 +44,43 @@ export const SettingAdvancedEndpointPage: FunctionComponent = observer(() => { const intl = useIntl(); const [chainId, setChainId] = useState( - chainStore.chainInfos[0].chainId + chainStore.modularChainInfos[0].chainId ); const [originalEndpoint, setOriginalEndpoint] = useState< | { rpc: string; - rest: string; + rest?: string; evmRpc?: string; } | undefined >(); const [isLoading, setIsLoading] = useState(false); - const chainInfo = chainStore.getChain(chainId); + const chainInfo = (() => { + const modularChainInfo = chainStore.getModularChain(chainId); + + if ("starknet" in modularChainInfo) { + return modularChainInfo.starknet; + } + + return chainStore.getChain(chainId); + })(); + const hasRestEndpoint = "rest" in chainInfo; + const hasEvmEndpoint = "evm" in chainInfo && chainInfo.evm != null; + const { setValue, watch, register, handleSubmit } = useForm<{ rpc: string; - lcd: string; + lcd?: string; evmRpc?: string; }>({ defaultValues: { - rpc: chainStore.getChain(chainId).rpc, - lcd: chainStore.getChain(chainId).rest, - evmRpc: chainStore.getChain(chainId).evm?.rpc, + rpc: chainInfo.rpc, + ...(hasRestEndpoint && { lcd: chainInfo.rest }), + ...(hasEvmEndpoint && { evmRpc: chainInfo.evm.rpc }), }, }); - const chainList = chainStore.chainInfosInUI.map((chainInfo) => { + const chainList = chainStore.modularChainInfosInUI.map((chainInfo) => { return { key: chainInfo.chainId, label: chainInfo.chainName, @@ -78,8 +89,12 @@ export const SettingAdvancedEndpointPage: FunctionComponent = observer(() => { useEffect(() => { setValue("rpc", chainInfo.rpc); - setValue("lcd", chainInfo.rest); - setValue("evmRpc", chainInfo.evm?.rpc); + if (hasRestEndpoint) { + setValue("lcd", chainInfo.rest); + } + if (hasEvmEndpoint) { + setValue("evmRpc", chainInfo.evm.rpc); + } const msg = new GetChainOriginalEndpointsMsg(chainId); new InExtensionMessageRequester() @@ -92,7 +107,19 @@ export const SettingAdvancedEndpointPage: FunctionComponent = observer(() => { setOriginalEndpoint(undefined); }); - }, [chainId, chainInfo, setValue]); + }, [chainId, chainInfo, hasEvmEndpoint, hasRestEndpoint, setValue]); + + const isEndpointNothingChanged = (() => { + const isRpcChanged = chainInfo.rpc !== watch("rpc"); + const isLcdChanged = hasRestEndpoint + ? chainInfo.rest === watch("lcd") + : false; + const isEvmRpcChanged = hasEvmEndpoint + ? chainInfo.evm.rpc === watch("evmRpc") + : false; + + return !isRpcChanged && !isLcdChanged && !isEvmRpcChanged; + })(); return ( { color: "primary", size: "large", isLoading, - disabled: - chainInfo.rpc === watch("rpc") && - chainInfo.rest === watch("lcd") && - chainInfo.evm?.rpc === watch("evmRpc"), + disabled: isEndpointNothingChanged, }} onSubmit={handleSubmit(async (data) => { setIsLoading(true); @@ -147,7 +171,11 @@ export const SettingAdvancedEndpointPage: FunctionComponent = observer(() => { } } - if (originalEndpoint?.rest !== data.lcd) { + if ( + hasRestEndpoint && + data.lcd != undefined && + originalEndpoint?.rest !== data.lcd + ) { try { await checkRestConnectivity(chainId, data.lcd); } catch (e) { @@ -171,8 +199,8 @@ export const SettingAdvancedEndpointPage: FunctionComponent = observer(() => { } if ( + hasEvmEndpoint && data.evmRpc != null && - chainInfo.evm != null && originalEndpoint?.evmRpc !== data.evmRpc ) { await checkEvmRpcConnectivity( @@ -269,8 +297,8 @@ export const SettingAdvancedEndpointPage: FunctionComponent = observer(() => { - - {chainInfo.evm && ( + {hasRestEndpoint && } + {hasEvmEndpoint && ( From a44d9bd37050a9276178ddb664b640d0429c27c3 Mon Sep 17 00:00:00 2001 From: delivan Date: Fri, 11 Oct 2024 18:59:07 +0900 Subject: [PATCH 2/6] Implement a logic to update starknet rpc endpoint --- .../pages/setting/advanced/endpoint/index.tsx | 9 +++- packages/background/src/chains/messages.ts | 3 +- packages/background/src/chains/service.ts | 35 ++++++++++++--- packages/chain-validator/src/connection.ts | 44 +++++++++++++++++++ 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/apps/extension/src/pages/setting/advanced/endpoint/index.tsx b/apps/extension/src/pages/setting/advanced/endpoint/index.tsx index 83064c434b..5cbd13cee6 100644 --- a/apps/extension/src/pages/setting/advanced/endpoint/index.tsx +++ b/apps/extension/src/pages/setting/advanced/endpoint/index.tsx @@ -16,6 +16,7 @@ import { checkEvmRpcConnectivity, checkRestConnectivity, checkRPCConnectivity, + checkStarknetRpcConnectivity, DifferentChainVersionError, } from "@keplr-wallet/chain-validator"; import { useNotification } from "../../../../hooks/notification"; @@ -144,13 +145,17 @@ export const SettingAdvancedEndpointPage: FunctionComponent = observer(() => { if ( !originalEndpoint || originalEndpoint.rpc !== data.rpc || - originalEndpoint.rpc !== data.lcd || + originalEndpoint.rest !== data.lcd || originalEndpoint.evmRpc !== data.evmRpc ) { try { if (originalEndpoint?.rpc !== data.rpc) { try { - await checkRPCConnectivity(chainId, data.rpc); + if (chainId.startsWith("starknet:")) { + await checkStarknetRpcConnectivity(chainId, data.rpc); + } else { + await checkRPCConnectivity(chainId, data.rpc); + } } catch (e) { if ( // In the case of this error, the chain version is different. diff --git a/packages/background/src/chains/messages.ts b/packages/background/src/chains/messages.ts index 178ce8a4fd..e974b899d8 100644 --- a/packages/background/src/chains/messages.ts +++ b/packages/background/src/chains/messages.ts @@ -270,7 +270,8 @@ export class ClearChainEndpointsMsg extends Message<{ export class GetChainOriginalEndpointsMsg extends Message<{ rpc: string; - rest: string; + rest?: string; + evmRpc?: string; }> { public static type() { return "get-chain-original-endpoints"; diff --git a/packages/background/src/chains/service.ts b/packages/background/src/chains/service.ts index 3f3eaf4d71..1ddb039b87 100644 --- a/packages/background/src/chains/service.ts +++ b/packages/background/src/chains/service.ts @@ -909,21 +909,32 @@ export class ChainsService { chainId: string ): { rpc: string; - rest: string; + rest?: string; evmRpc?: string; } => { const identifier = ChainIdHelper.parse(chainId).identifier; const originalChainInfos = this.embedChainInfos.concat( this.suggestedChainInfos ); - const chainInfo = originalChainInfos.find( - (c) => ChainIdHelper.parse(c.chainId).identifier === identifier - ); + const starknetChainInfos = this.modularChainInfos.reduce((acc, cur) => { + if ("starknet" in cur) { + acc.push(cur.starknet); + } + return acc; + }, [] as StarknetChainInfo[]); + const chainInfo = + originalChainInfos.find( + (c) => ChainIdHelper.parse(c.chainId).identifier === identifier + ) || + starknetChainInfos.find( + (c) => ChainIdHelper.parse(c.chainId).identifier === identifier + ); if (chainInfo) { return { rpc: chainInfo.rpc, - rest: chainInfo.rest, - evmRpc: chainInfo.evm?.rpc, + ...("rest" in chainInfo && { rest: chainInfo.rest }), + ...("evm" in chainInfo && + chainInfo.evm != null && { evmRpc: chainInfo.evm.rpc }), }; } @@ -1148,6 +1159,18 @@ export class ChainsService { cosmos, }; } + + // TODO: `mergeModularChainInfosWithDynamics` 같은 메소드로 빼기 + if ("starknet" in modularChainInfo) { + const endpoint = this.getEndpoint(modularChainInfo.chainId); + return { + ...modularChainInfo, + starknet: { + ...modularChainInfo.starknet, + rpc: endpoint?.rpc || modularChainInfo.starknet.rpc, + }, + }; + } return modularChainInfo; }); }, diff --git a/packages/chain-validator/src/connection.ts b/packages/chain-validator/src/connection.ts index 8bc52bd5b6..c4cb42ad47 100644 --- a/packages/chain-validator/src/connection.ts +++ b/packages/chain-validator/src/connection.ts @@ -187,3 +187,47 @@ export async function checkEvmRpcConnectivity( ); } } + +export async function checkStarknetRpcConnectivity( + chainId: string, + rpc: string +) { + const starknetChainId = chainId.split(":")[1]; + let resultStarknetChainId: SimpleFetchResponse<{ + result: string; + }>; + + try { + resultStarknetChainId = await simpleFetch<{ + result: string; + }>(rpc, { + method: "POST", + headers: { + "content-type": "application/json", + "request-source": "keplr-wallet-extension/chain-validator", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "starknet_chainId", + params: [], + id: 1, + }), + }); + } catch (e) { + console.log(e); + throw new Error( + "Failed to get response starknet_chainId from Starknet RPC endpoint" + ); + } + + const starknetChainIdFromResult = Buffer.from( + resultStarknetChainId.data.result.replace("0x", ""), + "hex" + ).toString(); + + if (starknetChainIdFromResult !== starknetChainId) { + throw new Error( + `Starknet RPC endpoint has different chain id (expected: ${starknetChainId}, actual: ${starknetChainIdFromResult})` + ); + } +} From 24df71d8c1381cefc3a43ac33a661c4fcc0d4f1c Mon Sep 17 00:00:00 2001 From: delivan Date: Fri, 11 Oct 2024 22:03:48 +0900 Subject: [PATCH 3/6] Implement token scan logic for starknet --- .../components/token-found-modal/index.tsx | 113 +++++-- apps/extension/src/stores/chain/index.tsx | 3 +- packages/background/src/index.ts | 3 +- packages/background/src/token-scan/service.ts | 295 +++++++++++------- 4 files changed, 277 insertions(+), 137 deletions(-) diff --git a/apps/extension/src/pages/main/components/token-found-modal/index.tsx b/apps/extension/src/pages/main/components/token-found-modal/index.tsx index 21a31bae2c..ef98336c6f 100644 --- a/apps/extension/src/pages/main/components/token-found-modal/index.tsx +++ b/apps/extension/src/pages/main/components/token-found-modal/index.tsx @@ -318,7 +318,11 @@ const FoundChainView: FunctionComponent<{ @@ -332,7 +336,12 @@ const FoundChainView: FunctionComponent<{ : ColorPalette["gray-10"] } > - {chainStore.getChain(tokenScan.chainId).chainName} + { + (chainStore.hasChain(tokenScan.chainId) + ? chainStore.getChain(tokenScan.chainId) + : chainStore.getModularChain(tokenScan.chainId) + ).chainName + } {numTokens} Tokens @@ -391,7 +400,11 @@ const FoundTokenView: FunctionComponent<{ - { - chainStore - .getChain(chainId) - .forceFindCurrency(asset.currency.coinMinimalDenom).coinDenom - } + {(() => { + if (chainStore.hasChain(chainId)) { + return chainStore + .getChain(chainId) + .forceFindCurrency(asset.currency.coinMinimalDenom).coinDenom; + } else { + const modularChainInfo = chainStore.getModularChain(chainId); + if ("starknet" in modularChainInfo) { + return ( + chainStore + .getModularChainInfoImpl(chainId) + .getCurrencies("starknet") + .find( + (cur) => + cur.coinMinimalDenom === asset.currency.coinMinimalDenom + )?.coinDenom ?? asset.currency.coinDenom + ); + } else if ("cosmos" in modularChainInfo) { + return ( + chainStore + .getModularChainInfoImpl(chainId) + .getCurrencies("cosmos") + .find( + (cur) => + cur.coinMinimalDenom === asset.currency.coinMinimalDenom + )?.coinDenom ?? asset.currency.coinDenom + ); + } else { + return asset.currency.coinDenom; + } + } + })()} @@ -421,20 +461,49 @@ const FoundTokenView: FunctionComponent<{ : ColorPalette["gray-50"] } > - {uiConfigStore.hideStringIfPrivacyMode( - new CoinPretty( - chainStore - .getChain(chainId) - .forceFindCurrency(asset.currency.coinMinimalDenom), - asset.amount - ) - .shrink(true) - .trim(true) - .maxDecimals(6) - .inequalitySymbol(true) - .toString(), - 2 - )} + {(() => { + const currency = (() => { + if (chainStore.hasChain(chainId)) { + return chainStore + .getChain(chainId) + .forceFindCurrency(asset.currency.coinMinimalDenom); + } else { + const modularChainInfo = chainStore.getModularChain(chainId); + if ("starknet" in modularChainInfo) { + return ( + chainStore + .getModularChainInfoImpl(chainId) + .getCurrencies("starknet") + .find( + (cur) => + cur.coinMinimalDenom === asset.currency.coinMinimalDenom + ) ?? asset.currency + ); + } else if ("cosmos" in modularChainInfo) { + return ( + chainStore + .getModularChainInfoImpl(chainId) + .getCurrencies("cosmos") + .find( + (cur) => + cur.coinMinimalDenom === asset.currency.coinMinimalDenom + ) ?? asset.currency + ); + } else { + return asset.currency; + } + } + })(); + return uiConfigStore.hideStringIfPrivacyMode( + new CoinPretty(currency, asset.amount) + .shrink(true) + .trim(true) + .maxDecimals(6) + .inequalitySymbol(true) + .toString(), + 2 + ); + })()} ); diff --git a/apps/extension/src/stores/chain/index.tsx b/apps/extension/src/stores/chain/index.tsx index 7ee33a0b29..5fdaa65517 100644 --- a/apps/extension/src/stores/chain/index.tsx +++ b/apps/extension/src/stores/chain/index.tsx @@ -118,7 +118,7 @@ export class ChainStore extends BaseChainStore { @computed get tokenScans(): TokenScan[] { return this._tokenScans.filter((scan) => { - if (!this.hasChain(scan.chainId)) { + if (!this.hasChain(scan.chainId) && !this.hasModularChain(scan.chainId)) { return false; } @@ -413,6 +413,7 @@ export class ChainStore extends BaseChainStore { BACKGROUND_PORT, new RevalidateTokenScansMsg(id) ); + if (res.vaultId === this.keyRingStore.selectedKeyInfo?.id) { runInAction(() => { this._tokenScans = res.tokenScans; diff --git a/packages/background/src/index.ts b/packages/background/src/index.ts index 0840d4f4d1..e707523803 100644 --- a/packages/background/src/index.ts +++ b/packages/background/src/index.ts @@ -264,7 +264,8 @@ export function init( chainsUIService, vaultService, keyRingV2Service, - keyRingCosmosService + keyRingCosmosService, + keyRingStarknetService ); const recentSendHistoryService = diff --git a/packages/background/src/token-scan/service.ts b/packages/background/src/token-scan/service.ts index 73bb389aae..e0245cebb7 100644 --- a/packages/background/src/token-scan/service.ts +++ b/packages/background/src/token-scan/service.ts @@ -9,12 +9,15 @@ import { Dec } from "@keplr-wallet/unit"; import { ChainIdHelper } from "@keplr-wallet/cosmos"; import { VaultService } from "../vault"; import { KVStore } from "@keplr-wallet/common"; +import { KeyRingStarknetService } from "src/keyring-starknet"; +import { CairoUint256 } from "starknet"; export type TokenScan = { chainId: string; infos: { - bech32Address: string; + bech32Address?: string; ethereumHexAddress?: string; + starknetHexAddress?: string; coinType?: number; assets: { currency: AppCurrency; @@ -33,7 +36,8 @@ export class TokenScanService { protected readonly chainsUIService: ChainsUIService, protected readonly vaultService: VaultService, protected readonly keyRingService: KeyRingService, - protected readonly keyRingCosmosService: KeyRingCosmosService + protected readonly keyRingCosmosService: KeyRingCosmosService, + protected readonly keyRingStarknetService: KeyRingStarknetService ) { makeObservable(this); } @@ -83,16 +87,17 @@ export class TokenScanService { } getTokenScans(vaultId: string): TokenScan[] { - return (this.vaultToMap.get(vaultId) ?? []) - .filter((tokenScan) => { - return this.chainsService.hasChainInfo(tokenScan.chainId); - }) - .sort((a, b) => { - // Sort by chain name - const aChainInfo = this.chainsService.getChainInfoOrThrow(a.chainId); - const bChainInfo = this.chainsService.getChainInfoOrThrow(b.chainId); - return aChainInfo.chainName.localeCompare(bChainInfo.chainName); - }); + return (this.vaultToMap.get(vaultId) ?? []).sort((a, b) => { + // Sort by chain name + const aChainInfo = this.chainsService.hasChainInfo(a.chainId) + ? this.chainsService.getChainInfoOrThrow(a.chainId) + : this.chainsService.getModularChainInfoOrThrow(a.chainId); + const bModualrChainInfo = this.chainsService.hasChainInfo(b.chainId) + ? this.chainsService.getChainInfoOrThrow(b.chainId) + : this.chainsService.getModularChainInfoOrThrow(b.chainId); + + return aChainInfo.chainName.localeCompare(bModualrChainInfo.chainName); + }); } protected async scanWithAllVaults(chainId: string): Promise { @@ -169,8 +174,8 @@ export class TokenScanService { return; } - const chainInfos = this.chainsService - .getChainInfos() + const modularChainInfos = this.chainsService + .getModularChainInfos() .filter( (chainInfo) => !this.chainsUIService.isEnabled(vaultId, chainInfo.chainId) @@ -178,12 +183,12 @@ export class TokenScanService { const tokenScans: TokenScan[] = []; const promises: Promise[] = []; - for (const chainInfo of chainInfos) { + for (const modularChainInfo of modularChainInfos) { promises.push( (async () => { const tokenScan = await this.calculateTokenScan( vaultId, - chainInfo.chainId + modularChainInfo.chainId ); if (tokenScan) { @@ -231,11 +236,6 @@ export class TokenScanService { return; } - const chainInfo = this.chainsService.getChainInfoOrThrow(chainId); - if (chainInfo.hideInUI) { - return; - } - if (this.chainsUIService.isEnabled(vaultId, chainId)) { return; } @@ -245,110 +245,179 @@ export class TokenScanService { infos: [], }; - if (this.chainsService.isEvmOnlyChain(chainId)) { - const evmInfo = this.chainsService.getEVMInfoOrThrow(chainId); - const pubkey = await this.keyRingService.getPubKey(chainId, vaultId); - const ethereumHexAddress = `0x${Buffer.from( - pubkey.getEthAddress() - ).toString("hex")}`; - - const res = await simpleFetch<{ - result: string; - }>(evmInfo.rpc, { - method: "POST", - headers: { - "content-type": "application/json", - "request-source": new URL(browser.runtime.getURL("/")).origin, - }, - body: JSON.stringify({ - jsonrpc: "2.0", - method: "eth_getBalance", - params: [ethereumHexAddress, "latest"], - id: 1, - }), - }); + const modularChainInfo = this.chainsService.getModularChainInfo(chainId); + if (modularChainInfo == null) { + return; + } - if (res.status === 200 && BigInt(res.data.result).toString(10) !== "0") { - tokenScan.infos.push({ - bech32Address: "", - ethereumHexAddress, - coinType: 60, - assets: [ - { - currency: chainInfo.stakeCurrency ?? chainInfo.currencies[0], - amount: BigInt(res.data.result).toString(10), - }, - ], - }); + if ("cosmos" in modularChainInfo) { + const chainInfo = this.chainsService.getChainInfoOrThrow(chainId); + if (chainInfo.hideInUI) { + return; } - } else { - const bech32Addresses: { - value: string; - coinType?: number; - }[] = await (async () => { - if (this.keyRingService.needKeyCoinTypeFinalize(vaultId, chainId)) { - return ( - await this.keyRingCosmosService.computeNotFinalizedKeyAddresses( - vaultId, - chainId - ) - ).map((addr) => { - return { - value: addr.bech32Address, - coinType: addr.coinType, - }; + + if (this.chainsService.isEvmOnlyChain(chainId)) { + const evmInfo = this.chainsService.getEVMInfoOrThrow(chainId); + const pubkey = await this.keyRingService.getPubKey(chainId, vaultId); + const ethereumHexAddress = `0x${Buffer.from( + pubkey.getEthAddress() + ).toString("hex")}`; + + const res = await simpleFetch<{ + result: string; + }>(evmInfo.rpc, { + method: "POST", + headers: { + "content-type": "application/json", + "request-source": new URL(browser.runtime.getURL("/")).origin, + }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "eth_getBalance", + params: [ethereumHexAddress, "latest"], + id: 1, + }), + }); + + if ( + res.status === 200 && + BigInt(res.data.result).toString(10) !== "0" + ) { + tokenScan.infos.push({ + bech32Address: "", + ethereumHexAddress, + coinType: 60, + assets: [ + { + currency: chainInfo.stakeCurrency ?? chainInfo.currencies[0], + amount: BigInt(res.data.result).toString(10), + }, + ], }); - } else { - return [ - { - value: (await this.keyRingCosmosService.getKey(vaultId, chainId)) - .bech32Address, - }, - ]; } - })(); + } else { + const bech32Addresses: { + value: string; + coinType?: number; + }[] = await (async () => { + if (this.keyRingService.needKeyCoinTypeFinalize(vaultId, chainId)) { + return ( + await this.keyRingCosmosService.computeNotFinalizedKeyAddresses( + vaultId, + chainId + ) + ).map((addr) => { + return { + value: addr.bech32Address, + coinType: addr.coinType, + }; + }); + } else { + return [ + { + value: ( + await this.keyRingCosmosService.getKey(vaultId, chainId) + ).bech32Address, + }, + ]; + } + })(); + + for (const bech32Address of bech32Addresses) { + const res = await simpleFetch<{ + balances: { denom: string; amount: string }[]; + }>( + chainInfo.rest, + `/cosmos/bank/v1beta1/balances/${bech32Address.value}?pagination.limit=1000` + ); - for (const bech32Address of bech32Addresses) { - const res = await simpleFetch<{ - balances: { denom: string; amount: string }[]; - }>( - chainInfo.rest, - `/cosmos/bank/v1beta1/balances/${bech32Address.value}?pagination.limit=1000` - ); - - if (res.status === 200) { - const assets: TokenScan["infos"][number]["assets"] = []; - - const balances = res.data?.balances ?? []; - for (const bal of balances) { - const currency = chainInfo.currencies.find( - (cur) => cur.coinMinimalDenom === bal.denom - ); - if (currency) { - // validate - if (typeof bal.amount !== "string") { - throw new Error("Invalid amount"); - } + if (res.status === 200) { + const assets: TokenScan["infos"][number]["assets"] = []; - const dec = new Dec(bal.amount); - if (dec.gt(new Dec(0))) { - assets.push({ - currency, - amount: bal.amount, - }); + const balances = res.data?.balances ?? []; + for (const bal of balances) { + const currency = chainInfo.currencies.find( + (cur) => cur.coinMinimalDenom === bal.denom + ); + if (currency) { + // validate + if (typeof bal.amount !== "string") { + throw new Error("Invalid amount"); + } + + const dec = new Dec(bal.amount); + if (dec.gt(new Dec(0))) { + assets.push({ + currency, + amount: bal.amount, + }); + } } } - } - if (assets.length > 0) { - tokenScan.infos.push({ - bech32Address: bech32Address.value, - coinType: bech32Address.coinType, - assets, - }); + if (assets.length > 0) { + tokenScan.infos.push({ + bech32Address: bech32Address.value, + coinType: bech32Address.coinType, + assets, + }); + } } } } + } else if ("starknet" in modularChainInfo) { + const { hexAddress: starknetHexAddress } = + await this.keyRingStarknetService.getStarknetKey(vaultId, chainId); + + await Promise.all( + modularChainInfo.starknet.currencies.map(async (currency) => { + const res = await simpleFetch<{ + result: string[]; + }>(modularChainInfo.starknet.rpc, { + method: "POST", + headers: { + "content-type": "application/json", + "request-source": new URL(browser.runtime.getURL("/")).origin, + }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "starknet_call", + params: { + block_id: "latest", + request: { + contract_address: currency.contractAddress, + calldata: [starknetHexAddress], + // selector.getSelectorFromName("balanceOf") + entry_point_selector: + "0x2e4263afad30923c891518314c3c95dbe830a16874e8abc5777a9a20b54c76e", + }, + }, + id: 1, + }), + }); + + if (res.status === 200) { + const amount = new CairoUint256({ + low: res.data.result[0], + high: res.data.result[1], + }) + .toBigInt() + .toString(10); + + if (amount !== "0") { + tokenScan.infos.push({ + starknetHexAddress, + assets: [ + { + currency, + amount, + }, + ], + }); + } + } + }) + ); } if (tokenScan.infos.length > 0) { From f9d38a177e5d94f22ee97663d52f8a3599048df3 Mon Sep 17 00:00:00 2001 From: delivan Date: Fri, 11 Oct 2024 22:59:30 +0900 Subject: [PATCH 4/6] Make starknet by token scan to be enabled on token found modal --- .../components/token-found-modal/index.tsx | 48 +++++++++------ packages/background/src/chains/service.ts | 58 +++++++++++-------- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/apps/extension/src/pages/main/components/token-found-modal/index.tsx b/apps/extension/src/pages/main/components/token-found-modal/index.tsx index ef98336c6f..913e59beb7 100644 --- a/apps/extension/src/pages/main/components/token-found-modal/index.tsx +++ b/apps/extension/src/pages/main/components/token-found-modal/index.tsx @@ -87,32 +87,42 @@ export const TokenFoundModal: FunctionComponent<{ const tokenScans = chainStore.tokenScans.slice(); for (const enable of enables) { - if ( - keyRingStore.needKeyCoinTypeFinalize( - keyRingStore.selectedKeyInfo.id, - chainStore.getChain(enable) - ) - ) { + if (chainStore.hasChain(enable)) { + if ( + keyRingStore.needKeyCoinTypeFinalize( + keyRingStore.selectedKeyInfo.id, + chainStore.getChain(enable) + ) + ) { + const tokenScan = tokenScans.find((tokenScan) => { + return ChainIdHelper.parse(tokenScan.chainId).identifier === enable; + }); + + if (tokenScan && tokenScan.infos.length > 1) { + needBIP44Selects.push(enable); + enables.splice(enables.indexOf(enable), 1); + } + + if ( + tokenScan && + tokenScan.infos.length === 1 && + tokenScan.infos[0].coinType != null + ) { + await keyRingStore.finalizeKeyCoinType( + keyRingStore.selectedKeyInfo.id, + enable, + tokenScan.infos[0].coinType + ); + } + } + } else { const tokenScan = tokenScans.find((tokenScan) => { return ChainIdHelper.parse(tokenScan.chainId).identifier === enable; }); if (tokenScan && tokenScan.infos.length > 1) { - needBIP44Selects.push(enable); enables.splice(enables.indexOf(enable), 1); } - - if ( - tokenScan && - tokenScan.infos.length === 1 && - tokenScan.infos[0].coinType != null - ) { - await keyRingStore.finalizeKeyCoinType( - keyRingStore.selectedKeyInfo.id, - enable, - tokenScan.infos[0].coinType - ); - } } } diff --git a/packages/background/src/chains/service.ts b/packages/background/src/chains/service.ts index 1ddb039b87..1b2048d182 100644 --- a/packages/background/src/chains/service.ts +++ b/packages/background/src/chains/service.ts @@ -1149,30 +1149,42 @@ export class ChainsService { getModularChainInfos = computedFn( (): ModularChainInfo[] => { - return this.modularChainInfos.slice().map((modularChainInfo) => { - if (this.hasChainInfo(modularChainInfo.chainId)) { - const cosmos = this.getChainInfoOrThrow(modularChainInfo.chainId); - return { - chainId: cosmos.chainId, - chainName: cosmos.chainName, - chainSymbolImageUrl: cosmos.chainSymbolImageUrl, - cosmos, - }; - } + return this.modularChainInfos + .slice() + .map((modularChainInfo) => { + if (this.hasChainInfo(modularChainInfo.chainId)) { + const cosmos = this.getChainInfoOrThrow(modularChainInfo.chainId); + return { + chainId: cosmos.chainId, + chainName: cosmos.chainName, + chainSymbolImageUrl: cosmos.chainSymbolImageUrl, + cosmos, + }; + } - // TODO: `mergeModularChainInfosWithDynamics` 같은 메소드로 빼기 - if ("starknet" in modularChainInfo) { - const endpoint = this.getEndpoint(modularChainInfo.chainId); - return { - ...modularChainInfo, - starknet: { - ...modularChainInfo.starknet, - rpc: endpoint?.rpc || modularChainInfo.starknet.rpc, - }, - }; - } - return modularChainInfo; - }); + // TODO: `mergeModularChainInfosWithDynamics` 같은 메소드로 빼기 + if ("starknet" in modularChainInfo) { + const endpoint = this.getEndpoint(modularChainInfo.chainId); + return { + ...modularChainInfo, + starknet: { + ...modularChainInfo.starknet, + rpc: endpoint?.rpc || modularChainInfo.starknet.rpc, + }, + }; + } + return modularChainInfo; + }) + .concat( + this.suggestedChainInfos.map((chainInfo) => { + return { + chainId: chainInfo.chainId, + chainName: chainInfo.chainName, + chainSymbolImageUrl: chainInfo.chainSymbolImageUrl, + cosmos: chainInfo, + }; + }) + ); }, { keepAlive: true, From 785abfa2c61afe44b6fd327a4b672a0394a1b3ba Mon Sep 17 00:00:00 2001 From: delivan Date: Mon, 14 Oct 2024 13:18:03 +0900 Subject: [PATCH 5/6] Fix build error --- packages/background/src/token-scan/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/background/src/token-scan/service.ts b/packages/background/src/token-scan/service.ts index e0245cebb7..f55130db82 100644 --- a/packages/background/src/token-scan/service.ts +++ b/packages/background/src/token-scan/service.ts @@ -9,7 +9,7 @@ import { Dec } from "@keplr-wallet/unit"; import { ChainIdHelper } from "@keplr-wallet/cosmos"; import { VaultService } from "../vault"; import { KVStore } from "@keplr-wallet/common"; -import { KeyRingStarknetService } from "src/keyring-starknet"; +import { KeyRingStarknetService } from "../keyring-starknet"; import { CairoUint256 } from "starknet"; export type TokenScan = { From 798876cc0b3113c6ccba4181a2e109248dcf7275 Mon Sep 17 00:00:00 2001 From: delivan Date: Mon, 14 Oct 2024 14:08:08 +0900 Subject: [PATCH 6/6] Minor fix --- .../src/pages/main/components/token-found-modal/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/extension/src/pages/main/components/token-found-modal/index.tsx b/apps/extension/src/pages/main/components/token-found-modal/index.tsx index 913e59beb7..d8a694dee0 100644 --- a/apps/extension/src/pages/main/components/token-found-modal/index.tsx +++ b/apps/extension/src/pages/main/components/token-found-modal/index.tsx @@ -87,7 +87,8 @@ export const TokenFoundModal: FunctionComponent<{ const tokenScans = chainStore.tokenScans.slice(); for (const enable of enables) { - if (chainStore.hasChain(enable)) { + const modularChainInfo = chainStore.getModularChain(enable); + if ("cosmos" in modularChainInfo) { if ( keyRingStore.needKeyCoinTypeFinalize( keyRingStore.selectedKeyInfo.id, @@ -115,7 +116,7 @@ export const TokenFoundModal: FunctionComponent<{ ); } } - } else { + } else if ("starknet" in modularChainInfo) { const tokenScan = tokenScans.find((tokenScan) => { return ChainIdHelper.parse(tokenScan.chainId).identifier === enable; });