diff --git a/.changeset/large-cycles-admire.md b/.changeset/large-cycles-admire.md new file mode 100644 index 0000000000..c500d0e8f5 --- /dev/null +++ b/.changeset/large-cycles-admire.md @@ -0,0 +1,11 @@ +--- +'@chainlink/mobula-state-adapter': major +--- + +**BREAKING CHANGE:** Migrated from v1 firehose WebSocket API to v2 targeted asset_id-based subscriptions. + +## Upgrade Instructions + +No changes are required on the NOPs part in order to upgrade. + +**Do not upgrade to this version until instructed to do so.** diff --git a/.pnp.cjs b/.pnp.cjs index 3db96094bd..c2920477db 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6839,6 +6839,25 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["npm:2.11.0", {\ + "packageLocation": "./.yarn/cache/@chainlink-external-adapter-framework-npm-2.11.0-60f407ab19-7a892deca8.zip/node_modules/@chainlink/external-adapter-framework/",\ + "packageDependencies": [\ + ["@chainlink/external-adapter-framework", "npm:2.11.0"],\ + ["@date-fns/tz", "npm:1.4.1"],\ + ["ajv", "npm:8.17.1"],\ + ["axios", "npm:1.13.2"],\ + ["eventsource", "npm:4.0.0"],\ + ["fastify", "npm:5.6.2"],\ + ["ioredis", "npm:5.8.2"],\ + ["mock-socket", "npm:9.3.1"],\ + ["pino", "npm:10.1.0"],\ + ["pino-pretty", "npm:13.1.2"],\ + ["prom-client", "npm:15.1.3"],\ + ["redlock", "npm:5.0.0-beta.2"],\ + ["ws", "virtual:664e8a533bd640fe25950f78953f988492df49452c3758ee6999f93e099115859a376c456a6fbf9c1131444282215c105622c72636d6d7f644610d55a51628d7#npm:8.18.3"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:2.11.1", {\ "packageLocation": "./.yarn/cache/@chainlink-external-adapter-framework-npm-2.11.1-3ad4f998bb-5d0ba40462.zip/node_modules/@chainlink/external-adapter-framework/",\ "packageDependencies": [\ @@ -7840,7 +7859,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./packages/sources/mobula-state/",\ "packageDependencies": [\ ["@chainlink/mobula-state-adapter", "workspace:packages/sources/mobula-state"],\ - ["@chainlink/external-adapter-framework", "npm:2.8.0"],\ + ["@chainlink/external-adapter-framework", "npm:2.11.0"],\ ["@sinonjs/fake-timers", "npm:9.1.2"],\ ["@types/jest", "npm:29.5.14"],\ ["@types/node", "npm:22.14.1"],\ diff --git a/.yarn/cache/@chainlink-external-adapter-framework-npm-2.11.0-60f407ab19-7a892deca8.zip b/.yarn/cache/@chainlink-external-adapter-framework-npm-2.11.0-60f407ab19-7a892deca8.zip new file mode 100644 index 0000000000..6b261e84e7 Binary files /dev/null and b/.yarn/cache/@chainlink-external-adapter-framework-npm-2.11.0-60f407ab19-7a892deca8.zip differ diff --git a/packages/sources/mobula-state/package.json b/packages/sources/mobula-state/package.json index 2a572fcab0..563b840a68 100644 --- a/packages/sources/mobula-state/package.json +++ b/packages/sources/mobula-state/package.json @@ -36,7 +36,7 @@ "typescript": "5.8.3" }, "dependencies": { - "@chainlink/external-adapter-framework": "2.8.0", + "@chainlink/external-adapter-framework": "2.11.0", "tslib": "2.4.1" } } diff --git a/packages/sources/mobula-state/src/config/includes.json b/packages/sources/mobula-state/src/config/includes.json new file mode 100644 index 0000000000..0bf7bd13ae --- /dev/null +++ b/packages/sources/mobula-state/src/config/includes.json @@ -0,0 +1,2367 @@ +[ + { + "from": "EZETH", + "to": "CUSTOMQUOTE", + "includes": [ + { + "from": "102478632", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "ALETH", + "to": "ETH", + "includes": [ + { + "from": "101618408", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "ALETH", + "to": "USD", + "includes": [ + { + "from": "101618408", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "AMPL", + "to": "USD", + "includes": [ + { + "from": "100000711", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "ANDY", + "to": "USD", + "includes": [ + { + "from": "102502391", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "ANON", + "to": "USD", + "includes": [ + { + "from": "102501268", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "ARCH", + "to": "USD", + "includes": [ + { + "from": "60896", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "AUSD", + "to": "USD", + "includes": [ + { + "from": "102484088", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "BANK", + "to": "USD", + "includes": [ + { + "from": "59064", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "BCSPX", + "to": "USD", + "includes": [ + { + "from": "102501646", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "BOME", + "to": "SOL", + "includes": [ + { + "from": "102482723", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "BONK", + "to": "SOL", + "includes": [ + { + "from": "101676379", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "BONKSOL", + "to": "USD", + "includes": [ + { + "from": "102482904", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "BR", + "to": "USD", + "includes": [ + { + "from": "102504264", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "BSOL", + "to": "SOL", + "includes": [ + { + "from": "1501", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "BSOL", + "to": "USD", + "includes": [ + { + "from": "1501", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "BTC", + "to": "USD", + "includes": [ + { + "from": "100001656", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "BUDDY", + "to": "USD", + "includes": [ + { + "from": "102503782", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "BUSD", + "to": "USD", + "includes": [ + { + "from": "100001586", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "CAT", + "to": "ETH", + "includes": [ + { + "from": "102484495", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "CAT", + "to": "USD", + "includes": [ + { + "from": "102484495", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "CBADA", + "to": "USD", + "includes": [ + { + "from": "60212", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "CBBTC", + "to": "BTC", + "includes": [ + { + "from": "102484820", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "CBBTC", + "to": "ETH", + "includes": [ + { + "from": "102484820", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "CBBTC", + "to": "USD", + "includes": [ + { + "from": "102484820", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "CBDOGE", + "to": "USD", + "includes": [ + { + "from": "60214", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "CBETH", + "to": "ETH", + "includes": [ + { + "from": "100029813", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "CBXRP", + "to": "ETH", + "includes": [ + { + "from": "60213", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "CBXRP", + "to": "USD", + "includes": [ + { + "from": "60213", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "CFG", + "to": "ETH", + "includes": [ + { + "from": "59682", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "CRVUSD", + "to": "USD", + "includes": [ + { + "from": "1874", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "CUSD", + "to": "USD", + "includes": [ + { + "from": "100002403", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "CUSDO", + "to": "USD", + "includes": [ + { + "from": "102502581", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "DAI", + "to": "USD", + "includes": [ + { + "from": "102502229", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "DERIVE", + "to": "USD", + "includes": [ + { + "from": "102501273", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "DEUSD", + "to": "USD", + "includes": [ + { + "from": "102484275", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "DOLA", + "to": "USD", + "includes": [ + { + "from": "100003811", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "EBTC", + "to": "BTC", + "includes": [ + { + "from": "102498397", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "EBTC", + "to": "USD", + "includes": [ + { + "from": "102498397", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "ETH", + "to": "USD", + "includes": [ + { + "from": "100004304", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "ETHX", + "to": "ETH", + "includes": [ + { + "from": "6409", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "EUL", + "to": "USD", + "includes": [ + { + "from": "100050320", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "EURC", + "to": "USD", + "includes": [ + { + "from": "100051949", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "EURE", + "to": "USD", + "includes": [ + { + "from": "102503543", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "EZETH", + "to": "ETH", + "includes": [ + { + "from": "102478632", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "EZETH", + "to": "USD", + "includes": [ + { + "from": "102478632", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "EZSOL", + "to": "USD", + "includes": [ + { + "from": "102484446", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "FAI", + "to": "USD", + "includes": [ + { + "from": "102478915", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "FBTC", + "to": "USD", + "includes": [ + { + "from": "102484001", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "FRAX", + "to": "USD", + "includes": [ + { + "from": "100004858", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "FRXETH", + "to": "ETH", + "includes": [ + { + "from": "102295500", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "FRXETH", + "to": "USD", + "includes": [ + { + "from": "102295500", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "FRXUSD", + "to": "USD", + "includes": [ + { + "from": "102503183", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "FWOG", + "to": "USD", + "includes": [ + { + "from": "102484239", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "GHO", + "to": "USD", + "includes": [ + { + "from": "2921", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "GRAIL", + "to": "USD", + "includes": [ + { + "from": "101676771", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "HAI", + "to": "USD", + "includes": [ + { + "from": "102480809", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "HBARX", + "to": "USD", + "includes": [ + { + "from": "3178", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "HOUSE", + "to": "USD", + "includes": [ + { + "from": "102504423", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "HSOL", + "to": "BBSOL", + "includes": [ + { + "from": "102483137", + "to": "102484775", + "inverse": false + } + ] + }, + { + "from": "HSOL", + "to": "SOL", + "includes": [ + { + "from": "102483137", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "HSOL", + "to": "USD", + "includes": [ + { + "from": "102483137", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "HUBSOL", + "to": "BBSOL", + "includes": [ + { + "from": "102483423", + "to": "102484775", + "inverse": false + } + ] + }, + { + "from": "HUBSOL", + "to": "SOL", + "includes": [ + { + "from": "102483423", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "HUBSOL", + "to": "USD", + "includes": [ + { + "from": "102483423", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "HYUSD", + "to": "USD", + "includes": [ + { + "from": "3248", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "INF", + "to": "BBSOL", + "includes": [ + { + "from": "6258", + "to": "102484775", + "inverse": false + } + ] + }, + { + "from": "INF", + "to": "SOL", + "includes": [ + { + "from": "6258", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "INF", + "to": "USD", + "includes": [ + { + "from": "6258", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "JITOSOL", + "to": "BBSOL", + "includes": [ + { + "from": "3576", + "to": "102484775", + "inverse": false + } + ] + }, + { + "from": "JITOSOL", + "to": "SOL", + "includes": [ + { + "from": "3576", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "JITOSOL", + "to": "USD", + "includes": [ + { + "from": "3576", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "JLP", + "to": "USD", + "includes": [ + { + "from": "10749", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "JUPSOL", + "to": "SOL", + "includes": [ + { + "from": "102483173", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "JUPSOL", + "to": "USD", + "includes": [ + { + "from": "102483173", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "KHYPE", + "to": "HYPE", + "includes": [ + { + "from": "61053", + "to": "102498883", + "inverse": false + } + ] + }, + { + "from": "KHYPE", + "to": "USD", + "includes": [ + { + "from": "61053", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "KLIMA", + "to": "USD", + "includes": [ + { + "from": "8536", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "KTA", + "to": "USD", + "includes": [ + { + "from": "102503855", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "KYSOL", + "to": "USD", + "includes": [ + { + "from": "102499675", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "LBTC", + "to": "BTC", + "includes": [ + { + "from": "102484658", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "LBTC", + "to": "USD", + "includes": [ + { + "from": "102484658", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "LISUSD", + "to": "USD", + "includes": [ + { + "from": "3200", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "LSETH", + "to": "ETH", + "includes": [ + { + "from": "3881", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "LST", + "to": "BBSOL", + "includes": [ + { + "from": "10642", + "to": "102484775", + "inverse": false + } + ] + }, + { + "from": "LST", + "to": "SOL", + "includes": [ + { + "from": "10642", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "LST", + "to": "USD", + "includes": [ + { + "from": "10642", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "LUSD", + "to": "USD", + "includes": [ + { + "from": "100006862", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "LVLUSD", + "to": "USD", + "includes": [ + { + "from": "102500883", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MAG7.SSI", + "to": "USD", + "includes": [ + { + "from": "102500949", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MANYU", + "to": "USD", + "includes": [ + { + "from": "60756", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MATIC", + "to": "USD", + "includes": [ + { + "from": "100007238", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "METH", + "to": "ETH", + "includes": [ + { + "from": "11019", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "METH", + "to": "USD", + "includes": [ + { + "from": "11019", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MIGGLES", + "to": "ETH", + "includes": [ + { + "from": "102484057", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "MIGGLES", + "to": "USD", + "includes": [ + { + "from": "102484057", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MIM", + "to": "USD", + "includes": [ + { + "from": "100007100", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MIMATIC", + "to": "USD", + "includes": [ + { + "from": "102480396", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MKR", + "to": "ETH", + "includes": [ + { + "from": "10982", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "MKR", + "to": "USD", + "includes": [ + { + "from": "10982", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MOOBIFI", + "to": "USD", + "includes": [ + { + "from": "102501442", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MSOL", + "to": "BBSOL", + "includes": [ + { + "from": "100008051", + "to": "102484775", + "inverse": false + } + ] + }, + { + "from": "MSOL", + "to": "SOL", + "includes": [ + { + "from": "100008051", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "MSOL", + "to": "USD", + "includes": [ + { + "from": "100008051", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MUSD", + "to": "USD", + "includes": [ + { + "from": "62281", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "MYX", + "to": "USD", + "includes": [ + { + "from": "59313", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "NEET", + "to": "USD", + "includes": [ + { + "from": "59216", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "OETH", + "to": "ETH", + "includes": [ + { + "from": "4953", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "OETH", + "to": "USD", + "includes": [ + { + "from": "4953", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "OS", + "to": "S", + "includes": [ + { + "from": "102502587", + "to": "102501606", + "inverse": false + } + ] + }, + { + "from": "OUSDT", + "to": "USD", + "includes": [ + { + "from": "102504126", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "PBTC", + "to": "USD", + "includes": [ + { + "from": "61216", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "PUFETH", + "to": "ETH", + "includes": [ + { + "from": "102480107", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "PUFETH", + "to": "USD", + "includes": [ + { + "from": "102480107", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "PUMPBTC", + "to": "BTC", + "includes": [ + { + "from": "102484042", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "PUMPBTC", + "to": "USD", + "includes": [ + { + "from": "102484042", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "PUSD", + "to": "USD", + "includes": [ + { + "from": "60895", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "RAI", + "to": "ETH", + "includes": [ + { + "from": "100009665", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "RETH", + "to": "ETH", + "includes": [ + { + "from": "100009986", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "RETH", + "to": "USD", + "includes": [ + { + "from": "100009986", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "RSETH", + "to": "ETH", + "includes": [ + { + "from": "102479784", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "RSETH", + "to": "USD", + "includes": [ + { + "from": "102502051", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "RSWETH", + "to": "ETH", + "includes": [ + { + "from": "102478385", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "RSWETH", + "to": "USD", + "includes": [ + { + "from": "102478385", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "RUSD", + "to": "USD", + "includes": [ + { + "from": "102503384", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SAVAX", + "to": "ETH", + "includes": [ + { + "from": "100001472", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "SAVAX", + "to": "USD", + "includes": [ + { + "from": "100001472", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SAVUSD", + "to": "USD", + "includes": [ + { + "from": "102502274", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SCBTC", + "to": "BTC", + "includes": [ + { + "from": "102504365", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "SCBTC", + "to": "USD", + "includes": [ + { + "from": "102504365", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SCETH", + "to": "ETH", + "includes": [ + { + "from": "102503669", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "SCETH", + "to": "USD", + "includes": [ + { + "from": "102503669", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SCUSD", + "to": "USD", + "includes": [ + { + "from": "102501746", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SDAI", + "to": "USD", + "includes": [ + { + "from": "9595", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SFRXETH", + "to": "ETH", + "includes": [ + { + "from": "658", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "SFRXETH", + "to": "USD", + "includes": [ + { + "from": "658", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SFRXUSD", + "to": "USD", + "includes": [ + { + "from": "102503211", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SHADOW", + "to": "USD", + "includes": [ + { + "from": "102502566", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SKY", + "to": "ETH", + "includes": [ + { + "from": "102484615", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "SKY", + "to": "USD", + "includes": [ + { + "from": "102484615", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SKYAI", + "to": "USD", + "includes": [ + { + "from": "59102", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SOL", + "to": "USD", + "includes": [ + { + "from": "100010811", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SOLVBTC", + "to": "BTC", + "includes": [ + { + "from": "102497875", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "SOLVBTC", + "to": "USD", + "includes": [ + { + "from": "102497875", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SOLVBTC.BBN", + "to": "USD", + "includes": [ + { + "from": "102503039", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "STBTC", + "to": "BTC", + "includes": [ + { + "from": "102498988", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "STBTC", + "to": "USD", + "includes": [ + { + "from": "102498988", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "STONE", + "to": "USD", + "includes": [ + { + "from": "10755", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "STS", + "to": "USD", + "includes": [ + { + "from": "102500875", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SUPEROETHB", + "to": "ETH", + "includes": [ + { + "from": "102502963", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "SUPEROETHB", + "to": "USD", + "includes": [ + { + "from": "102502963", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SUSD", + "to": "ETH", + "includes": [ + { + "from": "100008494", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "SUSD", + "to": "USD", + "includes": [ + { + "from": "100008494", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SUSDAI", + "to": "USD", + "includes": [ + { + "from": "59669", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SUSDE", + "to": "USD", + "includes": [ + { + "from": "102479623", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SUSDS", + "to": "USD", + "includes": [ + { + "from": "102500508", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SWETH", + "to": "ETH", + "includes": [ + { + "from": "6571", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "SWETH", + "to": "USD", + "includes": [ + { + "from": "6571", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SWPX", + "to": "USD", + "includes": [ + { + "from": "102502212", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "SYRUPUSDC", + "to": "USD", + "includes": [ + { + "from": "102503872", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "TBTC", + "to": "USD", + "includes": [ + { + "from": "6639", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "TKX", + "to": "USD", + "includes": [ + { + "from": "100011844", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "TRENCHER", + "to": "USD", + "includes": [ + { + "from": "59160", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "TROLL", + "to": "USD", + "includes": [ + { + "from": "59816", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "UNIBTC", + "to": "BTC", + "includes": [ + { + "from": "102484359", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "UNIBTC", + "to": "USD", + "includes": [ + { + "from": "102484359", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USD+", + "to": "USD", + "includes": [ + { + "from": "102501545", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USD0", + "to": "USD", + "includes": [ + { + "from": "102483548", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USD0++", + "to": "USD", + "includes": [ + { + "from": "102483997", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDA", + "to": "USD", + "includes": [ + { + "from": "102499948", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDAI", + "to": "USD", + "includes": [ + { + "from": "59629", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDE", + "to": "USD", + "includes": [ + { + "from": "102484651", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDF", + "to": "USD", + "includes": [ + { + "from": "102503745", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDG", + "to": "USD", + "includes": [ + { + "from": "102499321", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDH", + "to": "USD", + "includes": [ + { + "from": "63494", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDHL", + "to": "USD", + "includes": [ + { + "from": "60409", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDL", + "to": "USD", + "includes": [ + { + "from": "102482551", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDO", + "to": "USD", + "includes": [ + { + "from": "102502570", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDS", + "to": "USD", + "includes": [ + { + "from": "102502809", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDT0", + "to": "USD", + "includes": [ + { + "from": "102502522", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDTB", + "to": "USD", + "includes": [ + { + "from": "102500764", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDV", + "to": "USD", + "includes": [ + { + "from": "10627", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDX", + "to": "USD", + "includes": [ + { + "from": "102502846", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDY", + "to": "USD", + "includes": [ + { + "from": "6044", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USDZ", + "to": "USD", + "includes": [ + { + "from": "102483988", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USR", + "to": "USD", + "includes": [ + { + "from": "102484696", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "USX", + "to": "USD", + "includes": [ + { + "from": "62234", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "VAI", + "to": "USD", + "includes": [ + { + "from": "7151", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "VSOL", + "to": "BBSOL", + "includes": [ + { + "from": "102483515", + "to": "102484775", + "inverse": false + } + ] + }, + { + "from": "VSOL", + "to": "SOL", + "includes": [ + { + "from": "102483515", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "VSOL", + "to": "USD", + "includes": [ + { + "from": "102483515", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "WAMPL", + "to": "ETH", + "includes": [ + { + "from": "102504101", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "WAMPL", + "to": "USD", + "includes": [ + { + "from": "102504101", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "WBCOIN", + "to": "USD", + "includes": [ + { + "from": "102504902", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "WBTC", + "to": "BTC", + "includes": [ + { + "from": "9993", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "WBTC", + "to": "ETH", + "includes": [ + { + "from": "9993", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "WBTC", + "to": "USD", + "includes": [ + { + "from": "102502254", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "WEETH", + "to": "ETH", + "includes": [ + { + "from": "102500497", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "WEETH", + "to": "USD", + "includes": [ + { + "from": "102500497", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "WIF", + "to": "SOL", + "includes": [ + { + "from": "102479013", + "to": "100010811", + "inverse": false + } + ] + }, + { + "from": "WS", + "to": "USD", + "includes": [ + { + "from": "102500810", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "WSTETH", + "to": "ETH", + "includes": [ + { + "from": "102501417", + "to": "100004304", + "inverse": false + } + ] + }, + { + "from": "WSTETH", + "to": "USD", + "includes": [ + { + "from": "102501417", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "WSTHYPE", + "to": "HYPE", + "includes": [ + { + "from": "102503958", + "to": "102498883", + "inverse": false + } + ] + }, + { + "from": "X33", + "to": "USD", + "includes": [ + { + "from": "102503924", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "XUSD", + "to": "USD", + "includes": [ + { + "from": "102502680", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "YBTC.B", + "to": "BTC", + "includes": [ + { + "from": "60778", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "YU", + "to": "USD", + "includes": [ + { + "from": "59793", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "ZBTC", + "to": "BTC", + "includes": [ + { + "from": "102504385", + "to": "100001656", + "inverse": false + } + ] + }, + { + "from": "ZBTC", + "to": "USD", + "includes": [ + { + "from": "102504385", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "ZBU", + "to": "USD", + "includes": [ + { + "from": "8829", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "ZYN", + "to": "USD", + "includes": [ + { + "from": "102480615", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "HYPE", + "to": "USD", + "includes": [ + { + "from": "102498883", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "S", + "to": "USD", + "includes": [ + { + "from": "102501606", + "to": "USD", + "inverse": false + } + ] + }, + { + "from": "BBSOL", + "to": "USD", + "includes": [ + { + "from": "102484775", + "to": "USD", + "inverse": false + } + ] + } +] diff --git a/packages/sources/mobula-state/src/endpoint/price.ts b/packages/sources/mobula-state/src/endpoint/price.ts index 3cab43e31e..f6cb28b7c7 100644 --- a/packages/sources/mobula-state/src/endpoint/price.ts +++ b/packages/sources/mobula-state/src/endpoint/price.ts @@ -1,19 +1,20 @@ -import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' -import { InputParameters } from '@chainlink/external-adapter-framework/validation' +import { CryptoPriceEndpoint } from '@chainlink/external-adapter-framework/adapter' import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' +import { InputParameters } from '@chainlink/external-adapter-framework/validation' import { config } from '../config' import { wsTransport } from '../transport/price' export const inputParameters = new InputParameters( { base: { - aliases: ['from', 'coin', 'symbol', 'market'], + aliases: ['from', 'coin', 'symbol'], required: true, type: 'string', description: 'The symbol of symbols of the currency to query', }, quote: { - aliases: ['to', 'convert'], + // 'market' must be included in quote aliases for CryptoPriceEndpoint compatibility. + aliases: ['to', 'market'], required: true, type: 'string', description: 'The symbol of the currency to convert to', @@ -21,7 +22,7 @@ export const inputParameters = new InputParameters( }, [ { - base: 'ETH', + base: 'EZETH', quote: 'USD', }, ], @@ -33,7 +34,7 @@ export type BaseEndpointTypes = { Settings: typeof config.settings } -export const endpoint = new AdapterEndpoint({ +export const endpoint = new CryptoPriceEndpoint({ name: 'price', aliases: ['state', 'crypto'], transport: wsTransport, diff --git a/packages/sources/mobula-state/src/index.ts b/packages/sources/mobula-state/src/index.ts index 9b6fd4d5f8..98d7db9b8d 100644 --- a/packages/sources/mobula-state/src/index.ts +++ b/packages/sources/mobula-state/src/index.ts @@ -1,13 +1,15 @@ import { expose, ServerInstance } from '@chainlink/external-adapter-framework' -import { Adapter } from '@chainlink/external-adapter-framework/adapter' +import { PriceAdapter } from '@chainlink/external-adapter-framework/adapter' import { config } from './config' +import includes from './config/includes.json' import { fundingRate, price } from './endpoint' -export const adapter = new Adapter({ +export const adapter = new PriceAdapter({ defaultEndpoint: price.name, name: 'MOBULA_STATE', config, endpoints: [price, fundingRate], + includes, }) export const server = (): Promise => expose(adapter) diff --git a/packages/sources/mobula-state/src/transport/price.ts b/packages/sources/mobula-state/src/transport/price.ts index 3487fbcdc7..f5196b5798 100644 --- a/packages/sources/mobula-state/src/transport/price.ts +++ b/packages/sources/mobula-state/src/transport/price.ts @@ -1,4 +1,5 @@ -import { WebSocketTransport } from '@chainlink/external-adapter-framework/transports' +import { WebsocketReverseMappingTransport } from '@chainlink/external-adapter-framework/transports' +import { ProviderResult } from '@chainlink/external-adapter-framework/util' import { BaseEndpointTypes } from '../endpoint/price' export interface WSResponse { @@ -9,6 +10,17 @@ export interface WSResponse { volume24h: number baseSymbol: string quoteSymbol: string + baseID: string + quoteID: string +} + +// Interface for Mobula WebSocket subscription message +interface MobulaSubscribeMessage { + type: 'feed' + authorization: string + kind: 'asset_ids' + asset_ids: number[] + quote_id?: number } export type WsTransportTypes = BaseEndpointTypes & { @@ -16,44 +28,110 @@ export type WsTransportTypes = BaseEndpointTypes & { WsMessage: WSResponse } } -export const wsTransport = new WebSocketTransport({ - url: (context) => context.adapterSettings.WS_API_ENDPOINT, - handlers: { - open: (connection, context) => { - connection.send( - JSON.stringify({ type: 'feed', authorization: context.adapterSettings.API_KEY }), - ) - }, - message(message) { - if (!message.price) { + +// Hardcoded quote currency asset IDs +// These are commonly used quotes that don't need includes.json entries +const QUOTE_ASSET_IDS: Record = { + BTC: 100001656, // Bitcoin + ETH: 100004304, // Ethereum + SOL: 100010811, // Solana + HYPE: 102498883, // Hyperliquid + S: 102501606, // Sonic (using 'S' as symbol) + BBSOL: 102484775, // Bybit Staked SOL +} + +// Get asset ID from the resolved symbol (after framework includes are applied) +const getAssetId = (symbol: string): number => { + const parsed = Number.parseInt(symbol, 10) + + if (!Number.isNaN(parsed)) { + return parsed + } + + throw new Error( + `Unable to resolve asset ID for symbol: ${symbol}. Please ensure includes.json is configured for this symbol.`, + ) +} + +// Map quote symbols to IDs +// Returns: number for crypto quotes, 'USD' string for USD, or throws for unmapped symbols +const getQuoteId = (quote: string): number | 'USD' => { + const upperQuote = quote.toUpperCase() + + // USD doesn't need a numeric quote_id in the API, return string for composite key + if (upperQuote === 'USD') { + return 'USD' + } + + // Check hardcoded quote mappings (common crypto quotes) + const hardcodedId = QUOTE_ASSET_IDS[upperQuote] + if (hardcodedId) { + return hardcodedId + } + + // Check if quote is already a number (asset ID) + const parsed = Number.parseInt(quote, 10) + if (!Number.isNaN(parsed)) { + return parsed + } + + // Quote not found - must be in includes.json or use hardcoded quote + throw new Error( + `Unable to resolve quote ID for symbol: ${quote}. Please add a base/quote pair to includes.json or use a supported quote currency (BTC, ETH, SOL, HYPE, S, BBSOL, USD).`, + ) +} + +export const wsTransport: WebsocketReverseMappingTransport = + new WebsocketReverseMappingTransport({ + url: (context) => context.adapterSettings.WS_API_ENDPOINT, + handlers: { + message: (message): ProviderResult[] => { + if (!message.price) { + return [] + } + + // Use composite key for reverse mapping: baseID-quoteID + const compositeKey = `${message.baseID}-${message.quoteID}` + const params = wsTransport.getReverseMapping(compositeKey) + if (!params) { + return [] + } + return [ { - params: { - base: message.baseSymbol, - quote: message.quoteSymbol, - }, + params, // Use exact original params user sent response: { - errorMessage: 'No price in message', - statusCode: 500, - }, - }, - ] - } - - return [ - { - params: { base: message.baseSymbol, quote: message.quoteSymbol }, - response: { - result: message.price, - data: { result: message.price, - }, - timestamps: { - providerIndicatedTimeUnixMs: message.timestamp, + data: { result: message.price }, + timestamps: { providerIndicatedTimeUnixMs: message.timestamp }, }, }, - }, - ] + ] + }, + }, + builders: { + subscribeMessage: (params, context) => { + const assetId = getAssetId(params.base) + const quoteId = getQuoteId(params.quote) + + // Store mapping using composite key: baseID-quoteID -> original user params + const compositeKey = `${assetId}-${quoteId}` + wsTransport.setReverseMapping(compositeKey, params) + + const subscribeMsg: MobulaSubscribeMessage = { + type: 'feed', + authorization: context.adapterSettings.API_KEY, + kind: 'asset_ids', + asset_ids: [assetId], + } + + // Only add quote_id to API message for non-USD quotes + if (quoteId !== 'USD') { + subscribeMsg.quote_id = quoteId + } + + return subscribeMsg + }, + unsubscribeMessage: () => undefined, }, - }, -}) + }) diff --git a/packages/sources/mobula-state/test-payload.json b/packages/sources/mobula-state/test-payload.json index d28cef9c1a..0d67eb292c 100644 --- a/packages/sources/mobula-state/test-payload.json +++ b/packages/sources/mobula-state/test-payload.json @@ -1,6 +1,54 @@ { - "requests": [{ - "from": "ETH", - "to": "USD" - }] + "requests": [ + { + "from": "EZETH", + "to": "USD" + }, + { + "from": "EZETH", + "to": "ETH" + }, + { + "from": "CBETH", + "to": "ETH" + }, + { + "from": "LBTC", + "to": "BTC" + }, + { + "from": "GHO", + "to": "USD" + }, + { + "from": "TESTCOIN", + "to": "USD", + "overrides": { + "mobula-state": { + "TESTCOIN": "999888777" + } + } + }, + { + "from": "ANOTHERCOIN", + "to": "BTC", + "overrides": { + "mobula-state": { + "ANOTHERCOIN": "111222333" + } + } + }, + { + "from": "102478632", + "to": "USD" + }, + { + "from": "2921", + "to": "100001656" + }, + { + "from": "100029813", + "to": "100004304" + } + ] } diff --git a/packages/sources/mobula-state/test/integration/__snapshots__/adapter.test.ts.snap b/packages/sources/mobula-state/test/integration/__snapshots__/adapter.test.ts.snap index b1719f73ce..338b363f75 100644 --- a/packages/sources/mobula-state/test/integration/__snapshots__/adapter.test.ts.snap +++ b/packages/sources/mobula-state/test/integration/__snapshots__/adapter.test.ts.snap @@ -10,8 +10,8 @@ exports[`websocket funding rate endpoint have data should return success 1`] = ` "result": null, "statusCode": 200, "timestamps": { - "providerDataReceivedUnixMs": 4048, - "providerDataStreamEstablishedUnixMs": 4040, + "providerDataReceivedUnixMs": 15158, + "providerDataStreamEstablishedUnixMs": 15150, }, } `; @@ -26,8 +26,8 @@ exports[`websocket funding rate endpoint have partial data return success 1`] = "result": null, "statusCode": 200, "timestamps": { - "providerDataReceivedUnixMs": 4048, - "providerDataStreamEstablishedUnixMs": 4040, + "providerDataReceivedUnixMs": 16168, + "providerDataStreamEstablishedUnixMs": 15150, }, } `; @@ -43,22 +43,213 @@ exports[`websocket funding rate endpoint no data should return failure 1`] = ` } `; -exports[`websocket price endpoint have data should return success 1`] = ` +exports[`websocket price endpoint CBETH/ETH should return success - tests includes.json + hardcoded ETH quote 1`] = ` +{ + "data": { + "result": 1.0456, + }, + "result": 1.0456, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 8088, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint EZETH/ETH should return success - tests hardcoded ETH quote 1`] = ` +{ + "data": { + "result": 1.0612, + }, + "result": 1.0612, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 7078, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint EZETH/USD should return success - tests includes.json mapping 1`] = ` { "data": { - "result": 2325.847186068699, + "result": 4233.15, }, - "result": 2325.847186068699, + "result": 4233.15, "statusCode": 200, "timestamps": { "providerDataReceivedUnixMs": 1018, "providerDataStreamEstablishedUnixMs": 1010, - "providerIndicatedTimeUnixMs": 1726648165000, + "providerIndicatedTimeUnixMs": 1514764861500, }, } `; -exports[`websocket price endpoint no data should return failure 1`] = ` +exports[`websocket price endpoint GHO/USD should return success - tests includes.json mapping 1`] = ` +{ + "data": { + "result": 1.0012, + }, + "result": 1.0012, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 10108, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint LBTC/BTC should return success - tests includes.json + hardcoded BTC quote 1`] = ` +{ + "data": { + "result": 0.9985, + }, + "result": 0.9985, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 9098, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint base override with BTC quote should return success 1`] = ` +{ + "data": { + "result": 0.00123456, + }, + "result": 0.00123456, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 3038, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint base override with ETH quote should return success 1`] = ` +{ + "data": { + "result": 0.0456789, + }, + "result": 0.0456789, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 4048, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint base override with asset ID should return success 1`] = ` +{ + "data": { + "result": 125.67, + }, + "result": 125.67, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 2028, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint direct asset IDs with crypto quote should return success 1`] = ` +{ + "data": { + "result": 1.0456, + }, + "result": 1.0456, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 8088, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint direct asset IDs with hardcoded SOL quote should return success 1`] = ` +{ + "data": { + "result": 3.456, + }, + "result": 3.456, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 9098, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint direct base and quote asset IDs should return success 1`] = ` +{ + "data": { + "result": 0.0000102, + }, + "result": 0.0000102, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 10108, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint direct base asset ID should return success 1`] = ` +{ + "data": { + "result": 4233.15, + }, + "result": 4233.15, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 7078, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint quote override with asset ID should return success 1`] = ` +{ + "data": { + "result": 1.0612, + }, + "result": 1.0612, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 7078, + "providerDataStreamEstablishedUnixMs": 1010, + "providerIndicatedTimeUnixMs": 1514764861500, + }, +} +`; + +exports[`websocket price endpoint unmapped symbol should return failure 1`] = ` +{ + "error": { + "message": "The EA has not received any values from the Data Provider for the requested data yet. Retry after a short delay, and if the problem persists raise this issue in the relevant channels.", + "name": "AdapterError", + }, + "status": "errored", + "statusCode": 504, +} +`; + +exports[`websocket price endpoint unsupported quote currency should return failure 1`] = ` { "error": { "message": "The EA has not received any values from the Data Provider for the requested data yet. Retry after a short delay, and if the problem persists raise this issue in the relevant channels.", diff --git a/packages/sources/mobula-state/test/integration/adapter.test.ts b/packages/sources/mobula-state/test/integration/adapter.test.ts index fa8863f1b8..f144fcf772 100644 --- a/packages/sources/mobula-state/test/integration/adapter.test.ts +++ b/packages/sources/mobula-state/test/integration/adapter.test.ts @@ -1,9 +1,9 @@ import { WebSocketClassProvider } from '@chainlink/external-adapter-framework/transports' import { - TestAdapter, - setEnvVariables, mockWebSocketProvider, MockWebsocketServer, + setEnvVariables, + TestAdapter, } from '@chainlink/external-adapter-framework/util/testing-utils' import FakeTimers from '@sinonjs/fake-timers' import { mockWebsocketServer } from './fixtures' @@ -14,13 +14,20 @@ describe('websocket', () => { const wsEndpoint = 'ws://localhost:9090' let oldEnv: NodeJS.ProcessEnv - const dataPrice = { - base: 'ETH', + const dataPriceIncludesMapping = { + base: 'EZETH', quote: 'USD', endpoint: 'price', transport: 'ws', } + const dataPriceHardcodedQuote = { + base: 'EZETH', + quote: 'ETH', + endpoint: 'price', + transport: 'ws', + } + const dataFundingRate = { base: 'BTC', quote: '', @@ -50,8 +57,44 @@ describe('websocket', () => { }) // Send initial request to start background execute and wait for cache to be filled with results - await testAdapter.request(dataPrice) + await testAdapter.request(dataPriceIncludesMapping) await testAdapter.waitForCache(1) + + // Prime cache with all test pairs to avoid timing issues in CI + await testAdapter.request({ + base: 'TESTCOIN', + quote: 'USD', + endpoint: 'price', + transport: 'ws', + overrides: { MOBULA_STATE: { TESTCOIN: '999888777' } }, + }) + await testAdapter.request({ + base: 'ANOTHERCOIN', + quote: 'BTC', + endpoint: 'price', + transport: 'ws', + overrides: { MOBULA_STATE: { ANOTHERCOIN: '111222333' } }, + }) + await testAdapter.request({ + base: 'CUSTOMTOKEN', + quote: 'ETH', + endpoint: 'price', + transport: 'ws', + overrides: { MOBULA_STATE: { CUSTOMTOKEN: '444555666' } }, + }) + await testAdapter.request({ + base: '2921', // GHO asset ID directly + quote: '100001656', // BTC asset ID directly + endpoint: 'price', + transport: 'ws', + }) + await testAdapter.request({ + base: '102484658', // LBTC asset ID directly + quote: '100010811', // SOL asset ID directly + endpoint: 'price', + transport: 'ws', + }) + await testAdapter.waitForCache(6) // Wait for all primed pairs to be cached }) afterAll(async () => { @@ -62,20 +105,165 @@ describe('websocket', () => { }) describe('price endpoint', () => { - it('have data should return success', async () => { - const response = await testAdapter.request(dataPrice) + it('EZETH/USD should return success - tests includes.json mapping', async () => { + const response = await testAdapter.request(dataPriceIncludesMapping) expect(response.json()).toMatchSnapshot() }) - it('no data should return failure', async () => { + it('EZETH/ETH should return success - tests hardcoded ETH quote', async () => { + const response = await testAdapter.request(dataPriceHardcodedQuote) + expect(response.json()).toMatchSnapshot() + }) + + it('CBETH/ETH should return success - tests includes.json + hardcoded ETH quote', async () => { const response = await testAdapter.request({ - base: 'ETH', + base: 'CBETH', + quote: 'ETH', + endpoint: 'price', + transport: 'ws', + }) + expect(response.json()).toMatchSnapshot() + }) + + it('LBTC/BTC should return success - tests includes.json + hardcoded BTC quote', async () => { + const response = await testAdapter.request({ + base: 'LBTC', + quote: 'BTC', + endpoint: 'price', + transport: 'ws', + }) + expect(response.json()).toMatchSnapshot() + }) + + it('GHO/USD should return success - tests includes.json mapping', async () => { + const response = await testAdapter.request({ + base: 'GHO', + quote: 'USD', + endpoint: 'price', + transport: 'ws', + }) + expect(response.json()).toMatchSnapshot() + }) + + it('unmapped symbol should return failure', async () => { + const response = await testAdapter.request({ + base: 'UNMAPPED', + quote: 'USD', + endpoint: 'price', + transport: 'ws', + }) + expect(response.json()).toMatchSnapshot() + }) + + it('unsupported quote currency should return failure', async () => { + const response = await testAdapter.request({ + base: 'EZETH', quote: 'EUR', endpoint: 'price', transport: 'ws', }) expect(response.json()).toMatchSnapshot() }) + + it('base override with asset ID should return success', async () => { + const response = await testAdapter.request({ + base: 'TESTCOIN', + quote: 'USD', + endpoint: 'price', + transport: 'ws', + overrides: { + MOBULA_STATE: { + TESTCOIN: '999888777', + }, + }, + }) + expect(response.json()).toMatchSnapshot() + }) + + it('base override with BTC quote should return success', async () => { + const response = await testAdapter.request({ + base: 'ANOTHERCOIN', + quote: 'BTC', + endpoint: 'price', + transport: 'ws', + overrides: { + MOBULA_STATE: { + ANOTHERCOIN: '111222333', + }, + }, + }) + expect(response.json()).toMatchSnapshot() + }) + + it('base override with ETH quote should return success', async () => { + const response = await testAdapter.request({ + base: 'CUSTOMTOKEN', + quote: 'ETH', + endpoint: 'price', + transport: 'ws', + overrides: { + MOBULA_STATE: { + CUSTOMTOKEN: '444555666', + }, + }, + }) + expect(response.json()).toMatchSnapshot() + }) + + it('quote override with asset ID should return success', async () => { + const response = await testAdapter.request({ + base: 'EZETH', + quote: 'CUSTOMQUOTE', + endpoint: 'price', + transport: 'ws', + overrides: { + MOBULA_STATE: { + CUSTOMQUOTE: '100004304', // ETH asset ID as quote override + }, + }, + }) + expect(response.json()).toMatchSnapshot() + }) + + it('direct base asset ID should return success', async () => { + const response = await testAdapter.request({ + base: '102478632', // EZETH asset ID directly + quote: 'USD', + endpoint: 'price', + transport: 'ws', + }) + expect(response.json()).toMatchSnapshot() + }) + + it('direct base and quote asset IDs should return success', async () => { + const response = await testAdapter.request({ + base: '2921', // GHO asset ID directly + quote: '100001656', // BTC asset ID directly + endpoint: 'price', + transport: 'ws', + }) + expect(response.json()).toMatchSnapshot() + }) + + it('direct asset IDs with crypto quote should return success', async () => { + const response = await testAdapter.request({ + base: '100029813', // CBETH asset ID directly + quote: '100004304', // ETH asset ID directly + endpoint: 'price', + transport: 'ws', + }) + expect(response.json()).toMatchSnapshot() + }) + + it('direct asset IDs with hardcoded SOL quote should return success', async () => { + const response = await testAdapter.request({ + base: '102484658', // LBTC asset ID directly + quote: '100010811', // SOL asset ID directly (tests hardcoded SOL quote) + endpoint: 'price', + transport: 'ws', + }) + expect(response.json()).toMatchSnapshot() + }) }) describe('funding rate endpoint', () => { diff --git a/packages/sources/mobula-state/test/integration/fixtures.ts b/packages/sources/mobula-state/test/integration/fixtures.ts index 36ad3e42d5..b0fd2963fc 100644 --- a/packages/sources/mobula-state/test/integration/fixtures.ts +++ b/packages/sources/mobula-state/test/integration/fixtures.ts @@ -2,55 +2,210 @@ import { MockWebsocketServer } from '@chainlink/external-adapter-framework/util/ export const mockWebsocketServer = (URL: string): MockWebsocketServer => { const mockWsServer = new MockWebsocketServer(URL, { mock: false }) + + // Map of asset_id to mock responses - simulates Mobula v2 API targeted subscriptions + const priceResponses: Record = { + // EZETH responses + '102478632': [ + { + timestamp: 1514764861000 + 500, + price: 4233.15, + marketDepthUSDUp: 1097741407.1171298, + marketDepthUSDDown: 1032495335.1741029, + volume24h: 230120379.9751866, + baseSymbol: 'EZETH', + quoteSymbol: 'USD', + baseID: '102478632', + quoteID: 'USD', + }, + { + timestamp: 1514764861000 + 500, + price: 1.0612, + marketDepthUSDUp: 500000, + marketDepthUSDDown: 450000, + volume24h: 15000, + baseSymbol: 'EZETH', + quoteSymbol: 'ETH', + baseID: '102478632', + quoteID: '100004304', + }, + { + timestamp: 1514764861000 + 500, + price: 1.0612, + marketDepthUSDUp: 500000, + marketDepthUSDDown: 450000, + volume24h: 15000, + baseSymbol: 'EZETH', + quoteSymbol: 'CUSTOMQUOTE', + baseID: '102478632', + quoteID: '100004304', + }, + ], + // CBETH responses + '100029813': [ + { + timestamp: 1514764861000 + 500, + price: 1.0456, + marketDepthUSDUp: 800000, + marketDepthUSDDown: 750000, + volume24h: 25000, + baseSymbol: 'CBETH', + quoteSymbol: 'ETH', + baseID: '100029813', + quoteID: '100004304', + }, + ], + // LBTC responses + '102484658': [ + { + timestamp: 1514764861000 + 500, + price: 0.9985, + marketDepthUSDUp: 1200000, + marketDepthUSDDown: 1100000, + volume24h: 35000, + baseSymbol: 'LBTC', + quoteSymbol: 'BTC', + baseID: '102484658', + quoteID: '100001656', + }, + { + timestamp: 1514764861000 + 500, + price: 3.456, + marketDepthUSDUp: 1200000, + marketDepthUSDDown: 1100000, + volume24h: 35000, + baseSymbol: 'LBTC', + quoteSymbol: 'SOL', + baseID: '102484658', + quoteID: '100010811', + }, + ], + // GHO responses + '2921': [ + { + timestamp: 1514764861000 + 500, + price: 1.0012, + marketDepthUSDUp: 300000, + marketDepthUSDDown: 290000, + volume24h: 50000, + baseSymbol: 'GHO', + quoteSymbol: 'USD', + baseID: '2921', + quoteID: 'USD', + }, + { + timestamp: 1514764861000 + 500, + price: 0.0000102, + marketDepthUSDUp: 300000, + marketDepthUSDDown: 290000, + volume24h: 50000, + baseSymbol: 'GHO', + quoteSymbol: 'BTC', + baseID: '2921', + quoteID: '100001656', + }, + ], + // Override test responses + '999888777': [ + { + timestamp: 1514764861000 + 500, + price: 125.67, + marketDepthUSDUp: 150000, + marketDepthUSDDown: 140000, + volume24h: 75000, + baseSymbol: 'TESTCOIN', + quoteSymbol: 'USD', + baseID: '999888777', + quoteID: 'USD', + }, + ], + '111222333': [ + { + timestamp: 1514764861000 + 500, + price: 0.00123456, + marketDepthUSDUp: 90000, + marketDepthUSDDown: 85000, + volume24h: 12000, + baseSymbol: 'ANOTHERCOIN', + quoteSymbol: 'BTC', + baseID: '111222333', + quoteID: '100001656', + }, + ], + '444555666': [ + { + timestamp: 1514764861000 + 500, + price: 0.0456789, + marketDepthUSDUp: 60000, + marketDepthUSDDown: 55000, + volume24h: 8500, + baseSymbol: 'CUSTOMTOKEN', + quoteSymbol: 'ETH', + baseID: '444555666', + quoteID: '100004304', + }, + ], + } + mockWsServer.on('connection', (socket) => { - socket.on('message', () => { - socket.send( - JSON.stringify({ - timestamp: 1726648165000, - price: 2325.847186068699, - marketDepthUSDUp: 1097741407.1171298, - marketDepthUSDDown: 1032495335.1741029, - volume24h: 230120379.9751866, - baseSymbol: 'ETH', - quoteSymbol: 'USD', - }), - ) - - socket.send( - JSON.stringify({ - binanceFundingRate: { - symbol: 'BTCUSDC', - fundingTime: 1740441600000, - fundingRate: 0.009854, - marketPrice: '91505.25655876', - epochDurationMs: 28800000, - }, - deribitFundingRate: { - symbol: 'BTC', - fundingTime: 1740466800000, - fundingRate: 0.00573193029855309, - marketPrice: 91250.36, - epochDurationMs: 28800000, - }, - queryDetails: { base: 'BTC', quote: null }, - }), - ) - - socket.send( - JSON.stringify({ - binanceFundingRate: { - symbol: 'AERGOUSDT', - fundingTime: 1747368000001, - fundingRate: -0.00059603, - marketPrice: '0.15456000', - epochDurationMs: 14400000, - fundingRateCap: 2, - fundingRateFloor: -2, - }, - deribitFundingRate: null, - queryDetails: { base: 'AERGO', quote: null }, - }), - ) + socket.on('message', (message) => { + const parsed = JSON.parse(message as string) + + // Handle Mobula v2 price subscriptions (asset_ids based) + if (parsed.kind === 'asset_ids' && parsed.asset_ids && parsed.asset_ids.length > 0) { + const assetId = parsed.asset_ids[0].toString() + const responses = priceResponses[assetId] + + if (responses) { + // Send all price responses for this asset (different quote currencies) + responses.forEach((response: any) => { + socket.send(JSON.stringify(response)) + }) + } + } + + // Handle funding rate subscriptions + if (parsed.type === 'funding' && parsed.payload) { + const symbol = parsed.payload.symbol + + if (symbol === 'BTC') { + socket.send( + JSON.stringify({ + binanceFundingRate: { + symbol: 'BTCUSDC', + fundingTime: 1740441600000, + fundingRate: 0.009854, + marketPrice: '91505.25655876', + epochDurationMs: 28800000, + }, + deribitFundingRate: { + symbol: 'BTC', + fundingTime: 1740466800000, + fundingRate: 0.00573193029855309, + marketPrice: 91250.36, + epochDurationMs: 28800000, + }, + queryDetails: { base: 'BTC', quote: null }, + }), + ) + } else if (symbol === 'AERGO') { + socket.send( + JSON.stringify({ + binanceFundingRate: { + symbol: 'AERGOUSDT', + fundingTime: 1747368000001, + fundingRate: -0.00059603, + marketPrice: '0.15456000', + epochDurationMs: 14400000, + fundingRateCap: 2, + fundingRateFloor: -2, + }, + deribitFundingRate: null, + queryDetails: { base: 'AERGO', quote: null }, + }), + ) + } + } }) }) diff --git a/yarn.lock b/yarn.lock index 91cdba20d3..57b2963a55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3975,6 +3975,28 @@ __metadata: languageName: node linkType: hard +"@chainlink/external-adapter-framework@npm:2.11.0": + version: 2.11.0 + resolution: "@chainlink/external-adapter-framework@npm:2.11.0" + dependencies: + "@date-fns/tz": "npm:1.4.1" + ajv: "npm:8.17.1" + axios: "npm:1.13.2" + eventsource: "npm:4.0.0" + fastify: "npm:5.6.2" + ioredis: "npm:5.8.2" + mock-socket: "npm:9.3.1" + pino: "npm:10.1.0" + pino-pretty: "npm:13.1.2" + prom-client: "npm:15.1.3" + redlock: "npm:5.0.0-beta.2" + ws: "npm:8.18.3" + bin: + create-external-adapter: adapter-generator.js + checksum: 10/7a892deca87eacad95fe7032f92bbea70e52ead8f03041d40f5c159095874f9dba501ddd37d7404a9e2a9d091150cdd862f799a821ccd264016b7c18c0daef1d + languageName: node + linkType: hard + "@chainlink/external-adapter-framework@npm:2.11.1": version: 2.11.1 resolution: "@chainlink/external-adapter-framework@npm:2.11.1" @@ -4877,7 +4899,7 @@ __metadata: version: 0.0.0-use.local resolution: "@chainlink/mobula-state-adapter@workspace:packages/sources/mobula-state" dependencies: - "@chainlink/external-adapter-framework": "npm:2.8.0" + "@chainlink/external-adapter-framework": "npm:2.11.0" "@sinonjs/fake-timers": "npm:9.1.2" "@types/jest": "npm:^29.5.14" "@types/node": "npm:22.14.1"