diff --git a/common/src/consts.ts b/common/src/consts.ts index b93eb553..ebeb8260 100644 --- a/common/src/consts.ts +++ b/common/src/consts.ts @@ -107,7 +107,11 @@ export const INITIAL_NTT_DEPLOYMENT_BLOCK_BY_NETWORK_AND_CHAIN: NetworkChainBloc }; export const INITIAL_FT_DEPLOYMENT_BLOCK_BY_NETWORK_AND_CHAIN: NetworkChainBlockMapping = { - ['Mainnet']: {}, + ['Mainnet']: { + Solana: '285350104', + Arbitrum: '245882390', + Base: '18956026', + }, ['Testnet']: { Solana: '302162456', ArbitrumSepolia: '49505590', diff --git a/common/src/utils.ts b/common/src/utils.ts index 4e4c630e..5a163b08 100644 --- a/common/src/utils.ts +++ b/common/src/utils.ts @@ -235,3 +235,7 @@ export async function retry( } } } + +export function stringifyWithBigInt(obj: any) { + return JSON.stringify(obj, (_, value) => (typeof value === 'bigint' ? value.toString() : value)); +} diff --git a/package-lock.json b/package-lock.json index 644eea20..d42064e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2584,9 +2584,9 @@ "license": "MIT" }, "node_modules/@babel/runtime": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz", - "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz", + "integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2818,6 +2818,14 @@ "node": ">=11" } }, + "node_modules/@coral-xyz/anchor-errors": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz", + "integrity": "sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/@coral-xyz/anchor/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -7156,12 +7164,12 @@ } }, "node_modules/@solana/web3.js": { - "version": "1.91.8", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.91.8.tgz", - "integrity": "sha512-USa6OS1jbh8zOapRJ/CBZImZ8Xb7AJjROZl5adql9TpOoBN9BUzyyouS5oPuZHft7S7eB8uJPuXWYjMi6BHgOw==", + "version": "1.95.3", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.95.3.tgz", + "integrity": "sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og==", "dependencies": { - "@babel/runtime": "^7.24.5", - "@noble/curves": "^1.4.0", + "@babel/runtime": "^7.25.0", + "@noble/curves": "^1.4.2", "@noble/hashes": "^1.4.0", "@solana/buffer-layout": "^4.0.1", "agentkeepalive": "^4.5.0", @@ -7171,16 +7179,85 @@ "bs58": "^4.0.1", "buffer": "6.0.3", "fast-stable-stringify": "^1.0.0", - "jayson": "^4.1.0", + "jayson": "^4.1.1", "node-fetch": "^2.7.0", - "rpc-websockets": "^7.11.0", - "superstruct": "^0.14.2" + "rpc-websockets": "^9.0.2", + "superstruct": "^2.0.2" + } + }, + "node_modules/@solana/web3.js/node_modules/@noble/curves": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.5.0.tgz", + "integrity": "sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/web3.js/node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@solana/web3.js/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/@solana/web3.js/node_modules/rpc-websockets": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.0.2.tgz", + "integrity": "sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw==", + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/uuid": "^8.3.4", + "@types/ws": "^8.2.2", + "buffer": "^6.0.3", + "eventemitter3": "^5.0.1", + "uuid": "^8.3.2", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" } }, "node_modules/@solana/web3.js/node_modules/superstruct": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", - "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@solana/web3.js/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, "node_modules/@suchipi/femver": { "version": "1.0.0", @@ -7456,6 +7533,14 @@ "@svgr/core": "*" } }, + "node_modules/@swc/helpers": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.12.tgz", + "integrity": "sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -8463,6 +8548,11 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" + }, "node_modules/@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -9094,7 +9184,7 @@ "node_modules/@wormhole-foundation/example-liquidity-layer-solana": { "version": "0.0.1", "resolved": "file:watcher/sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz", - "integrity": "sha512-bv3GnsZP5GWVHTlnB+6DriV7NRWoL3PgWaGYHelTOEppYBhuJW6WPsbNNr3b7TMKNizPNNn0tkCWqVwglyU2gA==", + "integrity": "sha512-ebEnBbemfffTX2xh7FTIaz7i3sAfCZbl9YgLitjW+9awzhKxvL7ZY9pAmw7q5A9ITV3CoPgKqgQh41D/pPk78Q==", "dependencies": { "@certusone/wormhole-spydk": "^0.0.1", "@coral-xyz/anchor": "^0.30.0", @@ -9109,6 +9199,7 @@ "@wormhole-foundation/sdk-solana": "^0.7.0-beta.4", "@wormhole-foundation/sdk-solana-core": "^0.7.0-beta.4", "anchor-0.29.0": "npm:@coral-xyz/anchor@^0.29.0", + "bn.js": "^5.2.1", "dotenv": "^16.4.1", "ethers": "^5.7.2", "sha3": "^2.1.4", @@ -9117,11 +9208,12 @@ } }, "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@coral-xyz/anchor": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.30.0.tgz", - "integrity": "sha512-qreDh5ztiRHVnCbJ+RS70NJ6aSTPBYDAgFeQ7Z5QvaT5DcDIhNyt4onOciVz2ieIE1XWePOJDDu9SbNvPGBkvQ==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.30.1.tgz", + "integrity": "sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ==", "dependencies": { - "@coral-xyz/borsh": "^0.30.0", + "@coral-xyz/anchor-errors": "^0.30.1", + "@coral-xyz/borsh": "^0.30.1", "@noble/hashes": "^1.3.1", "@solana/web3.js": "^1.68.0", "bn.js": "^5.1.2", @@ -9140,10 +9232,10 @@ "node": ">=11" } }, - "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@coral-xyz/borsh": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.30.0.tgz", - "integrity": "sha512-OrcV+7N10cChhgDRUxM4iEIuwxUHHs52XD85R8cFCUqE0vbLYrcoPPPs+VF6kZ9DhdJGVW2I6DHJOp5TykyZog==", + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@coral-xyz/anchor/node_modules/@coral-xyz/borsh": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.30.1.tgz", + "integrity": "sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ==", "dependencies": { "bn.js": "^5.1.2", "buffer-layout": "^1.2.0" @@ -9155,51 +9247,134 @@ "@solana/web3.js": "^1.68.0" } }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/codecs": { + "version": "2.0.0-preview.4", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-2.0.0-preview.4.tgz", + "integrity": "sha512-gLMupqI4i+G4uPi2SGF/Tc1aXcviZF2ybC81x7Q/fARamNSgNOCUUoSCg9nWu1Gid6+UhA7LH80sWI8XjKaRog==", + "dependencies": { + "@solana/codecs-core": "2.0.0-preview.4", + "@solana/codecs-data-structures": "2.0.0-preview.4", + "@solana/codecs-numbers": "2.0.0-preview.4", + "@solana/codecs-strings": "2.0.0-preview.4", + "@solana/options": "2.0.0-preview.4" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/codecs-core": { + "version": "2.0.0-preview.4", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-preview.4.tgz", + "integrity": "sha512-A0VVuDDA5kNKZUinOqHxJQK32aKTucaVbvn31YenGzHX1gPqq+SOnFwgaEY6pq4XEopSmaK16w938ZQS8IvCnw==", + "dependencies": { + "@solana/errors": "2.0.0-preview.4" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/codecs-data-structures": { + "version": "2.0.0-preview.4", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-preview.4.tgz", + "integrity": "sha512-nt2k2eTeyzlI/ccutPcG36M/J8NAYfxBPI9h/nQjgJ+M+IgOKi31JV8StDDlG/1XvY0zyqugV3I0r3KAbZRJpA==", + "dependencies": { + "@solana/codecs-core": "2.0.0-preview.4", + "@solana/codecs-numbers": "2.0.0-preview.4", + "@solana/errors": "2.0.0-preview.4" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/codecs-numbers": { + "version": "2.0.0-preview.4", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-preview.4.tgz", + "integrity": "sha512-Q061rLtMadsO7uxpguT+Z7G4UHnjQ6moVIxAQxR58nLxDPCC7MB1Pk106/Z7NDhDLHTcd18uO6DZ7ajHZEn2XQ==", + "dependencies": { + "@solana/codecs-core": "2.0.0-preview.4", + "@solana/errors": "2.0.0-preview.4" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/codecs-strings": { + "version": "2.0.0-preview.4", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.0.0-preview.4.tgz", + "integrity": "sha512-YDbsQePRWm+xnrfS64losSGRg8Wb76cjK1K6qfR8LPmdwIC3787x9uW5/E4icl/k+9nwgbIRXZ65lpF+ucZUnw==", + "dependencies": { + "@solana/codecs-core": "2.0.0-preview.4", + "@solana/codecs-numbers": "2.0.0-preview.4", + "@solana/errors": "2.0.0-preview.4" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": ">=5" + } + }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/errors": { + "version": "2.0.0-preview.4", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-preview.4.tgz", + "integrity": "sha512-kadtlbRv2LCWr8A9V22On15Us7Nn8BvqNaOB4hXsTB3O0fU40D1ru2l+cReqLcRPij4znqlRzW9Xi0m6J5DIhA==", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^12.1.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/options": { + "version": "2.0.0-preview.4", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-2.0.0-preview.4.tgz", + "integrity": "sha512-tv2O/Frxql/wSe3jbzi5nVicIWIus/BftH+5ZR+r9r3FO0/htEllZS5Q9XdbmSboHu+St87584JXeDx3xm4jaA==", + "dependencies": { + "@solana/codecs-core": "2.0.0-preview.4", + "@solana/codecs-data-structures": "2.0.0-preview.4", + "@solana/codecs-numbers": "2.0.0-preview.4", + "@solana/codecs-strings": "2.0.0-preview.4", + "@solana/errors": "2.0.0-preview.4" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/spl-token": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.4.6.tgz", - "integrity": "sha512-1nCnUqfHVtdguFciVWaY/RKcQz1IF4b31jnKgAmjU9QVN1q7dRUkTEWJZgTYIEtsULjVnC9jRqlhgGN39WbKKA==", + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.4.8.tgz", + "integrity": "sha512-RO0JD9vPRi4LsAbMUdNbDJ5/cv2z11MGhtAvFeRzT4+hAGE/FUzRi0tkkWtuCfSIU3twC6CtmAihRp/+XXjWsA==", "dependencies": { "@solana/buffer-layout": "^4.0.0", "@solana/buffer-layout-utils": "^0.2.0", - "@solana/spl-token-group": "^0.0.4", - "@solana/spl-token-metadata": "^0.1.4", + "@solana/spl-token-group": "^0.0.5", + "@solana/spl-token-metadata": "^0.1.3", "buffer": "^6.0.3" }, "engines": { "node": ">=16" }, "peerDependencies": { - "@solana/web3.js": "^1.91.6" + "@solana/web3.js": "^1.94.0" } }, - "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/web3.js": { - "version": "1.91.7", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.91.7.tgz", - "integrity": "sha512-HqljZKDwk6Z4TajKRGhGLlRsbGK4S8EY27DA7v1z6yakewiUY3J7ZKDZRxcqz2MYV/ZXRrJ6wnnpiHFkPdv0WA==", + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/spl-token/node_modules/@solana/spl-token-group": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@solana/spl-token-group/-/spl-token-group-0.0.5.tgz", + "integrity": "sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ==", "dependencies": { - "@babel/runtime": "^7.23.4", - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.3.3", - "@solana/buffer-layout": "^4.0.1", - "agentkeepalive": "^4.5.0", - "bigint-buffer": "^1.1.5", - "bn.js": "^5.2.1", - "borsh": "^0.7.0", - "bs58": "^4.0.1", - "buffer": "6.0.3", - "fast-stable-stringify": "^1.0.0", - "jayson": "^4.1.0", - "node-fetch": "^2.7.0", - "rpc-websockets": "^7.5.1", - "superstruct": "^0.14.2" + "@solana/codecs": "2.0.0-preview.4", + "@solana/spl-type-length-value": "0.1.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@solana/web3.js": "^1.94.0" } }, - "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@solana/web3.js/node_modules/superstruct": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", - "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" - }, "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@wormhole-foundation/sdk-base": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-base/-/sdk-base-0.7.2.tgz", @@ -9272,21 +9447,33 @@ "node": ">=11" } }, - "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@wormhole-foundation/sdk-solana-core/node_modules/@coral-xyz/borsh": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.29.0.tgz", - "integrity": "sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==", + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@wormhole-foundation/sdk-solana-core/node_modules/@solana/web3.js": { + "version": "1.91.7", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.91.7.tgz", + "integrity": "sha512-HqljZKDwk6Z4TajKRGhGLlRsbGK4S8EY27DA7v1z6yakewiUY3J7ZKDZRxcqz2MYV/ZXRrJ6wnnpiHFkPdv0WA==", "dependencies": { - "bn.js": "^5.1.2", - "buffer-layout": "^1.2.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@solana/web3.js": "^1.68.0" + "@babel/runtime": "^7.23.4", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.3", + "@solana/buffer-layout": "^4.0.1", + "agentkeepalive": "^4.5.0", + "bigint-buffer": "^1.1.5", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.0", + "node-fetch": "^2.7.0", + "rpc-websockets": "^7.5.1", + "superstruct": "^0.14.2" } }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@wormhole-foundation/sdk-solana-core/node_modules/@solana/web3.js/node_modules/superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + }, "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@wormhole-foundation/sdk-solana/node_modules/@coral-xyz/anchor": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.29.0.tgz", @@ -9311,21 +9498,6 @@ "node": ">=11" } }, - "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@wormhole-foundation/sdk-solana/node_modules/@coral-xyz/borsh": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.29.0.tgz", - "integrity": "sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==", - "dependencies": { - "bn.js": "^5.1.2", - "buffer-layout": "^1.2.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@solana/web3.js": "^1.68.0" - } - }, "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@wormhole-foundation/sdk-solana/node_modules/@solana/spl-token": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.9.tgz", @@ -9342,6 +9514,33 @@ "@solana/web3.js": "^1.47.4" } }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@wormhole-foundation/sdk-solana/node_modules/@solana/web3.js": { + "version": "1.91.7", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.91.7.tgz", + "integrity": "sha512-HqljZKDwk6Z4TajKRGhGLlRsbGK4S8EY27DA7v1z6yakewiUY3J7ZKDZRxcqz2MYV/ZXRrJ6wnnpiHFkPdv0WA==", + "dependencies": { + "@babel/runtime": "^7.23.4", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.3", + "@solana/buffer-layout": "^4.0.1", + "agentkeepalive": "^4.5.0", + "bigint-buffer": "^1.1.5", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.0", + "node-fetch": "^2.7.0", + "rpc-websockets": "^7.5.1", + "superstruct": "^0.14.2" + } + }, + "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/@wormhole-foundation/sdk-solana/node_modules/@solana/web3.js/node_modules/superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + }, "node_modules/@wormhole-foundation/example-liquidity-layer-solana/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -16723,9 +16922,9 @@ } }, "node_modules/jayson": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz", - "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.2.tgz", + "integrity": "sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA==", "dependencies": { "@types/connect": "^3.4.33", "@types/node": "^12.12.54", @@ -16738,7 +16937,7 @@ "json-stringify-safe": "^5.0.1", "JSONStream": "^1.3.5", "uuid": "^8.3.2", - "ws": "^7.4.5" + "ws": "^7.5.10" }, "bin": { "jayson": "bin/jayson.js" @@ -16757,6 +16956,26 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "node_modules/jayson/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -23262,7 +23481,7 @@ "@types/bn.js": "^5.1.0", "@wormhole-foundation/example-liquidity-layer-definitions": "file:./sdk/wormhole-foundation-example-liquidity-layer-definitions-0.0.1.tgz", "@wormhole-foundation/example-liquidity-layer-evm": "file:./sdk/wormhole-foundation-example-liquidity-layer-evm-0.0.1.tgz", - "@wormhole-foundation/example-liquidity-layer-solana": "file:./sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz", + "@wormhole-foundation/example-liquidity-layer-solana": "file:sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz", "@wormhole-foundation/wormhole-monitor-common": "^0.0.1", "algosdk": "^2.4.0", "anchor-0.29.0": "npm:@coral-xyz/anchor@^0.29.0", @@ -23378,49 +23597,6 @@ "@solana/web3.js": "^1.91.6" } }, - "watcher/node_modules/@solana/web3.js": { - "version": "1.91.7", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.91.7.tgz", - "integrity": "sha512-HqljZKDwk6Z4TajKRGhGLlRsbGK4S8EY27DA7v1z6yakewiUY3J7ZKDZRxcqz2MYV/ZXRrJ6wnnpiHFkPdv0WA==", - "dependencies": { - "@babel/runtime": "^7.23.4", - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.3.3", - "@solana/buffer-layout": "^4.0.1", - "agentkeepalive": "^4.5.0", - "bigint-buffer": "^1.1.5", - "bn.js": "^5.2.1", - "borsh": "^0.7.0", - "bs58": "^4.0.1", - "buffer": "6.0.3", - "fast-stable-stringify": "^1.0.0", - "jayson": "^4.1.0", - "node-fetch": "^2.7.0", - "rpc-websockets": "^7.5.1", - "superstruct": "^0.14.2" - } - }, - "watcher/node_modules/@solana/web3.js/node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "watcher/node_modules/@solana/web3.js/node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "watcher/node_modules/@solana/web3.js/node_modules/superstruct": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", - "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" - }, "watcher/node_modules/base-x": { "version": "4.0.0", "license": "MIT" diff --git a/watcher/package.json b/watcher/package.json index 84d04481..cdc6e64e 100644 --- a/watcher/package.json +++ b/watcher/package.json @@ -37,9 +37,9 @@ "@solana/spl-token": "^0.4.6", "@solana/web3.js": "^1.73.0", "@types/bn.js": "^5.1.0", - "@wormhole-foundation/example-liquidity-layer-definitions": "file:./sdk/wormhole-foundation-example-liquidity-layer-definitions-0.0.1.tgz", - "@wormhole-foundation/example-liquidity-layer-evm": "file:./sdk/wormhole-foundation-example-liquidity-layer-evm-0.0.1.tgz", - "@wormhole-foundation/example-liquidity-layer-solana": "file:./sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz", + "@wormhole-foundation/example-liquidity-layer-definitions": "file:sdk/wormhole-foundation-example-liquidity-layer-definitions-0.0.1.tgz", + "@wormhole-foundation/example-liquidity-layer-evm": "file:sdk/wormhole-foundation-example-liquidity-layer-evm-0.0.1.tgz", + "@wormhole-foundation/example-liquidity-layer-solana": "file:sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz", "@wormhole-foundation/wormhole-monitor-common": "^0.0.1", "algosdk": "^2.4.0", "anchor-0.29.0": "npm:@coral-xyz/anchor@^0.29.0", diff --git a/watcher/sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz b/watcher/sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz index dde6a6c1..df135d0d 100644 Binary files a/watcher/sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz and b/watcher/sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz differ diff --git a/watcher/src/fastTransfer/consts.ts b/watcher/src/fastTransfer/consts.ts index 779ff3c9..8bccbfe3 100644 --- a/watcher/src/fastTransfer/consts.ts +++ b/watcher/src/fastTransfer/consts.ts @@ -1,12 +1,19 @@ -import { Network } from '@wormhole-foundation/sdk-base'; +import { Chain, Network } from '@wormhole-foundation/sdk-base'; export type FastTransferContracts = 'MatchingEngine' | 'TokenRouter' | 'USDCMint'; -// Will define more as we know what the mainnet addresses are -export type MatchingEngineProgramId = 'mPydpGUWxzERTNpyvTKdvS7v8kvw5sgwfiP8WQFrXVS'; -export type TokenRouterProgramId = 'tD8RmtdcV7bzBeuFgyrFc8wvayj988ChccEzRQzo6md'; -export type USDCMintAddress = '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'; -export type SwapLayerProgramId = 'SwapLayer1111111111111111111111111111111111'; +export type MatchingEngineProgramId = + | 'mPydpGUWxzERTNpyvTKdvS7v8kvw5sgwfiP8WQFrXVS' + | 'HtkeCDdYY4i9ncAxXKjYTx8Uu3WM8JbtiLRYjtHwaVXb'; +export type TokenRouterProgramId = + | 'tD8RmtdcV7bzBeuFgyrFc8wvayj988ChccEzRQzo6md' + | '28topqjtJzMnPaGFmmZk68tzGmj9W9aMntaEK3QkgtRe'; +export type USDCMintAddress = + | '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU' + | 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; +export type SwapLayerProgramId = + | 'SwapLayer1111111111111111111111111111111111' + | '9Zv8ajzFjacRoYCgCPus4hq3pYjpNa9KkTFQ1sHa1h3d'; export interface SolanaContractAddresses { MatchingEngine: MatchingEngineProgramId; @@ -27,15 +34,30 @@ export type ContractAddresses = SolanaContractAddresses | EthereumContractAddres export type FastTransferContractAddresses = { [key in Network]?: { - Solana?: SolanaContractAddresses; - ArbitrumSepolia?: EthereumContractAddresses; - Ethereum?: EthereumContractAddresses; + // For each chain, use SolanaContractAddresses if it's Solana, otherwise use EthereumContractAddresses + [chain in Chain]?: chain extends 'Solana' ? SolanaContractAddresses : EthereumContractAddresses; }; }; // Will add more chains as needed export const FAST_TRANSFER_CONTRACTS: FastTransferContractAddresses = { - Mainnet: {}, + Mainnet: { + Solana: { + MatchingEngine: 'HtkeCDdYY4i9ncAxXKjYTx8Uu3WM8JbtiLRYjtHwaVXb', + TokenRouter: '28topqjtJzMnPaGFmmZk68tzGmj9W9aMntaEK3QkgtRe', + USDCMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + // TODO: uncomment this when SwapLayer is deployed on Solana Mainnet + // SwapLayer: '9Zv8ajzFjacRoYCgCPus4hq3pYjpNa9KkTFQ1sHa1h3d', + }, + Arbitrum: { + TokenRouter: '0x70287c79ee41C5D1df8259Cd68Ba0890cd389c47', + CircleBridge: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', + }, + Base: { + TokenRouter: '0x70287c79ee41C5D1df8259Cd68Ba0890cd389c47', + CircleBridge: '0x1682Ae6375C4E4A97e4B583BC394c861A46D8962', + }, + }, Testnet: { Solana: { MatchingEngine: 'mPydpGUWxzERTNpyvTKdvS7v8kvw5sgwfiP8WQFrXVS', @@ -49,5 +71,19 @@ export const FAST_TRANSFER_CONTRACTS: FastTransferContractAddresses = { }, }; -// Will add more chains as needed -export type FTChains = 'ArbitrumSepolia'; +// Separate testnet and mainnet chains +export type FTEVMMainnetChain = 'Arbitrum' | 'Base'; +export type FTEVMTestnetChain = 'ArbitrumSepolia'; +export type FTEVMChain = FTEVMMainnetChain | FTEVMTestnetChain; + +export const FTEVMMainnetChains: FTEVMMainnetChain[] = ['Arbitrum', 'Base']; +export const FTEVMTestnetChains: FTEVMTestnetChain[] = ['ArbitrumSepolia']; + +export const isFTEVMChain = (chain: Chain, network: Network): chain is FTEVMChain => { + if (network === 'Mainnet') { + return FTEVMMainnetChains.includes(chain as FTEVMMainnetChain); + } else if (network === 'Testnet') { + return FTEVMTestnetChains.includes(chain as FTEVMTestnetChain); + } + return false; +}; diff --git a/watcher/src/fastTransfer/tokenRouter/parser.ts b/watcher/src/fastTransfer/tokenRouter/parser.ts index 47095bca..7381e66f 100644 --- a/watcher/src/fastTransfer/tokenRouter/parser.ts +++ b/watcher/src/fastTransfer/tokenRouter/parser.ts @@ -10,7 +10,7 @@ import { contracts, toChainId, } from '@wormhole-foundation/sdk-base'; -import { FAST_TRANSFER_CONTRACTS, FTChains } from '../consts'; +import { FAST_TRANSFER_CONTRACTS, FTEVMChain } from '../consts'; import isNotNull from '../../../src/utils/isNotNull'; import { MarketOrder } from '../types'; @@ -19,17 +19,8 @@ class TokenRouterParser { private evmTokenRouter: EvmTokenRouter; private network: Network; private chain: Chain; - // the only 2 functions we care about - private functionSelectors = [ - ethers.utils - .id('placeFastMarketOrder(uint64,uint64,uint16,bytes32,bytes,address,uint64,uint32)') - .substring(0, 10), - ethers.utils - .id('placeFastMarketOrder(uint64,uint16,bytes32,bytes,uint64,uint32)') - .substring(0, 10), - ]; - - constructor(network: Network, chain: FTChains, provider: ethers.providers.JsonRpcProvider) { + private functionSelectors: string[]; + constructor(network: Network, chain: FTEVMChain, provider: ethers.providers.JsonRpcProvider) { this.provider = provider; this.evmTokenRouter = new EvmTokenRouter( this.provider, @@ -38,6 +29,19 @@ class TokenRouterParser { ); this.network = network; this.chain = chain; + // on Mainnet the TokenRouter is called via a SwapLayer proxy contract using the `initiate` method + // on Testnet there is no Swaplayer so we check using the primitive methods + this.functionSelectors = + this.network === 'Mainnet' + ? [ethers.utils.id('initiate(uint16,bytes32,bytes)').substring(0, 10)] + : [ + ethers.utils + .id('placeFastMarketOrder(uint64,uint64,uint16,bytes32,bytes,address,uint64,uint32)') + .substring(0, 10), + ethers.utils + .id('placeFastMarketOrder(uint64,uint16,bytes32,bytes,uint64,uint32)') + .substring(0, 10), + ]; } async parseFastMarketOrder(txHash: string): Promise { diff --git a/watcher/src/index.ts b/watcher/src/index.ts index 25f1aef0..f7b2eb07 100644 --- a/watcher/src/index.ts +++ b/watcher/src/index.ts @@ -5,6 +5,7 @@ import { initDb } from './databases/utils'; import { Mode, getNetwork, getMode } from '@wormhole-foundation/wormhole-monitor-common'; import { startSupervisor } from './workers/supervisor'; import { Chain, Network } from '@wormhole-foundation/sdk-base'; +import { FTEVMMainnetChains, FTEVMTestnetChains } from './fastTransfer/consts'; initDb(); @@ -93,7 +94,8 @@ const supportedNTTChains: Chain[] = ? ['Solana', 'Sepolia', 'ArbitrumSepolia', 'BaseSepolia', 'OptimismSepolia'] : ['Solana', 'Ethereum', 'Fantom', 'Arbitrum', 'Optimism', 'Base']; -const supportedFTChains: Chain[] = network === 'Testnet' ? ['Solana', 'ArbitrumSepolia'] : []; +const supportedFTChains: Chain[] = + network === 'Testnet' ? ['Solana', ...FTEVMTestnetChains] : ['Solana', ...FTEVMMainnetChains]; if (mode === 'vaa') { startSupervisor(supportedChains); diff --git a/watcher/src/watchers/FTEVMWatcher.ts b/watcher/src/watchers/FTEVMWatcher.ts index 21faa867..718b38dc 100644 --- a/watcher/src/watchers/FTEVMWatcher.ts +++ b/watcher/src/watchers/FTEVMWatcher.ts @@ -2,7 +2,7 @@ import knex, { Knex } from 'knex'; import { Watcher } from './Watcher'; import { Network } from '@wormhole-foundation/sdk-base'; import { assertEnvironmentVariable } from '@wormhole-foundation/wormhole-monitor-common'; -import { FAST_TRANSFER_CONTRACTS, FTChains } from '../fastTransfer/consts'; +import { FAST_TRANSFER_CONTRACTS, FTEVMChain } from '../fastTransfer/consts'; import { ethers } from 'ethers'; import { AXIOS_CONFIG_JSON, RPCS_BY_CHAIN } from '../consts'; import { makeBlockKey } from '../databases/utils'; @@ -29,7 +29,7 @@ export class FTEVMWatcher extends Watcher { constructor( network: Network, - chain: FTChains, + chain: FTEVMChain, finalizedBlockTag: BlockTag = 'latest', isTest = false ) { @@ -154,8 +154,8 @@ export class FTEVMWatcher extends Watcher { } // we do not need to compare the lastBlockTime from tokenRouter and swapLayer as they both use toBlock - const lastBlockTime = tokenRouterResults.lastBlockTime; - return makeBlockKey(toBlock.toString(), lastBlockTime.toString()); + const lastBlockTime = new Date(tokenRouterResults.lastBlockTime * 1000); + return makeBlockKey(toBlock.toString(), lastBlockTime.toISOString()); } // saves items in smaller batches to reduce the impact in any case anything fails diff --git a/watcher/src/watchers/FTSolanaWatcher.ts b/watcher/src/watchers/FTSolanaWatcher.ts index ea911e5d..ccea6ab0 100644 --- a/watcher/src/watchers/FTSolanaWatcher.ts +++ b/watcher/src/watchers/FTSolanaWatcher.ts @@ -44,6 +44,7 @@ import knex, { Knex } from 'knex'; import { assertEnvironmentVariable, parseWormholeSequenceFromLogs, + stringifyWithBigInt, } from '@wormhole-foundation/wormhole-monitor-common'; import { getLogger } from '../utils/logger'; import base58 from 'bs58'; @@ -53,7 +54,6 @@ import { TokenRouterProgramId, USDCMintAddress, } from '../fastTransfer/consts'; -import { FastMarketOrder } from '@wormhole-foundation/example-liquidity-layer-definitions'; export class FTSolanaWatcher extends SolanaWatcher { readonly network: Network; @@ -66,6 +66,9 @@ export class FTSolanaWatcher extends SolanaWatcher { readonly USDC_MINT: USDCMintAddress; readonly TOKEN_ROUTER_PROGRAM_ID: TokenRouterProgramId; readonly eventParser: EventParser; + // for `settleAuction*` tests, there wouldn't be a `fast_vaa_hash` since there is no db. + // but we still want to return to the unit test to check the remaining data + readonly isTest: boolean; constructor(network: Network, isTest: boolean = false) { super(network, 'ft'); @@ -91,6 +94,7 @@ export class FTSolanaWatcher extends SolanaWatcher { new PublicKey(this.MATCHING_ENGINE_PROGRAM_ID), this.matchingEngineBorshCoder ); + this.isTest = isTest; // hacky way to not connect to the db in tests // this is to allow ci to run without a db @@ -642,7 +646,7 @@ export class FTSolanaWatcher extends SolanaWatcher { res: VersionedTransactionResponse, ix: MessageCompiledInstruction, seq: number - ): Promise { + ): Promise { if (!res.meta?.innerInstructions) { throw new Error( `[parseSettleAuctionComplete] ${res.transaction.signatures[0]} no inner instructions` @@ -688,7 +692,11 @@ export class FTSolanaWatcher extends SolanaWatcher { // if there is a auction to settle, the auction must exist. And the pubkey // will be in the db since we parse everything chronologically const fast_vaa_hash = await this.getFastVaaHashFromAuctionPubkey(auctionAccountPubkey.pubkey); - + // We want to return the info even without the vaa hash if it's a testing environment + if (!fast_vaa_hash && !this.isTest) { + this.handleMissingFastVaaHash(auctionAccountPubkey.pubkey, 'parseSettleAuctionComplete'); + return null; + } const info = { fast_vaa_hash: fast_vaa_hash, repayment: BigInt(decodedData.data.amount.toString()), @@ -721,7 +729,7 @@ export class FTSolanaWatcher extends SolanaWatcher { async parseSettleAuctionNoneLocal( res: VersionedTransactionResponse, ix: MessageCompiledInstruction - ): Promise { + ): Promise { const accountKeys = await this.getAccountsByParsedTransaction(res.transaction.signatures[0]); const accountKeyIndexes = ix.accountKeyIndexes; if (accountKeyIndexes.length < 7) { @@ -731,7 +739,11 @@ export class FTSolanaWatcher extends SolanaWatcher { const auction = accountKeys[accountKeyIndexes[6]]; const fast_vaa_hash = await this.getFastVaaHashFromAuctionPubkey(auction.pubkey); - + // We want to return the info even without the vaa hash if its a testing environment + if (!fast_vaa_hash && !this.isTest) { + this.handleMissingFastVaaHash(auction.pubkey, 'parseSettleAuctionNoneLocal'); + return null; + } const info = { fast_vaa_hash: fast_vaa_hash, // No one executed anything so no repayment @@ -754,7 +766,7 @@ export class FTSolanaWatcher extends SolanaWatcher { async parseSettleAuctionNoneCctp( res: VersionedTransactionResponse, ix: MessageCompiledInstruction - ): Promise { + ): Promise { const accountKeys = await this.getAccountsByParsedTransaction(res.transaction.signatures[0]); const accountKeyIndexes = ix.accountKeyIndexes; @@ -766,6 +778,11 @@ export class FTSolanaWatcher extends SolanaWatcher { const auction = accountKeys[accountKeyIndexes[8]]; const fast_vaa_hash = await this.getFastVaaHashFromAuctionPubkey(auction.pubkey); + // We want to return the info even without the vaa hash if its a testing environment + if (!fast_vaa_hash && !this.isTest) { + this.handleMissingFastVaaHash(auction.pubkey, 'parseSettleAuctionNoneCctp'); + return null; + } const info = { fast_vaa_hash: fast_vaa_hash, @@ -778,6 +795,10 @@ export class FTSolanaWatcher extends SolanaWatcher { }; await this.saveFastTransferInfo('fast_transfer_settlements', info); + await this.updateMarketOrder({ + fast_vaa_hash, + status: FastTransferStatus.SETTLED, + }); return info; } @@ -919,6 +940,7 @@ export class FTSolanaWatcher extends SolanaWatcher { return auction; } + async getDbLatestAuctionHistoryIndex(): Promise { if (!this.pg) { this.logger.debug('No database connection, returning 0'); @@ -926,12 +948,11 @@ export class FTSolanaWatcher extends SolanaWatcher { } try { const result = await this.pg('auction_history_mapping').max('index as maxIndex').first(); - this.logger.debug('Latest auction history index query result:', result); const maxIndex = result && result.maxIndex !== null ? BigInt(result.maxIndex) : 0n; this.logger.info(`Latest auction history index: ${maxIndex}`); return maxIndex; } catch (error) { - this.logger.error('Failed to fetch the largest index from auction_history_mapping:', error); + this.logger.error(`Failed to fetch the largest index from auction_history_mapping: ${error}`); throw new Error('Database query failed'); } } @@ -949,10 +970,9 @@ export class FTSolanaWatcher extends SolanaWatcher { const result = await this.pg('auction_history_mapping') .where({ auction_pubkey: auction }) .first(); - this.logger.debug(`Auction history mapping for ${auction}:`, result); return result; } catch (error) { - this.logger.error(`Error fetching auction history mapping for ${auction}:`, error); + this.logger.error(`Error fetching auction history mapping for ${auction}: ${error}`); throw error; } } @@ -965,13 +985,13 @@ export class FTSolanaWatcher extends SolanaWatcher { return; } try { - const result = await this.pg('auction_history_mapping') + await this.pg('auction_history_mapping') .insert(mappings) .onConflict('auction_pubkey') .merge(); - this.logger.info(`Saved ${mappings.length} auction history mappings. Result:`, result); + this.logger.info(`Saved ${mappings.length} auction history mappings.`); } catch (error) { - this.logger.error('Error saving auction history mappings:', error); + this.logger.error(`Error saving auction history mappings: ${error}`); throw error; } } @@ -981,15 +1001,11 @@ export class FTSolanaWatcher extends SolanaWatcher { this.logger.debug('No database connection, skipping saveFastTransfer'); return; } - this.logger.debug(`Saving fast transfer ${fastTransfer.fast_vaa_hash}`); try { - const result = await this.pg('fast_transfers') - .insert(fastTransfer) - .onConflict('fast_vaa_id') - .merge(); - this.logger.info(`Saved fast transfer ${fastTransfer.fast_vaa_hash}. Result:`, result); + await this.pg('fast_transfers').insert(fastTransfer).onConflict('fast_vaa_id').merge(); + this.logger.info(`Saved fast transfer ${fastTransfer.fast_vaa_hash}.`); } catch (error) { - this.logger.error(`Error saving fast transfer ${fastTransfer.fast_vaa_hash}:`, error); + this.logger.error(`Error saving fast transfer ${fastTransfer.fast_vaa_hash}: ${error}`); throw error; } } @@ -1000,13 +1016,12 @@ export class FTSolanaWatcher extends SolanaWatcher { return; } try { - const result = await this.pg('market_orders') - .insert(update) - .onConflict('fast_vaa_hash') - .merge(); - this.logger.info(`Updated market order ${update.fast_vaa_id}. Result:`, result); + // some `FastTransferUpdate` have only `fast_vaa_id` or `fast_vaa_hash`. We want to merge base on this unique key + const conflictKey = update.fast_vaa_id ? 'fast_vaa_id' : 'fast_vaa_hash'; + await this.pg('market_orders').insert(update).onConflict(conflictKey).merge(); + this.logger.info(`Updated market order ${update.fast_vaa_id || update.fast_vaa_hash}.`); } catch (error) { - this.logger.error('Update data that failed:', update); + this.logger.error(`Update data that failed: ${stringifyWithBigInt(update)}`); throw error; } } @@ -1017,10 +1032,10 @@ export class FTSolanaWatcher extends SolanaWatcher { return; } try { - const result = await this.pg('fast_transfer_auctions').where(id).update(update); - this.logger.info(`Updated auction ${id.fast_vaa_hash}. Result:`, result); + await this.pg('fast_transfer_auctions').where(id).update(update); + this.logger.info(`Updated auction for fast vaa hash: ${id.fast_vaa_hash}.`); } catch (error) { - this.logger.error(`Error updating auction ${id.fast_vaa_hash}:`, error); + this.logger.error(`Error updating auction ${id.fast_vaa_hash}: ${error}`); throw error; } } @@ -1036,15 +1051,10 @@ export class FTSolanaWatcher extends SolanaWatcher { const result = await this.pg('fast_transfer_auctions') .where({ auction_pubkey: auctionPubkey.toBase58() }) .first(); - this.logger.debug( - `Fast VAA hash for auction pubkey ${auctionPubkey.toBase58()}:`, - result?.fast_vaa_hash - ); return result?.fast_vaa_hash || ''; } catch (error) { this.logger.error( - `Error fetching Fast VAA hash for auction pubkey ${auctionPubkey.toBase58()}:`, - error + `Error fetching Fast VAA hash for auction pubkey ${auctionPubkey.toBase58()}: ${error}` ); throw error; } @@ -1052,21 +1062,23 @@ export class FTSolanaWatcher extends SolanaWatcher { async saveFastTransferInfo( table: string, - info: - | FastMarketOrder - | FastTransferAuctionInfo - | FastTransferExecutionInfo - | FastTransferSettledInfo + info: FastTransferAuctionInfo | FastTransferExecutionInfo | FastTransferSettledInfo ): Promise { if (!this.pg) { this.logger.debug(`No database connection, skipping saveFastTransferInfo for table ${table}`); return; } try { - const result = await this.pg(table).insert(info); - this.logger.info(`Saved fast transfer info to table ${table}. Result:`, result); + await this.pg(table).insert(info); + this.logger.info( + `Saved fast transfer info to table ${table}. fast transfer: ${info.fast_vaa_hash}` + ); } catch (error) { - this.logger.error(`Error saving fast transfer info to table ${table}:`, error); + this.logger.error( + `Error saving fast transfer info to table ${table}: ${error}, info: ${stringifyWithBigInt( + info + )}` + ); throw error; } } @@ -1076,13 +1088,27 @@ export class FTSolanaWatcher extends SolanaWatcher { this.logger.debug('No database connection, skipping saveAuctionLogs'); return; } - this.logger.debug(`Attempting to save auction logs for ${auctionLogs.fast_vaa_hash}`); try { - const result = await this.pg('auction_logs').insert(auctionLogs); - this.logger.info(`Saved auction logs for ${auctionLogs.fast_vaa_hash}. Result:`, result); + await this.pg('auction_logs').insert(auctionLogs); + this.logger.info(`Saved auction logs for ${auctionLogs.fast_vaa_hash}.`); } catch (error) { - this.logger.error(`Error saving auction logs for ${auctionLogs.fast_vaa_hash}:`, error); + this.logger.error(`Error saving auction logs for ${auctionLogs.fast_vaa_hash}: ${error}`); throw error; } } + + // on Testnet, we might miss some auction <-> fast_vaa_hash mappings as the RPCs clean up old blocks. The only workaround is to skip parsing + // the settle instructions. + // on Mainnet, we parse from the first ever Fast Transfer, which means we will every info we need. If we don't it means we did something wrong. + private handleMissingFastVaaHash(auctionPubkey: PublicKey, context: string): void { + if (this.network === 'Mainnet') { + throw new Error( + `[${context}] No fast_vaa_hash found for auction ${auctionPubkey.toBase58()} in mainnet` + ); + } else { + this.logger.warn( + `[${context}] No fast_vaa_hash found for auction ${auctionPubkey.toBase58()} in testnet. Skipping...` + ); + } + } } diff --git a/watcher/src/watchers/utils.ts b/watcher/src/watchers/utils.ts index 577a7d7b..f868d6b4 100644 --- a/watcher/src/watchers/utils.ts +++ b/watcher/src/watchers/utils.ts @@ -19,6 +19,7 @@ import { NTTSolanaWatcher } from './NTTSolanaWatcher'; import { Chain, Network } from '@wormhole-foundation/sdk-base'; import { FTEVMWatcher } from './FTEVMWatcher'; import { FTSolanaWatcher } from './FTSolanaWatcher'; +import { isFTEVMChain } from '../fastTransfer/consts'; export function makeFinalizedWatcher(network: Network, chainName: Chain): Watcher { if (chainName === 'Solana') { @@ -126,20 +127,13 @@ export function makeFinalizedNTTWatcher(network: Network, chainName: Chain): Wat } export function makeFinalizedFTWatcher(network: Network, chainName: Chain): Watcher { - if ('Testnet') { - // These are testnet only chains - if (chainName === 'ArbitrumSepolia') { - return new FTEVMWatcher(network, chainName, 'finalized'); - } else if (chainName === 'Solana') { - return new FTSolanaWatcher(network); - } else { - throw new Error( - `Attempted to create finalized NTT watcher for unsupported testnet chain ${chainName}` - ); - } + if (chainName === 'Solana') { + return new FTSolanaWatcher(network); + } else if (isFTEVMChain(chainName, network)) { + return new FTEVMWatcher(network, chainName, 'finalized'); } else { throw new Error( - `Attempted to create finalized NTT watcher for unsupported network ${network}, ${chainName}` + `Attempted to create finalized FT watcher for unsupported chain ${chainName} on ${network}` ); } }