From 1051892e0b922c63e44aa74a38e65ea25c557d26 Mon Sep 17 00:00:00 2001 From: matias martinez Date: Wed, 30 Aug 2023 11:54:37 -0300 Subject: [PATCH 1/3] Fix sui token balances --- examples/package-lock.json | 2 +- examples/wallet-manager.ts | 7 +++ examples/wallet-watcher.ts | 76 ----------------------------- package-lock.json | 92 ++++++++++++++++-------------------- package.json | 2 +- src/balances/sui.ts | 4 +- src/wallets/sui/index.ts | 19 +++++--- test/wallets/sui/sui.test.ts | 3 ++ 8 files changed, 68 insertions(+), 137 deletions(-) delete mode 100644 examples/wallet-watcher.ts diff --git a/examples/package-lock.json b/examples/package-lock.json index a9c129e..8dc52cd 100644 --- a/examples/package-lock.json +++ b/examples/package-lock.json @@ -14,7 +14,7 @@ }, "..": { "name": "@xlabs-xyz/wallet-monitor", - "version": "0.2.14", + "version": "0.2.15", "license": "MIT", "dependencies": { "@ethersproject/bignumber": "^5.7.0", diff --git a/examples/wallet-manager.ts b/examples/wallet-manager.ts index 6370553..291e737 100644 --- a/examples/wallet-manager.ts +++ b/examples/wallet-manager.ts @@ -25,6 +25,12 @@ const allChainWallets = { ], }, sui: { + rebalance: { + enabled: false, + strategy: 'default', + interval: 10000, + minBalanceThreshold: 0.1, + }, wallets: [ { privateKey: 'ODV9VYi3eSljEWWmpWh8s9m/P2BNNxU/Vp8jwADeNuw=' }, { address: '0x934042d46762fadf9f61ef07aa265fc14d28525c7051224a5f1cba2409aef307' }, @@ -33,6 +39,7 @@ const allChainWallets = { { address: '0x18337b4c5b964b7645506542589c5ed496e794af82f98b3789fed96f61a94c96' }, { address: '0x9ae846f88db3476d7c9f2d8fc49722f7085a3b46aad998120dd11ebeab83e021' }, { address: '0xcb39d897bf0561af7531d37db9781e54528269fed4761275931ce32f20352977' }, + { address: '0x8f11fe7121be742f46e2b3bc2eba081efdc3027697c317a917a2d16fd9b59ab1', tokens: ['USDC', 'USDT'] }, ] }, klatyn: { diff --git a/examples/wallet-watcher.ts b/examples/wallet-watcher.ts deleted file mode 100644 index fc840c8..0000000 --- a/examples/wallet-watcher.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { WalletWatcher } from 'wallet-monitor'; - -// If some configuration is not correct, WalletMonitor will throw an error inmediately, even if it -// wasn't started yet. If the monitor was instantiated correctly, you can be sure that it has all -// necessary configuration to run, including valid addresses and tokens. -const ethMonitor = new WalletWatcher({ - network: 'mainnet', - chainName: 'ethereum', - // pollInterval is optional. Defaults to 60 seconds. - // sets the time between each polling run - // pollInterval: 100* 15, - - // logger is optional. You can pass a logger if you are interested in getting logs from the monitor - // logger: console, - - // Wallet options are optional. - // The options available depend on the blockchain type you are monitoring (evm, solana, algo, etc...) - // the options for each type of chain are defined in wallets//index.ts - // the all can share a basic set of options defined in wallets/base-wallet.ts - walletOptions: { - // nodeUrl: 'https://eth.llamarpc.com', - // tokenPollConcurrency: 1, - }, -}, [ - { - // This is the public native address of the wallet you want to monitor - // the native balance for this wallet will be pulled - address: '0x80C67432656d59144cEFf962E8fAF8926599bCF8', - - // the tokens parameter accepts a list of tokens to pull balances for. - // the balances of these tokens will be pulled for the wallet above. - // you can see the supported tokens for each chain in the config for - // the chain. For example for ethereum: wallets/evm/ethereum.config.ts - tokens: ['USDC', 'DAI'] - }, - { - address: '0x8d0d970225597085A59ADCcd7032113226C0419d', - - // for tokens not supported you can add the token address instead: - tokens: ['usdc'] - }, - { - address: '0xBd8eDBCad57b5197373309954DD959fCCa40d183', - - // the token names are case insensitive - tokens: ['Usdc', 'Dai'] - } -]); - -const solanaMonitor = new WalletWatcher({ - network: `mainnet-beta`, - chainName: 'solana', - }, [ - { - // Random address taken from solana explorer - address: "6VnfVsLdLwNuuCmooLTziQ99PFXZ5vc3yyqyb9tMDhhw", - tokens: ["usdc"] - } - ] -) - -const monitors = [ethMonitor, solanaMonitor] - -monitors.forEach((monitor) => { - monitor.on('balances', (balances) => { - console.log("Received Balances:", balances); - }) - monitor.on('error', (error) => { - console.error("Error:", error); - }) - monitor.on('skipped', (skipped) => { - console.error("Skipped:", skipped); - }) - monitor.start(); - setTimeout(monitor.stop, 5 * 60 * 1000) -}) diff --git a/package-lock.json b/package-lock.json index d3a27a3..1b903a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1958,9 +1958,9 @@ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" }, "node_modules/@mysten/sui.js/node_modules/jayson": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.0.0.tgz", - "integrity": "sha512-v2RNpDCMu45fnLzSk47vx7I+QUaOsox6f5X0CUlabAFwxoP+8MfAY0NQRFwOEYXIxm8Ih5y6OaEa5KYiQMkyAA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz", + "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==", "dependencies": { "@types/connect": "^3.4.33", "@types/node": "^12.12.54", @@ -1983,17 +1983,14 @@ } }, "node_modules/@noble/curves": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz", - "integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dependencies": { - "@noble/hashes": "1.3.0" + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/ed25519": { @@ -2008,15 +2005,15 @@ ] }, "node_modules/@noble/hashes": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", - "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@noble/secp256k1": { "version": "1.7.1", @@ -2119,45 +2116,36 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@scure/base": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", - "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.2.tgz", + "integrity": "sha512-sSCrnIdaUZQHhBxZThMuk7Wm1TWzMD3uJNdGgx3JS23xSqevu0tAOsg8k66nL3R2NwQe65AI9GgqpPOgZys/eA==", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@scure/bip32": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.0.tgz", - "integrity": "sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", + "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", "dependencies": { - "@noble/curves": "~1.0.0", - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" + "@noble/curves": "~1.2.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip39": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz", - "integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", "dependencies": { "@noble/hashes": "~1.3.0", "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@sinclair/typebox": { diff --git a/package.json b/package.json index 2e83dc7..30835c3 100644 --- a/package.json +++ b/package.json @@ -71,8 +71,8 @@ "eslint-plugin-unused-imports": "^2.0.0", "ganache": "^7.8.0", "ganache-cli": "^6.12.2", - "jest": "^29.5.0", "grpc-tools": "^1.12.4", + "jest": "^29.5.0", "prettier": "^2.8.7", "ts-jest": "^29.1.0", "typescript": "^5.0.2" diff --git a/src/balances/sui.ts b/src/balances/sui.ts index 91ad76d..e6b89d1 100644 --- a/src/balances/sui.ts +++ b/src/balances/sui.ts @@ -5,6 +5,7 @@ import { parseFixed } from '@ethersproject/bignumber'; export type SuiTokenData = { symbol: string; decimals: number; + address: string; }; export interface SuiTransactionDetails { @@ -45,7 +46,8 @@ export async function pullSuiTokenData( return { symbol: coinData.symbol, - decimals: coinData.decimals + decimals: coinData.decimals, + address: address }; } diff --git a/src/wallets/sui/index.ts b/src/wallets/sui/index.ts index 12c7d0b..b8cec4a 100644 --- a/src/wallets/sui/index.ts +++ b/src/wallets/sui/index.ts @@ -6,7 +6,13 @@ import { BaseWalletOptions, TransferRecepit, WalletData, } from "../base-wallet"; -import { pullSuiNativeBalance, pullSuiTokenBalances, pullSuiTokenData, transferSuiNativeBalance } from "../../balances/sui"; +import { + SuiTokenData, + pullSuiNativeBalance, + pullSuiTokenBalances, + pullSuiTokenData, + transferSuiNativeBalance +} from "../../balances/sui"; import { SUI_CHAIN_CONFIG, @@ -49,7 +55,7 @@ const SUI_HEX_ADDRESS_REGEX = /^0x[a-fA-F0-9]{64}::coin::COIN$/; export class SuiWalletToolbox extends WalletToolbox { provider: Connection; private chainConfig: SuiChainConfig; - private tokenData: Record = {}; + private tokenData: Record = {}; public options: SuiWalletOptions; constructor( @@ -105,7 +111,6 @@ export class SuiWalletToolbox extends WalletToolbox { } public parseTokensConfig(tokens: string[], failOnInvalidTokens: boolean): string[] { - const knownTokens = this.getKnownTokens(); const validTokens: string[] = []; for (const token of tokens) { if (this.isValidNativeTokenAddress(token)) { @@ -131,8 +136,10 @@ export class SuiWalletToolbox extends WalletToolbox { const tokens = this.walletTokens(this.wallets); await mapConcurrent(tokens, async (token: string): Promise => { // token can be stored as symbol or address, so we normalize to address here - const tokenAddress = this.isKnownToken(token) ? this.getKnownTokens()[token] : token; - this.tokenData[tokenAddress] = await pullSuiTokenData(this.provider, tokenAddress); + const tokenAddress = this.isKnownToken(token) ? this.getKnownTokens()[token] : token; + const tokenData = await pullSuiTokenData(this.provider, tokenAddress); + this.tokenData[tokenAddress] = tokenData + this.tokenData[token] = tokenData; }, 1); this.logger.debug(`Sui token data: ${JSON.stringify(this.tokenData)}`); @@ -170,7 +177,7 @@ export class SuiWalletToolbox extends WalletToolbox { const symbol: string = tokenData?.symbol ? tokenData.symbol : ""; for (const balance of allBalances) { - if (balance.coinType === tokenAddress) { + if (balance.coinType === tokenData.address) { const formattedBalance = formatFixed( balance.totalBalance, diff --git a/test/wallets/sui/sui.test.ts b/test/wallets/sui/sui.test.ts index 8dbd67c..17c6456 100644 --- a/test/wallets/sui/sui.test.ts +++ b/test/wallets/sui/sui.test.ts @@ -29,16 +29,19 @@ jest.mock("../../../src/balances/sui", () => ({ return { symbol: "USDC", decimals: 6, + address: "0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN" }; case "0xfded065d61215141ea7afac2e5c1a7b23514a91cbb2cfd86a38ed97e2f7871ac::PEPE::PEPE": return { symbol: "PEPE", decimals: 9, + address: "0xfded065d61215141ea7afac2e5c1a7b23514a91cbb2cfd86a38ed97e2f7871ac::PEPE::PEPE" }; case "0xd01cebc27fe22868df462f33603646549e13a4b279f5e900b99b9843680445e1::SHIBA::SHIBA": return { symbol: "SHIBA", decimals: 9, + address: "0xd01cebc27fe22868df462f33603646549e13a4b279f5e900b99b9843680445e1::SHIBA::SHIBA" }; } }) From 71475fd62a19a9bfdc7f1fce883095bb040dbabe Mon Sep 17 00:00:00 2001 From: matias martinez Date: Wed, 30 Aug 2023 15:52:10 -0300 Subject: [PATCH 2/3] allow coin types without coin::COIN --- examples/wallet-manager.ts | 5 ++++- src/wallets/sui/index.ts | 13 +++++-------- test/wallets/sui/sui.test.ts | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/wallet-manager.ts b/examples/wallet-manager.ts index 291e737..bbfc4c6 100644 --- a/examples/wallet-manager.ts +++ b/examples/wallet-manager.ts @@ -39,7 +39,10 @@ const allChainWallets = { { address: '0x18337b4c5b964b7645506542589c5ed496e794af82f98b3789fed96f61a94c96' }, { address: '0x9ae846f88db3476d7c9f2d8fc49722f7085a3b46aad998120dd11ebeab83e021' }, { address: '0xcb39d897bf0561af7531d37db9781e54528269fed4761275931ce32f20352977' }, - { address: '0x8f11fe7121be742f46e2b3bc2eba081efdc3027697c317a917a2d16fd9b59ab1', tokens: ['USDC', 'USDT'] }, + { + address: '0x8f11fe7121be742f46e2b3bc2eba081efdc3027697c317a917a2d16fd9b59ab1', + tokens: ['USDC', 'USDT', '0x5d1f47ea69bb0de31c313d7acf89b890dbb8991ea8e03c6c355171f84bb1ba4a::turbos::TURBOS'] + }, ] }, klatyn: { diff --git a/src/wallets/sui/index.ts b/src/wallets/sui/index.ts index b8cec4a..0e3164c 100644 --- a/src/wallets/sui/index.ts +++ b/src/wallets/sui/index.ts @@ -49,8 +49,7 @@ export type SuiDefaultConfigs = Record; export type SuiChainName = keyof typeof SUI_CHAINS; -// TODO: See other token types -const SUI_HEX_ADDRESS_REGEX = /^0x[a-fA-F0-9]{64}::coin::COIN$/; +const SUI_HEX_ADDRESS_REGEX = /^0x[a-fA-F0-9]{64}::\w+::\w+$/; export class SuiWalletToolbox extends WalletToolbox { provider: Connection; @@ -116,7 +115,7 @@ export class SuiWalletToolbox extends WalletToolbox { if (this.isValidNativeTokenAddress(token)) { validTokens.push(token); } else if (this.isKnownToken(token)) { - validTokens.push(token.toUpperCase()); + validTokens.push(this.getKnownTokens()[token.toUpperCase()]); } else if (failOnInvalidTokens) { throw new Error(`Invalid token address: ${token}`); } @@ -134,12 +133,10 @@ export class SuiWalletToolbox extends WalletToolbox { public async warmup() { const tokens = this.walletTokens(this.wallets); - await mapConcurrent(tokens, async (token: string): Promise => { - // token can be stored as symbol or address, so we normalize to address here - const tokenAddress = this.isKnownToken(token) ? this.getKnownTokens()[token] : token; + await mapConcurrent(tokens, async (tokenAddress: string): Promise => { + // tokens has addresses, per this.parseTokensConfig() contract const tokenData = await pullSuiTokenData(this.provider, tokenAddress); - this.tokenData[tokenAddress] = tokenData - this.tokenData[token] = tokenData; + this.tokenData[tokenAddress] = tokenData; }, 1); this.logger.debug(`Sui token data: ${JSON.stringify(this.tokenData)}`); diff --git a/test/wallets/sui/sui.test.ts b/test/wallets/sui/sui.test.ts index 17c6456..49734bb 100644 --- a/test/wallets/sui/sui.test.ts +++ b/test/wallets/sui/sui.test.ts @@ -72,7 +72,7 @@ describe("sui wallet tests", () => { const wallets: WalletConfig[] = [ { address: "0x0000000000000000000000000000000000000000000000000000000000000000", - tokens: ["USDC", "PEPE", "SHIBA"], + tokens: ["USDC", "PEPE", "SHIBA", "0x5d1f47ea69bb0de31c313d7acf89b890dbb8991ea8e03c6c355171f84bb1ba4a::turbos::TURBOS"], }, ]; const wallet = createWalletToolbox( @@ -83,7 +83,7 @@ describe("sui wallet tests", () => { ); await wallet.warmup(); - expect(pullSuiTokenData).toHaveBeenCalled(); + expect(pullSuiTokenData).toHaveBeenCalledTimes(2); // KnownToken (USDC) and Valid Address (TURBOS) const tokenBalances = await wallet.pullTokenBalances( "0x0000000000000000000000000000000000000000000000000000000000000000", From 894df3cc5a46f55517fa03eea643a08f8068f8c7 Mon Sep 17 00:00:00 2001 From: matias martinez Date: Thu, 31 Aug 2023 12:18:15 -0300 Subject: [PATCH 3/3] cru --- src/wallets/sui/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wallets/sui/index.ts b/src/wallets/sui/index.ts index 0e3164c..90449a4 100644 --- a/src/wallets/sui/index.ts +++ b/src/wallets/sui/index.ts @@ -115,7 +115,7 @@ export class SuiWalletToolbox extends WalletToolbox { if (this.isValidNativeTokenAddress(token)) { validTokens.push(token); } else if (this.isKnownToken(token)) { - validTokens.push(this.getKnownTokens()[token.toUpperCase()]); + validTokens.push(this.getKnownTokenAddress(token)); } else if (failOnInvalidTokens) { throw new Error(`Invalid token address: ${token}`); } @@ -123,6 +123,10 @@ export class SuiWalletToolbox extends WalletToolbox { return validTokens; } + private getKnownTokenAddress(token: string): string { + return this.getKnownTokens()[token.toUpperCase()]; + } + private getKnownTokens(): Record { return SUI_CHAIN_CONFIGS[this.chainName].knownTokens[this.network]; }