From a814b332341a7963c935592ad0b58f4894f47c90 Mon Sep 17 00:00:00 2001 From: jerryfan01234 <44346807+jerryfan01234@users.noreply.github.com> Date: Wed, 28 Aug 2024 17:45:00 -0400 Subject: [PATCH] Keplr geoblock new endpoint (#2117) --- indexer/pnpm-lock.yaml | 271 ++++++++++++++- .../api/v4/compliance-v2-controller.test.ts | 194 ++++++----- indexer/services/comlink/package.json | 1 + .../api/v4/compliance-v2-controller.ts | 321 ++++++++++++------ indexer/services/comlink/tsconfig.json | 1 + 5 files changed, 600 insertions(+), 188 deletions(-) diff --git a/indexer/pnpm-lock.yaml b/indexer/pnpm-lock.yaml index d4bf518059..8f0733538a 100644 --- a/indexer/pnpm-lock.yaml +++ b/indexer/pnpm-lock.yaml @@ -429,6 +429,7 @@ importers: '@dydxprotocol-indexer/redis': workspace:^0.0.1 '@dydxprotocol-indexer/v4-proto-parser': workspace:^0.0.1 '@dydxprotocol-indexer/v4-protos': workspace:^0.0.1 + '@keplr-wallet/cosmos': ^0.12.122 '@tsoa/runtime': ^5.0.0 '@types/big.js': ^6.1.5 '@types/body-parser': ^1.19.2 @@ -479,6 +480,7 @@ importers: '@dydxprotocol-indexer/redis': link:../../packages/redis '@dydxprotocol-indexer/v4-proto-parser': link:../../packages/v4-proto-parser '@dydxprotocol-indexer/v4-protos': link:../../packages/v4-protos + '@keplr-wallet/cosmos': 0.12.122 '@tsoa/runtime': 5.0.0 big.js: 6.2.1 body-parser: 1.20.0 @@ -4945,6 +4947,48 @@ packages: - supports-color dev: true + /@ethersproject/address/5.7.0: + resolution: {integrity: sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==} + dependencies: + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/rlp': 5.7.0 + dev: false + + /@ethersproject/bignumber/5.7.0: + resolution: {integrity: sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + bn.js: 5.2.1 + dev: false + + /@ethersproject/bytes/5.7.0: + resolution: {integrity: sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==} + dependencies: + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/keccak256/5.7.0: + resolution: {integrity: sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==} + dependencies: + '@ethersproject/bytes': 5.7.0 + js-sha3: 0.8.0 + dev: false + + /@ethersproject/logger/5.7.0: + resolution: {integrity: sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==} + dev: false + + /@ethersproject/rlp/5.7.0: + resolution: {integrity: sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + dev: false + /@exodus/schemasafe/1.3.0: resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} dev: false @@ -5312,6 +5356,69 @@ packages: resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} dev: true + /@keplr-wallet/common/0.12.122: + resolution: {integrity: sha512-Q+8+wmGYDarEcyXQQSD//ugKmBTl0Gxam0MGydERHSnm5BEl/1bQNAT4ZSo+1zeuncKdwHQ6MSRegX46/XjMTQ==} + dependencies: + '@keplr-wallet/crypto': 0.12.122 + '@keplr-wallet/types': 0.12.122 + buffer: 6.0.3 + delay: 4.4.1 + dev: false + + /@keplr-wallet/cosmos/0.12.122: + resolution: {integrity: sha512-t0p40l7UQ4hK0Sw2fw54qnuNPE+riBlP1ITy/LnU0UzLxZkR7bGnAQF5V32OLnT75QVChwsjDxNA4xSNCsgImQ==} + dependencies: + '@ethersproject/address': 5.7.0 + '@keplr-wallet/common': 0.12.122 + '@keplr-wallet/crypto': 0.12.122 + '@keplr-wallet/proto-types': 0.12.122 + '@keplr-wallet/simple-fetch': 0.12.122 + '@keplr-wallet/types': 0.12.122 + '@keplr-wallet/unit': 0.12.122 + bech32: 1.1.4 + buffer: 6.0.3 + long: 4.0.0 + protobufjs: 6.11.3 + dev: false + + /@keplr-wallet/crypto/0.12.122: + resolution: {integrity: sha512-prD0XdmlbTldysisnMTH3oDXmENtZnhur6YvL/7Q+uorr0HutfwfZYTsMy2RI1DUyzudyQ7kDsyNdCSLWI8nqw==} + dependencies: + '@ethersproject/keccak256': 5.7.0 + bip32: 2.0.6 + bip39: 3.1.0 + bs58check: 2.1.2 + buffer: 6.0.3 + crypto-js: 4.2.0 + elliptic: 6.5.4 + sha.js: 2.4.11 + dev: false + + /@keplr-wallet/proto-types/0.12.122: + resolution: {integrity: sha512-JM1Mx1cmikmscUdJ8qVyJ+DEdzOHnG5xT8QyWEf/xWq6kwy4+VWXghdbLk70mWtbMQAc00RpTy5IM4ALsHwAoQ==} + dependencies: + long: 4.0.0 + protobufjs: 6.11.3 + dev: false + + /@keplr-wallet/simple-fetch/0.12.122: + resolution: {integrity: sha512-yxBQDKQwGfD7XH8dui/BL8SdfTyLuDdqCfKianb66K0JxzJoc7qXJm6b71erOHh3ncJXPXHHtsTHDO5u1MO7/w==} + dev: false + + /@keplr-wallet/types/0.12.122: + resolution: {integrity: sha512-VdFecbB3pQyHIiLw0QOSypv69iWRQwRDKscNKQfmdLhuNX12XdZpBYxTi0IBUUSltGvn81NYPZjkw1b/OnSkNw==} + dependencies: + long: 4.0.0 + dev: false + + /@keplr-wallet/unit/0.12.122: + resolution: {integrity: sha512-RWEmYf9TBuArfpCvfhfd3P7w4UqDISTcApPq8QE/r+eyhFx9nSdE6s/4NqAxGBXn3SRbPgylSu0DpP3pmMQH1g==} + dependencies: + '@keplr-wallet/types': 0.12.122 + big-integer: 1.6.52 + utility-types: 3.11.0 + dev: false + /@mapbox/node-pre-gyp/1.0.11: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true @@ -5819,6 +5926,10 @@ packages: '@types/express': 4.17.13 dev: false + /@types/node/10.12.18: + resolution: {integrity: sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==} + dev: false + /@types/node/18.0.3: resolution: {integrity: sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==} @@ -6615,6 +6726,12 @@ packages: /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + /base-x/3.0.10: + resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /base/0.11.2: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} @@ -6646,6 +6763,11 @@ packages: resolution: {integrity: sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==} dev: false + /big-integer/1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + dev: false + /big.js/6.2.1: resolution: {integrity: sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==} dev: false @@ -6659,7 +6781,25 @@ packages: dependencies: file-uri-to-path: 1.0.0 dev: false - optional: true + + /bip32/2.0.6: + resolution: {integrity: sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==} + engines: {node: '>=6.0.0'} + dependencies: + '@types/node': 10.12.18 + bs58check: 2.1.2 + create-hash: 1.2.0 + create-hmac: 1.1.7 + tiny-secp256k1: 1.1.6 + typeforce: 1.18.0 + wif: 2.0.6 + dev: false + + /bip39/3.1.0: + resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} + dependencies: + '@noble/hashes': 1.3.0 + dev: false /bluebird/3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} @@ -6756,6 +6896,20 @@ packages: update-browserslist-db: 1.0.10_browserslist@4.21.5 dev: true + /bs58/4.0.1: + resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} + dependencies: + base-x: 3.0.10 + dev: false + + /bs58check/2.1.2: + resolution: {integrity: sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==} + dependencies: + bs58: 4.0.1 + create-hash: 1.2.0 + safe-buffer: 5.2.1 + dev: false + /bser/2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: @@ -6777,6 +6931,13 @@ packages: isarray: 1.0.0 dev: false + /buffer/6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + /byline/5.0.0: resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} engines: {node: '>=0.10.0'} @@ -6889,6 +7050,13 @@ packages: engines: {node: '>=8'} dev: false + /cipher-base/1.0.4: + resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==} + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: false + /cjs-module-lexer/1.2.2: resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} @@ -7144,6 +7312,27 @@ packages: request: 2.88.2 dev: true + /create-hash/1.2.0: + resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} + dependencies: + cipher-base: 1.0.4 + inherits: 2.0.4 + md5.js: 1.3.5 + ripemd160: 2.0.2 + sha.js: 2.4.11 + dev: false + + /create-hmac/1.1.7: + resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} + dependencies: + cipher-base: 1.0.4 + create-hash: 1.2.0 + inherits: 2.0.4 + ripemd160: 2.0.2 + safe-buffer: 5.2.1 + sha.js: 2.4.11 + dev: false + /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -7166,6 +7355,10 @@ packages: shebang-command: 2.0.0 which: 2.0.2 + /crypto-js/4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + dev: false + /crypto-randomuuid/1.0.0: resolution: {integrity: sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA==} dev: false @@ -7346,6 +7539,11 @@ packages: isobject: 3.0.1 dev: false + /delay/4.4.1: + resolution: {integrity: sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ==} + engines: {node: '>=6'} + dev: false + /delay/5.0.0: resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} engines: {node: '>=10'} @@ -8543,7 +8741,6 @@ packages: /file-uri-to-path/1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} dev: false - optional: true /fill-range/4.0.0: resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} @@ -9121,6 +9318,15 @@ packages: dependencies: function-bind: 1.1.1 + /hash-base/3.1.0: + resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} + engines: {node: '>=4'} + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.0 + safe-buffer: 5.2.1 + dev: false + /hash.js/1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} dependencies: @@ -9237,6 +9443,10 @@ packages: resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==} dev: false + /ieee754/1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + /ignore/5.2.0: resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} engines: {node: '>= 4'} @@ -10491,6 +10701,10 @@ packages: engines: {node: '>= 0.6.0'} dev: false + /js-sha3/0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + dev: false + /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -10924,6 +11138,14 @@ packages: uc.micro: 1.0.6 dev: false + /md5.js/1.3.5: + resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} + dependencies: + hash-base: 3.1.0 + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: false + /mdurl/1.0.1: resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} dev: false @@ -11126,7 +11348,7 @@ packages: hasBin: true dependencies: event-lite: 0.1.3 - ieee754: 1.1.13 + ieee754: 1.2.1 int64-buffer: 0.1.10 isarray: 1.0.0 dev: false @@ -11146,7 +11368,6 @@ packages: /nan/2.16.0: resolution: {integrity: sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==} dev: false - optional: true /nanomatch/1.2.13: resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} @@ -12277,6 +12498,13 @@ packages: dependencies: glob: 7.2.3 + /ripemd160/2.0.2: + resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} + dependencies: + hash-base: 3.1.0 + inherits: 2.0.4 + dev: false + /run-async/2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -12414,6 +12642,14 @@ packages: /setprototypeof/1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + /sha.js/2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: false + /shebang-command/1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -13002,6 +13238,18 @@ packages: next-tick: 1.1.0 dev: true + /tiny-secp256k1/1.1.6: + resolution: {integrity: sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==} + engines: {node: '>=6.0.0'} + requiresBuild: true + dependencies: + bindings: 1.5.0 + bn.js: 4.12.0 + create-hmac: 1.1.7 + elliptic: 6.5.4 + nan: 2.16.0 + dev: false + /tmp/0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -13275,6 +13523,10 @@ packages: for-each: 0.3.3 is-typed-array: 1.1.10 + /typeforce/1.18.0: + resolution: {integrity: sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==} + dev: false + /typescript/4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} @@ -13445,6 +13697,11 @@ packages: which-typed-array: 1.1.9 dev: false + /utility-types/3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + dev: false + /utils-merge/1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -13578,6 +13835,12 @@ packages: dev: false optional: true + /wif/2.0.6: + resolution: {integrity: sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==} + dependencies: + bs58check: 2.1.2 + dev: false + /winston-transport/4.5.0: resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==} engines: {node: '>= 6.4.0'} diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts index 608041089c..3a907c6e1e 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts @@ -18,6 +18,7 @@ import config from '../../../../src/config'; import { DateTime } from 'luxon'; import { ComplianceAction } from '../../../../src/controllers/api/v4/compliance-v2-controller'; import { ExtendedSecp256k1Signature, Secp256k1 } from '@cosmjs/crypto'; +import { verifyADR36Amino } from '@keplr-wallet/cosmos'; import { getGeoComplianceReason } from '../../../../src/helpers/compliance/compliance-utils'; import { isRestrictedCountryHeaders, isWhitelistedAddress } from '@dydxprotocol-indexer/compliance'; import { toBech32 } from '@cosmjs/encoding'; @@ -39,6 +40,11 @@ jest.mock('@cosmjs/crypto', () => ({ }, })); +jest.mock('@keplr-wallet/cosmos', () => ({ + ...jest.requireActual('@keplr-wallet/cosmos'), + verifyADR36Amino: jest.fn(), +})); + jest.mock('@cosmjs/encoding', () => ({ toBech32: jest.fn(), })); @@ -46,8 +52,11 @@ jest.mock('@cosmjs/encoding', () => ({ describe('ComplianceV2Controller', () => { const ipAddr: string = '192.168.1.1'; - const ipAddrMock: jest.Mock = (getIpAddr as unknown as jest.Mock); - const toBech32Mock: jest.Mock = (toBech32 as unknown as jest.Mock); + const verifySignatureMock = Secp256k1.verifySignature as jest.Mock; + const fromFixedLengthMock = ExtendedSecp256k1Signature.fromFixedLength as jest.Mock; + const verifyADR36AminoMock = verifyADR36Amino as jest.Mock; + const ipAddrMock = getIpAddr as jest.Mock; + const toBech32Mock = toBech32 as jest.Mock; beforeAll(async () => { await dbHelpers.migrate(); @@ -324,34 +333,44 @@ describe('ComplianceV2Controller', () => { }); }); - describe('POST /geoblock', () => { + const geoblockEndpoint = '/v4/compliance/geoblock'; + const geoblockKeplerEndpoint = '/v4/compliance/geoblock-keplr'; + const geoblockBody = { + address: testConstants.defaultAddress, + message: 'Test message', + action: ComplianceAction.CONNECT, + signedMessage: 'signedmessage123', + pubkey: 'asdfasdf', + timestamp: 1620000000, + }; + const geoblockKeplrBody = { + address: testConstants.defaultAddress, + message: 'Test message', + action: ComplianceAction.CONNECT, + signedMessage: 'signedmessage123', + pubkey: 'asdfasdf', + }; + const endpoints = [ + { endpoint: geoblockEndpoint, description: 'POST /geoblock', body: geoblockBody }, + { endpoint: geoblockKeplerEndpoint, description: 'POST /geoblock-keplr', body: geoblockKeplrBody }, + ]; + + describe.each(endpoints)('$description endpoint', ({ endpoint, body }) => { let getGeoComplianceReasonSpy: jest.SpyInstance; let isRestrictedCountryHeadersSpy: jest.SpyInstance; let isWhitelistedAddressSpy: jest.SpyInstance; - const body: any = { - address: testConstants.defaultAddress, - message: 'Test message', - action: ComplianceAction.CONNECT, - signedMessage: 'signedmessage123', - pubkey: 'asdfasdf', - timestamp: 1620000000, - }; - beforeEach(async () => { getGeoComplianceReasonSpy = getGeoComplianceReason as unknown as jest.Mock; isRestrictedCountryHeadersSpy = isRestrictedCountryHeaders as unknown as jest.Mock; isWhitelistedAddressSpy = isWhitelistedAddress as unknown as jest.Mock; ipAddrMock.mockReturnValue(ipAddr); await testMocks.seedData(); - jest.mock('@cosmjs/crypto', () => ({ - Secp256k1: { - verifySignature: jest.fn().mockResolvedValue(true), - }, - ExtendedSecp256k1Signature: { - fromFixedLength: jest.fn().mockResolvedValue({} as ExtendedSecp256k1Signature), - }, - })); + // Mock verification to true to reduce mocking within individual tests + verifySignatureMock.mockResolvedValue(true); + fromFixedLengthMock.mockResolvedValue({} as ExtendedSecp256k1Signature); + verifyADR36AminoMock.mockReturnValue(true); + toBech32Mock.mockReturnValue(testConstants.defaultAddress); jest.spyOn(DateTime, 'now').mockReturnValue(DateTime.fromSeconds(1620000000)); // Mock current time jest.spyOn(stats, 'increment'); @@ -365,58 +384,74 @@ describe('ComplianceV2Controller', () => { }); it('should return 400 for non-dYdX address', async () => { - await sendRequest({ - type: RequestMethod.POST, - path: '/v4/compliance/geoblock', - body: { - ...body, - address: '0x123', // Non-dYdX address - }, - expectedStatus: 400, - }); + if (endpoint === geoblockEndpoint) { + await sendRequest({ + type: RequestMethod.POST, + path: endpoint, + body: { + ...body, + address: '0x123', // Non-dYdX address + }, + expectedStatus: 400, + }); + } }); it('should return 400 for invalid timestamp', async () => { - await sendRequest({ - type: RequestMethod.POST, - path: '/v4/compliance/geoblock', - body: { - ...body, - timestamp: 1619996600, // More than 30 seconds difference - }, - expectedStatus: 400, - }); + if (endpoint === geoblockEndpoint) { + await sendRequest({ + type: RequestMethod.POST, + path: endpoint, + body: { + ...body, + timestamp: 1619996600, // More than 30 seconds difference + }, + expectedStatus: 400, + }); + } }); it('should return 400 for invalid signature', async () => { - // Mock verifySignature to return false for this test - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(false); - - await sendRequest({ - type: RequestMethod.POST, - path: '/v4/compliance/geoblock', - body, - expectedStatus: 400, - }); + if (endpoint === geoblockEndpoint) { + // Mock verifySignature to return false for this test + (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(false); + await sendRequest({ + type: RequestMethod.POST, + path: endpoint, + body, + expectedStatus: 400, + }); + } }); it('should return 400 for incorrect address', async () => { - toBech32Mock.mockResolvedValueOnce('invalid_address'); + if (endpoint === geoblockEndpoint) { + toBech32Mock.mockResolvedValueOnce('invalid_address'); + await sendRequest({ + type: RequestMethod.POST, + path: endpoint, + body, + expectedStatus: 400, + }); + } + }); - await sendRequest({ - type: RequestMethod.POST, - path: '/v4/compliance/geoblock', - body, - expectedStatus: 400, - }); + it('should return 400 for failed keplr validation', async () => { + if (endpoint === geoblockKeplerEndpoint) { + (verifyADR36Amino as jest.Mock).mockReturnValueOnce(false); + await sendRequest({ + type: RequestMethod.POST, + path: endpoint, + body, + expectedStatus: 400, + }); + } }); it('should process valid request', async () => { - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); - const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body, }); @@ -425,7 +460,6 @@ describe('ComplianceV2Controller', () => { }); it('should return COMPLIANT from a restricted country when whitelisted', async () => { - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); await dbHelpers.clearData(); @@ -436,7 +470,7 @@ describe('ComplianceV2Controller', () => { isWhitelistedAddressSpy.mockReturnValue(true); const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body, expectedStatus: 200, }); @@ -449,14 +483,13 @@ describe('ComplianceV2Controller', () => { }); it('should set status to BLOCKED for CONNECT action from a restricted country with no existing compliance status and no wallet', async () => { - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); await dbHelpers.clearData(); const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body, expectedStatus: 200, }); @@ -469,10 +502,12 @@ describe('ComplianceV2Controller', () => { reason: ComplianceReason.US_GEO, })); - expect(stats.increment).toHaveBeenCalledWith(`${config.SERVICE_NAME}.compliance-v2-controller.geo_block.compliance_status_changed.count`, + expect(stats.increment).toHaveBeenCalledWith( + `${config.SERVICE_NAME}.compliance-v2-controller.${endpoint === geoblockEndpoint ? 'geo_block' : 'geo_block_keplr'}.compliance_status_changed.count`, { newStatus: ComplianceStatus.BLOCKED, - }); + }, + ); expect(response.body.status).toEqual(ComplianceStatus.BLOCKED); expect(response.body.reason).toEqual(ComplianceReason.US_GEO); @@ -480,13 +515,12 @@ describe('ComplianceV2Controller', () => { }); it('should set status to FIRST_STRIKE_CLOSE_ONLY for CONNECT action from a restricted country with no existing compliance status and a wallet', async () => { - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body: { ...body, action: ComplianceAction.CONNECT, @@ -501,7 +535,8 @@ describe('ComplianceV2Controller', () => { status: ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY, reason: ComplianceReason.US_GEO, })); - expect(stats.increment).toHaveBeenCalledWith(`${config.SERVICE_NAME}.compliance-v2-controller.geo_block.compliance_status_changed.count`, + expect(stats.increment).toHaveBeenCalledWith( + `${config.SERVICE_NAME}.compliance-v2-controller.${endpoint === geoblockEndpoint ? 'geo_block' : 'geo_block_keplr'}.compliance_status_changed.count`, { newStatus: ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY, }); @@ -512,12 +547,11 @@ describe('ComplianceV2Controller', () => { }); it('should set status to COMPLIANT for any action from a non-restricted country with no existing compliance status', async () => { - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); isRestrictedCountryHeadersSpy.mockReturnValue(false); const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body, expectedStatus: 200, }); @@ -537,13 +571,12 @@ describe('ComplianceV2Controller', () => { address: testConstants.defaultAddress, status: ComplianceStatus.COMPLIANT, }); - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body: { ...body, action: ComplianceAction.CONNECT, @@ -570,13 +603,12 @@ describe('ComplianceV2Controller', () => { status: ComplianceStatus.FIRST_STRIKE, reason: ComplianceReason.US_GEO, }); - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body: { ...body, action: ComplianceAction.CONNECT, @@ -584,7 +616,8 @@ describe('ComplianceV2Controller', () => { expectedStatus: 200, }); - expect(stats.increment).toHaveBeenCalledWith(`${config.SERVICE_NAME}.compliance-v2-controller.geo_block.compliance_status_changed.count`, + expect(stats.increment).toHaveBeenCalledWith( + `${config.SERVICE_NAME}.compliance-v2-controller.${endpoint === geoblockEndpoint ? 'geo_block' : 'geo_block_keplr'}.compliance_status_changed.count`, { newStatus: ComplianceStatus.CLOSE_ONLY, }); @@ -610,13 +643,12 @@ describe('ComplianceV2Controller', () => { reason: ComplianceReason.US_GEO, updatedAt: createdAt, }); - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body: { ...body, action: ComplianceAction.CONNECT, @@ -643,20 +675,20 @@ describe('ComplianceV2Controller', () => { status: ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY, reason: ComplianceReason.US_GEO, }); - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body: { ...body, action: ComplianceAction.INVALID_SURVEY, }, expectedStatus: 200, }); - expect(stats.increment).toHaveBeenCalledWith(`${config.SERVICE_NAME}.compliance-v2-controller.geo_block.compliance_status_changed.count`, + expect(stats.increment).toHaveBeenCalledWith( + `${config.SERVICE_NAME}.compliance-v2-controller.${endpoint === geoblockEndpoint ? 'geo_block' : 'geo_block_keplr'}.compliance_status_changed.count`, { newStatus: ComplianceStatus.CLOSE_ONLY, }); @@ -680,20 +712,20 @@ describe('ComplianceV2Controller', () => { status: ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY, reason: ComplianceReason.US_GEO, }); - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); const response: any = await sendRequest({ type: RequestMethod.POST, - path: '/v4/compliance/geoblock', + path: endpoint, body: { ...body, action: ComplianceAction.VALID_SURVEY, }, expectedStatus: 200, }); - expect(stats.increment).toHaveBeenCalledWith(`${config.SERVICE_NAME}.compliance-v2-controller.geo_block.compliance_status_changed.count`, + expect(stats.increment).toHaveBeenCalledWith( + `${config.SERVICE_NAME}.compliance-v2-controller.${endpoint === geoblockEndpoint ? 'geo_block' : 'geo_block_keplr'}.compliance_status_changed.count`, { newStatus: ComplianceStatus.FIRST_STRIKE, }); diff --git a/indexer/services/comlink/package.json b/indexer/services/comlink/package.json index 6430a22b1f..ba12e7064b 100644 --- a/indexer/services/comlink/package.json +++ b/indexer/services/comlink/package.json @@ -32,6 +32,7 @@ "@dydxprotocol-indexer/redis": "workspace:^0.0.1", "@dydxprotocol-indexer/v4-proto-parser": "workspace:^0.0.1", "@dydxprotocol-indexer/v4-protos": "workspace:^0.0.1", + "@keplr-wallet/cosmos": "^0.12.122", "@tsoa/runtime": "^5.0.0", "big.js": "^6.2.1", "body-parser": "^1.20.0", diff --git a/indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts b/indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts index fc1c634d5a..8dbddcda8b 100644 --- a/indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts @@ -12,6 +12,7 @@ import { WalletFromDatabase, WalletTable, } from '@dydxprotocol-indexer/postgres'; +import { verifyADR36Amino } from '@keplr-wallet/cosmos'; import express from 'express'; import { matchedData } from 'express-validator'; import _ from 'lodash'; @@ -213,118 +214,58 @@ router.post( } = req.body; try { - if (!address.startsWith(DYDX_ADDRESS_PREFIX)) { - return create4xxResponse( - res, - `Address ${address} is not a valid dYdX V4 address`, - ); + const failedValidationResponse = await validateSignature( + res, action, address, timestamp, message, signedMessage, pubkey, currentStatus, + ); + if (failedValidationResponse) { + return failedValidationResponse; } + return await checkCompliance(req, res, address, action, false); + } catch (error) { + return handleError(error, 'geoblock', message, req, res); + } finally { + stats.timing( + `${config.SERVICE_NAME}.${controllerName}.geo_block.timing`, + Date.now() - start, + ); + } + }, +); - const pubkeyArray: Uint8Array = new Uint8Array(Buffer.from(pubkey, 'base64')); - if (address !== generateAddress(pubkeyArray)) { - return create4xxResponse( - res, - `Address ${address} does not correspond to the pubkey provided ${pubkey}`, - ); - } +router.post( + '/geoblock-keplr', + handleValidationErrors, + ExportResponseCodeStats({ controllerName }), + async (req: express.Request, res: express.Response) => { + const start: number = Date.now(); - // Verify the timestamp is within GEOBLOCK_REQUEST_TTL_SECONDS seconds of the current time - const now = DateTime.now().toSeconds(); - if (Math.abs(now - timestamp) > GEOBLOCK_REQUEST_TTL_SECONDS) { - return create4xxResponse( - res, - `Timestamp is not within the valid range of ${GEOBLOCK_REQUEST_TTL_SECONDS} seconds`, - ); - } + const { + address, + message, + action, + signedMessage, + pubkey, + }: { + address: string, + message: string, + action: ComplianceAction, + signedMessage: string, // base64 encoded + pubkey: string, // base64 encoded + } = req.body; - // Prepare the message for verification - const messageToSign: string = `${message}:${action}"${currentStatus || ''}:${timestamp}`; - const messageHash: Uint8Array = sha256(Buffer.from(messageToSign)); - const signedMessageArray: Uint8Array = new Uint8Array(Buffer.from(signedMessage, 'base64')); - const signature: ExtendedSecp256k1Signature = ExtendedSecp256k1Signature - .fromFixedLength(signedMessageArray); - - // Verify the signature - const isValidSignature: boolean = await Secp256k1.verifySignature( - signature, - messageHash, - pubkeyArray, + try { + const failedValidationResponse = validateSignatureKeplr( + res, address, message, signedMessage, pubkey, ); - if (!isValidSignature) { - return create4xxResponse( - res, - 'Signature verification failed', - ); + if (failedValidationResponse) { + return failedValidationResponse; } - - if (isWhitelistedAddress(address)) { - return res.send({ - status: ComplianceStatus.COMPLIANT, - updatedAt: DateTime.utc().toISO(), - }); - } - - const [ - complianceStatus, - wallet, - ]: [ - ComplianceStatusFromDatabase[], - WalletFromDatabase | undefined, - ] = await Promise.all([ - ComplianceStatusTable.findAll( - { address: [address] }, - [], - ), - WalletTable.findById(address), - ]); - - const updatedAt: string = DateTime.utc().toISO(); - const complianceStatusFromDatabase: - ComplianceStatusFromDatabase | undefined = await upsertComplianceStatus( - req, - action, - address, - wallet, - complianceStatus, - updatedAt, - ); - if (complianceStatus.length === 0 || - complianceStatus[0] !== complianceStatusFromDatabase) { - if (complianceStatusFromDatabase !== undefined && - complianceStatusFromDatabase.status !== ComplianceStatus.COMPLIANT - ) { - stats.increment( - `${config.SERVICE_NAME}.${controllerName}.geo_block.compliance_status_changed.count`, - { - newStatus: complianceStatusFromDatabase!.status, - }, - ); - } - } - - const response = { - status: complianceStatusFromDatabase!.status, - reason: complianceStatusFromDatabase!.reason, - updatedAt: complianceStatusFromDatabase!.updatedAt, - }; - - return res.send(response); + return await checkCompliance(req, res, address, action, true); } catch (error) { - logger.error({ - at: 'ComplianceV2Controller POST /geoblock', - message, - error, - params: JSON.stringify(req.params), - query: JSON.stringify(req.query), - body: JSON.stringify(req.body), - }); - return create4xxResponse( - res, - error.message, - ); + return handleError(error, 'geoblock-keplr', message, req, res); } finally { stats.timing( - `${config.SERVICE_NAME}.${controllerName}.geo_block.timing`, + `${config.SERVICE_NAME}.${controllerName}.geo_block_keplr.timing`, Date.now() - start, ); } @@ -335,6 +276,180 @@ function generateAddress(pubkeyArray: Uint8Array): string { return toBech32('dydx', ripemd160(sha256(pubkeyArray))); } +/** + * Validates a signature by performing various checks including address format, + * public key correspondence, timestamp validity, and signature verification. + * + * @returns {Promise} Returns undefined if validation + * is successful. Returns an HTTP response with an error message if validation fails. + */ +async function validateSignature( + res: express.Response, + action: ComplianceAction, + address: string, + timestamp: number, + message: string, + signedMessage: string, + pubkey: string, + currentStatus?: string, +): Promise { + if (!address.startsWith(DYDX_ADDRESS_PREFIX)) { + return create4xxResponse( + res, + `Address ${address} is not a valid dYdX V4 address`, + ); + } + + const pubkeyArray: Uint8Array = new Uint8Array(Buffer.from(pubkey, 'base64')); + if (address !== generateAddress(pubkeyArray)) { + return create4xxResponse( + res, + `Address ${address} does not correspond to the pubkey provided ${pubkey}`, + ); + } + + // Verify the timestamp is within GEOBLOCK_REQUEST_TTL_SECONDS seconds of the current time + const now = DateTime.now().toSeconds(); + if (Math.abs(now - timestamp) > GEOBLOCK_REQUEST_TTL_SECONDS) { + return create4xxResponse( + res, + `Timestamp is not within the valid range of ${GEOBLOCK_REQUEST_TTL_SECONDS} seconds`, + ); + } + + // Prepare the message for verification + const messageToSign: string = `${message}:${action}"${currentStatus || ''}:${timestamp}`; + const messageHash: Uint8Array = sha256(Buffer.from(messageToSign)); + const signedMessageArray: Uint8Array = new Uint8Array(Buffer.from(signedMessage, 'base64')); + const signature: ExtendedSecp256k1Signature = ExtendedSecp256k1Signature + .fromFixedLength(signedMessageArray); + + // Verify the signature + const isValidSignature: boolean = await Secp256k1.verifySignature( + signature, + messageHash, + pubkeyArray, + ); + if (!isValidSignature) { + return create4xxResponse( + res, + 'Signature verification failed', + ); + } + + return undefined; +} + +/** + * Validates a signature using verifyADR36Amino provided by keplr package. + * + * @returns {Promise} Returns undefined if validation + * is successful. Returns an HTTP response with an error message if validation fails. + */ +function validateSignatureKeplr( + res:express.Response, + address: string, + message: string, + signedMessage: string, + pubkey: string, +): express.Response | undefined { + const messageToSign: string = message; + + const pubKeyUint = new Uint8Array(Buffer.from(pubkey, 'base64')); + const signedMessageUint = new Uint8Array(Buffer.from(signedMessage, 'base64')); + + const isVerified = verifyADR36Amino( + 'dydx', address, messageToSign, pubKeyUint, signedMessageUint, 'secp256k1', + ); + + if (!isVerified) { + return create4xxResponse( + res, + 'Keplr signature verification failed', + ); + } + + return undefined; +} + +async function checkCompliance( + req: express.Request, + res: express.Response, + address: string, + action: ComplianceAction, + forKeplr: boolean, +): Promise { + if (isWhitelistedAddress(address)) { + return res.send({ + status: ComplianceStatus.COMPLIANT, + updatedAt: DateTime.utc().toISO(), + }); + } + + const [ + complianceStatus, + wallet, + ]: [ + ComplianceStatusFromDatabase[], + WalletFromDatabase | undefined, + ] = await Promise.all([ + ComplianceStatusTable.findAll( + { address: [address] }, + [], + ), + WalletTable.findById(address), + ]); + + const updatedAt: string = DateTime.utc().toISO(); + const complianceStatusFromDatabase: + ComplianceStatusFromDatabase | undefined = await upsertComplianceStatus( + req, + action, + address, + wallet, + complianceStatus, + updatedAt, + ); + if (complianceStatus.length === 0 || + complianceStatus[0] !== complianceStatusFromDatabase) { + if (complianceStatusFromDatabase !== undefined && + complianceStatusFromDatabase.status !== ComplianceStatus.COMPLIANT + ) { + stats.increment( + `${config.SERVICE_NAME}.${controllerName}.geo_block${forKeplr ? '_keplr' : ''}.compliance_status_changed.count`, + { + newStatus: complianceStatusFromDatabase!.status, + }, + ); + } + } + + const response = { + status: complianceStatusFromDatabase!.status, + reason: complianceStatusFromDatabase!.reason, + updatedAt: complianceStatusFromDatabase!.updatedAt, + }; + + return res.send(response); +} + +function handleError( + error: Error, endpointName: string, message:string, req: express.Request, res: express.Response, +): express.Response { + logger.error({ + at: `ComplianceV2Controller POST /${endpointName}`, + message, + error, + params: JSON.stringify(req.params), + query: JSON.stringify(req.query), + body: JSON.stringify(req.body), + }); + return create4xxResponse( + res, + error.message, + ); +} + /** * If the address doesn't exist in the compliance table: * - if the request is from a restricted country: diff --git a/indexer/services/comlink/tsconfig.json b/indexer/services/comlink/tsconfig.json index 58ad57d97f..218d789bff 100644 --- a/indexer/services/comlink/tsconfig.json +++ b/indexer/services/comlink/tsconfig.json @@ -5,6 +5,7 @@ "outDir": "build", "experimentalDecorators": true, "emitDecoratorMetadata": true, + "skipLibCheck": true }, "include": [ "src",