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

Query StealthKeyChanged events from subgraph #675

Merged
merged 49 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6925fc0
feat: set the last fetched block as the start block
marcomariscal Apr 26, 2024
9932c07
feat: handle caching user announcements and latest fetched block
marcomariscal Apr 30, 2024
154ff46
feat: show user announcements if there are any
marcomariscal Apr 30, 2024
136c4f3
fix: handle watching/loading announcements
marcomariscal Apr 30, 2024
9ba3115
fix: parse out lastFetchedBlock and fix user announcement loading logic
marcomariscal May 1, 2024
9b49317
chore: log
marcomariscal May 1, 2024
9af424d
feat: handle block data caching
marcomariscal Jun 20, 2024
c449a82
feat: show most recent block data if exists
marcomariscal Jun 20, 2024
22cc126
fix: type check
marcomariscal Jun 20, 2024
be0f63e
feat: handle user announcements already present and sign language
marcomariscal Jun 21, 2024
cd9e394
feat: only show fetching when no user announcements
marcomariscal Jun 24, 2024
1f9591d
feat: fetching latest from last fetched block component
marcomariscal Jun 24, 2024
6916958
feat: fetching latest translation for cn
marcomariscal Jun 24, 2024
8cbf9b0
feat: clear local storage button and functionality
marcomariscal Jun 24, 2024
5d04a36
fix: start block handling logic
marcomariscal Jun 24, 2024
def9921
feat: dedupe user announcements
marcomariscal Jun 24, 2024
3046be2
fix: logic
marcomariscal Jun 24, 2024
1c43d4d
fix: minimize debugging logs on userAnnouncement changes
marcomariscal Jun 25, 2024
c5ad0bb
feat: handle scanning latest announcements from last fetched block
marcomariscal Jun 25, 2024
5909fb8
feat: sort by timestamp explicitly
marcomariscal Jun 25, 2024
9f0a3f9
feat: no loading sequence when there are announcements
marcomariscal Jun 25, 2024
c40d035
fix: need sig lately verbiage
marcomariscal Jun 27, 2024
8a90bc7
fix: add need sig lately to cn
marcomariscal Jun 27, 2024
f39c807
fix: little more mb
marcomariscal Jun 27, 2024
aca5dc5
fix: no withdraw verbiage on need-sig-lately
marcomariscal Jun 28, 2024
29f6f6e
feat: handle need sig
marcomariscal Jun 28, 2024
44c53b3
Update frontend/src/i18n/locales/en-US.json
marcomariscal Jul 8, 2024
a211d46
feat: handle sign button instead of needs sig
marcomariscal Jul 8, 2024
e39a690
Update frontend/src/i18n/locales/zh-CN.json
marcomariscal Jul 8, 2024
ba09324
fix: move local storage clear button above lang
marcomariscal Jul 8, 2024
a72ff26
fix: spacing more uniform
marcomariscal Jul 9, 2024
d8b730b
fix: use computed ref as param, and set setIsInWithdrawFlow to false …
marcomariscal Jul 9, 2024
5d63f2a
feat: sign and withdraw
marcomariscal Jul 9, 2024
dcd0ab2
fix: contract periphery tests (#688)
marcomariscal Jul 9, 2024
af15d8c
fix: use balanceIndex to ensure that the correct balance is fetched f…
marcomariscal Jul 9, 2024
c6d04ed
fix: dedupe by tx hash and receiver instead of just tx hash
marcomariscal Jul 9, 2024
a6d8952
fix: include receiver to derive isWithdrawn
marcomariscal Jul 11, 2024
b627889
fix: img
marcomariscal Jul 11, 2024
16a1045
Query StealthKeyChanged events from subgraph for user registration bl…
jferas May 2, 2024
9fb7573
Added valid non-found in subgraph error (vs schema error) and removed…
jferas May 2, 2024
f029262
When getting stealthKeys via subgraph StealthKeyChanged event, save b…
jferas May 6, 2024
8599e8e
Remove unneeded 'block: undefined' return values from 'lookupRecipien…
jferas May 8, 2024
3958d85
Query 10k announcements unless it's Gnosis subgraph
garyghayrat May 21, 2024
637f0fa
Update `umbra-js` version and add `.npmignore`
garyghayrat Jun 5, 2024
7fc2c24
Changed public key acquisition to query registry contract before subg…
jferas Jun 10, 2024
798d512
Fix to return proper error string when public address not found in su…
jferas Jun 24, 2024
e426284
Merge branch 'master' into stealthkey-events-from-subgraph
jferas Jul 11, 2024
08ec0c3
Update UmbraJs version to 0.2.1
jferas Jul 11, 2024
3a23a1a
Use umbra-js@0.2.1 in frontend
garyghayrat Jul 12, 2024
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: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@adraffy/ens-normalize": "1.9.2",
"@metamask/jazzicon": "^2.0.0",
"@quasar/extras": "^1.15.8",
"@umbracash/umbra-js": "0.1.6",
jferas marked this conversation as resolved.
Show resolved Hide resolved
"@umbracash/umbra-js": "0.2.1",
"@uniswap/token-lists": "^1.0.0-beta.19",
"@unstoppabledomains/resolution": "8.5.0",
"@web3-onboard/coinbase": "2.2.7",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/AccountReceive.vue
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ function useScan() {
setScanPrivateKey,
scanPrivateKey,
resetScanSettings: resetScanSettingsInSettingsStore,
getRegisteredBlockNumber,
} = useSettingsStore();
const { signer, userAddress: userWalletAddress, isAccountSetup, provider } = useWalletStore();

Expand Down Expand Up @@ -484,6 +485,7 @@ function useScan() {

// Default scan behavior
for await (const announcementsBatch of umbra.value.fetchSomeAnnouncements(
getRegisteredBlockNumber(),
signer.value,
userWalletAddress.value,
overrides
Expand Down
16 changes: 15 additions & 1 deletion frontend/src/store/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const settings = {
language: 'language',
sendHistorySave: 'send-history-save',
UmbraApiVersion: 'umbra-api-version',
registeredBlockNumber: 'registered-block-number',
};


Expand All @@ -29,6 +30,7 @@ const startBlock = ref<number | undefined>(undefined); // block number to start
const endBlock = ref<number | undefined>(undefined); // block number to scan through
const scanPrivateKey = ref<string>(); // private key entered when scanning
const lastWallet = ref<string>(); // name of last wallet used
const registeredBlockNumber = ref<number | undefined>(undefined); // block number of the when the user registered
const params = new URLSearchParams(window.location.search);
const paramLocale = params.get('locale') || undefined;

Expand All @@ -43,7 +45,9 @@ export default function useSettingsStore() {
lastWallet.value = LocalStorage.getItem(settings.lastWallet)
? String(LocalStorage.getItem(settings.lastWallet))
: undefined;

registeredBlockNumber.value = LocalStorage.getItem(settings.registeredBlockNumber)
? Number(LocalStorage.getItem(settings.registeredBlockNumber))
: undefined;
});
setLanguage(
paramLocale
Expand Down Expand Up @@ -140,6 +144,14 @@ export default function useSettingsStore() {
LocalStorage.remove(settings.UmbraApiVersion);
}

function getRegisteredBlockNumber() {
return registeredBlockNumber.value;
}

function setRegisteredBlockNumber(blockNumber: number) {
registeredBlockNumber.value = blockNumber;
LocalStorage.set(settings.registeredBlockNumber, blockNumber);
}

return {
toggleDarkMode,
Expand All @@ -162,5 +174,7 @@ export default function useSettingsStore() {
getUmbraApiVersion,
setUmbraApiVersion,
clearUmbraApiVersion,
getRegisteredBlockNumber,
setRegisteredBlockNumber,
};
}
22 changes: 8 additions & 14 deletions frontend/src/store/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,19 +632,13 @@ const hasSetPublicKeysLegacy = async (name: string, provider: Provider) => {

// Helper method to check if user has registered public keys in the StealthKeyRegistry
async function getRegisteredStealthKeys(account: string, provider: Provider) {
garyghayrat marked this conversation as resolved.
Show resolved Hide resolved
let retryCounter = 0;
while (retryCounter < 3) {
try {
console.log(`getting stealth keys for ${account}, try ${retryCounter + 1} of 3`);
const stealthPubKeys = await utils.lookupRecipient(account, provider); // throws if no keys found
return stealthPubKeys;
} catch (err) {
window.logger.warn(err);
retryCounter++;
if (retryCounter < 3) {
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for 2 seconds
}
}
const { setRegisteredBlockNumber } = useSettingsStore();
try {
const registrationInfo = await utils.lookupRecipient(account, provider); // throws if no keys found
setRegisteredBlockNumber(Number(registrationInfo.block));
return registrationInfo;
} catch (err) {
window.logger.warn(err);
return null;
}
return null;
}
1 change: 1 addition & 0 deletions umbra-js/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
2 changes: 1 addition & 1 deletion umbra-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@umbracash/umbra-js",
"version": "0.1.6",
"version": "0.2.1",
"description": "Send and receive stealth payments",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
Expand Down
84 changes: 15 additions & 69 deletions umbra-js/src/classes/Umbra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ import {
invalidStealthAddresses,
getEthSweepGasInfo,
lookupRecipient,
getBlockNumberUserRegistered,
assertSupportedAddress,
checkSupportedAddresses,
getBlockNumberUserRegistered,
recursiveGraphFetch,
} from '../utils/utils';
import { Umbra as UmbraContract, Umbra__factory, ERC20__factory } from '../typechain';
import { ETH_ADDRESS, UMBRA_BATCH_SEND_ABI } from '../utils/constants';
import type { Announcement, ChainConfig, EthersProvider, GraphFilterOverride, ScanOverrides, SendOverrides, SubgraphAnnouncement, UserAnnouncement, AnnouncementDetail, SendBatch, SendData} from '../types'; // prettier-ignore
import type { Announcement, ChainConfig, EthersProvider, ScanOverrides, SendOverrides, SubgraphAnnouncement, UserAnnouncement, AnnouncementDetail, SendBatch, SendData} from '../types'; // prettier-ignore

// Mapping from chainId to contract information
const umbraAddress = '0xFb2dc580Eed955B528407b4d36FfaFe3da685401'; // same on all supported networks
Expand Down Expand Up @@ -72,7 +73,7 @@ const chainConfigs: Record<number, ChainConfig> = {
* @notice Helper method to parse chainConfig input and return a valid chain configuration
* @param chainConfig Supported chainID as number, or custom ChainConfig
*/
const parseChainConfig = (chainConfig: ChainConfig | number) => {
export const parseChainConfig = (chainConfig: ChainConfig | number) => {
if (!chainConfig) {
throw new Error('chainConfig not provided');
}
Expand Down Expand Up @@ -372,7 +373,7 @@ export class Umbra {
}

/**
* @notice Fetches all Umbra event logs using Goldsky, if available, falling back to RPC if not
* @notice Fetches all Umbra event logs using a subgraph, if available, falling back to RPC if not
* @param overrides Override the start and end block used for scanning;
* @returns A list of Announcement events supplemented with additional metadata, such as the sender, block,
* timestamp, and txhash
Expand All @@ -397,7 +398,7 @@ export class Umbra {
return filtered.filter((i) => i !== null) as AnnouncementDetail[];
};

// Try querying events using Goldsky, fallback to querying logs.
// Try querying events using a subgraph, fallback to querying logs.
if (this.chainConfig.subgraphUrl) {
try {
for await (const subgraphAnnouncements of this.fetchAllAnnouncementsFromSubgraph(startBlock, endBlock)) {
Expand All @@ -417,17 +418,23 @@ export class Umbra {

/**
* @notice Fetches Umbra event logs starting from the block user registered their stealth keys in using
* Goldsky, if available, falling back to RPC if not
* a subgraph, if available, falling back to RPC if not
* @param possibleRegisteredBlockNumber Block number when user registered their stealth keys (if known)
* @param Signer Signer with provider to use for fetching the block number (if not known) from the StealthKeyRegistry contract
* @param address Address of the user for fetching the block number (if not known) from the subgraph or StealthKeyRegistry contract
* @param overrides Override the start and end block used for scanning;
* @returns A list of Announcement events supplemented with additional metadata, such as the sender, block,
* timestamp, and txhash
* @dev If the registered block number is not known, it will be fetched from the subgraph or the StealthKeyRegistry contract
*/
async *fetchSomeAnnouncements(
possibleRegisteredBlockNumber: number | undefined,
garyghayrat marked this conversation as resolved.
Show resolved Hide resolved
Signer: JsonRpcSigner,
address: string,
overrides: ScanOverrides = {}
): AsyncGenerator<AnnouncementDetail[]> {
const registeredBlockNumber = await getBlockNumberUserRegistered(address, Signer.provider);
const registeredBlockNumber =
possibleRegisteredBlockNumber || (await getBlockNumberUserRegistered(address, Signer.provider, this.chainConfig));
// Get start and end blocks to scan events for
const startBlock = overrides.startBlock || registeredBlockNumber || this.chainConfig.startBlock;
const endBlock = overrides.endBlock || 'latest';
Expand All @@ -437,7 +444,7 @@ export class Umbra {
}

/**
* @notice Fetches all Umbra event logs using Goldsky
* @notice Fetches all Umbra event logs using a subgraph
* @param startBlock Scanning start block
* @param endBlock Scannding end block
* @returns A list of Announcement events supplemented with additional metadata, such as the sender, block,
Expand Down Expand Up @@ -732,67 +739,6 @@ export class Umbra {

// ============================== PRIVATE, FUNCTIONAL HELPER METHODS ==============================

/**
* @notice Generic method to recursively grab every 'page' of results
* @dev NOTE: the query MUST return the ID field
* @dev Modifies from: https://github.com/dcgtc/dgrants/blob/f5a783524d0b56eea12c127b2146fba8fb9273b4/app/src/utils/utils.ts#L443
* @dev Relevant docs: https://thegraph.com/docs/developer/graphql-api#example-3
* @dev Lives outside of the class instance because user's should not need access to this method
* @dev TODO support node.js by replacing reliance on browser's fetch module with https://github.com/paulmillr/micro-ftch
* @param url the url we will recursively fetch from
* @param key the key in the response object which holds results
* @param query a function which will return the query string (with the page in place)
* @param before the current array of objects
*/
async function* recursiveGraphFetch(
url: string,
key: string,
query: (filter: string) => string,
before: any[] = [],
overrides?: GraphFilterOverride
): AsyncGenerator<any[]> {
// retrieve the last ID we collected to use as the starting point for this query
const fromId = before.length ? (before[before.length - 1].id as string | number) : false;
let startBlockFilter = '';
let endBlockFilter = '';
const startBlock = overrides?.startBlock ? overrides.startBlock.toString() : '';
const endBlock = overrides?.endBlock ? overrides?.endBlock.toString() : '';

if (startBlock) {
startBlockFilter = `block_gte: "${startBlock}",`;
}

if (endBlock && endBlock !== 'latest') {
endBlockFilter = `block_lte: "${endBlock}",`;
}
// Fetch this 'page' of results - please note that the query MUST return an ID
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: query(`
first: 1000,
orderBy: id,
orderDirection: desc,
where: {
${fromId ? `id_lt: "${fromId}",` : ''}
${startBlockFilter}
${endBlockFilter}
}
`),
}),
});

// Resolve the json
const json = await res.json();

// If there were results on this page yield the results then query the next page, otherwise do nothing.
if (json.data[key].length) {
yield json.data[key]; // yield the data for this page
yield* recursiveGraphFetch(url, key, query, [...before, ...json.data[key]], overrides); // yield the data for the next pages
}
}

/**
* @notice Tries withdrawing ETH from a stealth address on behalf of a user
* @dev Attempts multiple retries before returning an error. Retries only occur if there was an
Expand Down
15 changes: 15 additions & 0 deletions umbra-js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,20 @@ export interface UserAnnouncement {
txHash: string;
}

// StealthKeyChanged event data received from subgraph queries
export interface SubgraphStealthKeyChangedEvent {
block: string;
from: string;
id: string; // the subgraph uses an ID of `timestamp-logIndex`
registrant: string;
spendingPubKeyPrefix: BigNumber;
spendingPubKey: BigNumber;
timestamp: string;
txHash: string;
viewingPubKeyPrefix: BigNumber;
viewingPubKey: BigNumber;
}

export interface SendBatch {
token: string;
amount: BigNumberish;
Expand All @@ -135,4 +149,5 @@ export interface SendData {
export type GraphFilterOverride = {
startBlock?: number | string;
endBlock?: number | string;
registrant?: string;
};
Loading
Loading