diff --git a/.eslintrc b/.eslintrc index f2959157..68efb738 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,6 +8,7 @@ "semi": [2, "never"], "@typescript-eslint/semi": [2, "never"], "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], - "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }] + "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }], + "import/no-cycle": "off" } } diff --git a/README.md b/README.md index e1f5aec0..fa73e26f 100644 --- a/README.md +++ b/README.md @@ -201,3 +201,61 @@ client.transactions.submit(signedPaymentTxn.toString()) > ⚠️ Note that oracle prices change over time. It's possible for a transaction to fail if the oracle price changes in between the time the transaction is constructed and when it is absorbed by the consensus group. The API exposes what the next oracle price will be at `https://api.helium.io /v1/oracle/predictions`. See https://developer.helium.com/blockchain/api/oracle for more details. To avoid failed transactions, it may be worth querying both the oracle predictions, and the current oracle value, and taking the greatest of those values. + +### Sending payment from multi-signature wallet +Sending a payment from a multi-signature wallet requires collecting the minimum number of required signatures from the addresses associated with the multi-signature address in question. Below is an example of creating and signing a 1 of 2 multi-signature address transaction. +```js +import { Keypair, MultisigSignature } from '@helium/crypto' +import Address, { MultisigAddress } from '@helium/address' +import { PaymentV2, Transaction } from '@helium/transactions' +import { Client } from '@helium/http' + +const client = new Client() + +// the transactions library needs to be configured +// with the latest chain vars in order to calcluate fees +const vars = await client.vars.get() +Transaction.config(vars) + +// initialize a keypair available for signing +const bob = await Keypair.fromWords(['one', 'two', ..., 'twelve']) + +// initialize an address from a b58 string +const alice = Address.fromB58('148d8KTRcKA5JKPekBcKFd4KfvprvFRpjGtivhtmRmnZ8MFYnP3') + +// initialize multisig address with the full set of addreses and required number of signatures +const multisigAddress = await MultisigAddress.create([bob.address, alice], 1) + +// get the speculative nonce for the multisig keypair +const account = await client.accounts.get(multisigAddress.b58) + +// create random payee address +const payeeAddress = Address.fromB58('13dSybmfNofup3rBGat2poGfuab4BhYZNKUJFczSi4jcwLmoXvD') + +// construct a PaymentV2 txn to sign +const paymentTxn = new PaymentV2({ + payer: multisigAddress, + payments: [ + { + payee: payeeAddress, + amount: amountToSend, + }, + ], + nonce: account.speculativeNonce + 1, +}) + +// Create signatures payload, a map of address to signature, and finally a KeySignature list +const bobSignature = (await paymentTxn.sign({ payer: bob })).signature || new Uint8Array() +const signatureMap = new Map([[bob.address, await bob.sign(paymentTxn.serialize())]]) +const signatures = KeySignature.fromMap([bob.address, aliceAddress], signatureMap) + +// Construct multisig signature using the address, the full set of all addresses, and the required signatures +const multisigSig = new MultisigSignature([bob.address, aliceAddress], signatures) + +// Update signature on payment trasnaction +await paymentTxn.sign({payer: multisigSig}) + +// submit the serialized txn to the Blockchain HTTP API +client.transactions.submit(paymentTxn.toString()) + +``` diff --git a/integration_tests/fixtures/users.ts b/integration_tests/fixtures/users.ts index 53ed93f8..71db84c7 100644 --- a/integration_tests/fixtures/users.ts +++ b/integration_tests/fixtures/users.ts @@ -38,6 +38,9 @@ export const aliceWords = [ export const bobB58 = '13M8dUbxymE3xtiAXszRkGMmezMhBS8Li7wEsMojLdb4Sdxc4wc' export const aliceB58 = '148d8KTRcKA5JKPekBcKFd4KfvprvFRpjGtivhtmRmnZ8MFYnP3' +export const bobAliceMultisig2of2B58 = '1SYJnDnV2G1HSzoBF9nwd5apBX3pS7nLeLkjnVXemBZTP8C8F44TBYnr' +export const bobAliceMultisig1of2B58 = '1SVRdbavwiw4SM6cQFq6DN2nhK4YSqTd7cPhELjshVxzdQvoKbhQWocF' +export const testnetBobAliceMultisig2of2B58 = '14x4TpdfsLeL9MMcaJp6EVXFnA5tsgqXCr2u8MCL4qMEpKEYPCHZEEGJo' export const bobBip39Words = bobWords.map((word) => (word !== 'energy' ? word : 'episode')) diff --git a/integration_tests/package.json b/integration_tests/package.json index 928d4666..57a6ca9f 100644 --- a/integration_tests/package.json +++ b/integration_tests/package.json @@ -10,5 +10,8 @@ "@helium/http": "^4.1.0", "@helium/transactions": "^4.1.0" }, - "version": "4.1.0" + "version": "4.1.0", + "devDependencies": { + "nock": "^13.2.4" + } } diff --git a/integration_tests/tests/create_and_submit_payment.spec.ts b/integration_tests/tests/create_and_submit_payment.spec.ts index f70f9e56..83392ddf 100644 --- a/integration_tests/tests/create_and_submit_payment.spec.ts +++ b/integration_tests/tests/create_and_submit_payment.spec.ts @@ -1,8 +1,9 @@ import nock from 'nock' -import { Keypair } from '@helium/crypto' -import Address from '@helium/address' +import { Keypair, MultisigSignature } from '@helium/crypto' +import Address, { MultisigAddress } from '@helium/address' import { Client } from '@helium/http' import { PaymentV1, PaymentV2 } from '@helium/transactions' +import KeySignature from '@helium/crypto/build/KeySignature' import { bobWords, bobBip39Words, aliceB58 } from '../fixtures/users' test('create and submit a payment txn', async () => { @@ -96,3 +97,40 @@ test('using the bip39 checksum word should match serialization', async () => { 'wgGOAQohATUaccIv7+wiMZNq0oJrIX7OOdn3f8bEljmSYpnDhpKVEiUKIQGcZZ1yPMHoEKcuePfer0c2qH8Q74/PyAEAtTMn5+5JpBAKIAEqQK88GjmG9CrESHVdcL//ZfWD+KsBnbKmZqKlx8oD89FUms7OjZNcL5NiQ4o0jREg+ahkjc2jX4SgKBBniM+QoAA=', ) }) + +test('create and sign multisig payment', async () => { + const bob = await Keypair.fromWords(bobBip39Words) + const aliceAddress = Address.fromB58(aliceB58) + + const multisigAddress = await MultisigAddress.create([bob.address, aliceAddress], 1) + + const paymentTxn = new PaymentV2({ + payer: multisigAddress, + payments: [ + { + payee: aliceAddress, + amount: 10, + }, + ], + nonce: 1, + }) + // Sign with bob keypair and create signature for multisig + const bobSignedTransaction = await paymentTxn.sign({ payer: bob }) + const bobSignature : Uint8Array = bobSignedTransaction.signature || new Uint8Array() + + // Create map of address to signature and convert to KeySignature list + const signatureMap = new Map([[bob.address, bobSignature]]) + const signatures = KeySignature.fromMap([bob.address, aliceAddress], signatureMap) + + // Construct multisig signature and verify + const multisigSig = new MultisigSignature([bob.address, aliceAddress], signatures) + expect(multisigSig.isValid(multisigAddress)).toBeTruthy() + + // Sign payment trasnaction with multisig signature + await paymentTxn.sign({ payer: multisigSig }) + + const serializedTxn = paymentTxn.toString() + expect(serializedTxn).toBe( + 'wgHXAQolAgECEiBqqzKbCO7og1KrG7VnpqrgT+wIowchqqdNAdWDQAa5HRIlCiEBnGWdcjzB6BCnLnj33q9HNqh/EO+Pz8gBALUzJ+fuSaQQCiABKoQBATUaccIv7+wiMZNq0oJrIX7OOdn3f8bEljmSYpnDhpKVAZxlnXI8wegQpy54996vRzaofxDvj8/IAQC1Myfn7kmkAEDfDD6a0GpxsreMPBmr+VACsNHtdEpBnCL1RUzTvqS6N7x9dmSEt8SZeqlTFTmzaLoC8zi4OCNf6zcf+Z347fsH', + ) +}) diff --git a/integration_tests/tsconfig.json b/integration_tests/tsconfig.json index e2926701..48610a17 100644 --- a/integration_tests/tsconfig.json +++ b/integration_tests/tsconfig.json @@ -1,4 +1,3 @@ { - "extends": "../../tsconfig.json", - "include": ["./tests"] + "extends": "../tsconfig.json", } diff --git a/integration_tests/yarn.lock b/integration_tests/yarn.lock index e01724b2..b57e56b0 100644 --- a/integration_tests/yarn.lock +++ b/integration_tests/yarn.lock @@ -2,256 +2,39 @@ # yarn lockfile v1 -"@helium/crypto@^3.24.0": - version "3.24.0" - resolved "https://registry.yarnpkg.com/@helium/crypto/-/crypto-3.24.0.tgz#5aa88945a0c646ae5f38e5bd8560a5fd18ee2157" - integrity sha512-XUk2oJOEPYx5LN+NyNw1Y/jhIum+NaZEeZLjvqNAtgEqgVlEkPKcZNc1vJwb7r1qftY92O6P0owRTC2vhexmUg== - dependencies: - bs58 "^4.0.1" - create-hash "^1.2.0" - libsodium-wrappers "^0.7.6" - -"@helium/proto@^1.3.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@helium/proto/-/proto-1.4.0.tgz#7a0ce268642173966519c2fb5f6e57508bff76d1" - integrity sha512-0yzzwfwpygowZNqB/9+vI/0gq5TV55ALAUqVuX6TbZZd9DGv4+AHICLiaIandLBBmNPpFzJ0c/IBdBcHJS/g1w== - dependencies: - protobufjs "^6.8.9" - -"@helium/transactions@^3.30.0": - version "3.30.0" - resolved "https://registry.yarnpkg.com/@helium/transactions/-/transactions-3.30.0.tgz#b0bdc20f08f6573271a05f37542c070ea0cefc54" - integrity sha512-LuZI7ybo4czDDXYEIqDreAdCH954Nj9MxIPDJxRAsChmWX7v76JTyAJUk5PYNgXePXL7Grl9+xRc4l+9jvKGqQ== - dependencies: - "@helium/crypto" "^3.24.0" - "@helium/proto" "^1.3.0" - "@types/libsodium-wrappers" "^0.7.8" - long "^4.0.0" - path "^0.12.7" - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= - -"@types/libsodium-wrappers@^0.7.8": - version "0.7.8" - resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.8.tgz#34575d7692fdbb7a7fdb63afcde381db86ec0de2" - integrity sha512-vkDSj6enD3K0+Ep83wnoGUk+f7sqsO4alsqxxEZ8BcTJhFmcY4UehYH3rTf4M3JGHXNhdpGFDdMbWFMgyvw/fA== - -"@types/long@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== - -"@types/node@^13.7.0": - version "13.13.39" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.39.tgz#237d071fb593d3aaa96f655a8eb2f91e0fb1480c" - integrity sha512-wct+WgRTTkBm2R3vbrFOqyZM5w0g+D8KnhstG9463CJBVC3UVZHMToge7iMBR1vDl/I+NWFHUeK9X+JcF0rWKw== - -base-x@^3.0.2: - version "3.0.8" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" - integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== - dependencies: - safe-buffer "^5.0.1" - -bs58@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" - integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= - dependencies: - base-x "^3.0.2" - -cipher-base@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -libsodium-wrappers@^0.7.6: - version "0.7.8" - resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.8.tgz#d95cdf3e7236c2aef76844bf8e1929ba9eef3e9e" - integrity sha512-PDhPWXBqd/SaqAFUBgH2Ux7b3VEEJgyD6BQB+VdNFJb9PbExGr/T/myc/MBoSvl8qLzfm0W0IVByOQS5L1MrCg== - dependencies: - libsodium "0.7.8" - -libsodium@0.7.8: - version "0.7.8" - resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.8.tgz#fbd12247b7b1353f88d8de1cbc66bc1a07b2e008" - integrity sha512-/Qc+APf0jbeWSaeEruH0L1/tbbT+sbf884ZL0/zV/0JXaDPBzYkKbyb/wmxMHgAHzm3t6gqe7bOOXAVwfqVikQ== - -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -path@^0.12.7: - version "0.12.7" - resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" - integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= - dependencies: - process "^0.11.1" - util "^0.10.3" - -process@^0.11.1: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -protobufjs@^6.8.9: - version "6.10.2" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" - integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" - "@types/node" "^13.7.0" - long "^4.0.0" - -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -sha.js@^2.4.0: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -util@^0.10.3: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" - integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== - dependencies: - inherits "2.0.3" +debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +lodash.set@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" + integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nock@^13.2.4: + version "13.2.4" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.4.tgz#43a309d93143ee5cdcca91358614e7bde56d20e1" + integrity sha512-8GPznwxcPNCH/h8B+XZcKjYPXnUV5clOKCjAqyjsiqA++MpNx9E9+t8YPp0MbThO+KauRo7aZJ1WuIZmOrT2Ug== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + lodash.set "^4.3.2" + propagate "^2.0.0" + +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== diff --git a/packages/address/package-lock.json b/packages/address/package-lock.json new file mode 100644 index 00000000..260cd20c --- /dev/null +++ b/packages/address/package-lock.json @@ -0,0 +1,117 @@ +{ + "name": "@helium/address", + "version": "4.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@helium/address", + "version": "4.0.0", + "license": "Apache-2.0", + "dependencies": { + "bs58": "^4.0.1", + "js-sha256": "^0.9.0", + "multiformats": "^9.6.4" + }, + "devDependencies": { + "@types/bs58": "4.0.1" + } + }, + "node_modules/@types/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==", + "dev": true, + "dependencies": { + "base-x": "^3.0.6" + } + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "node_modules/multiformats": { + "version": "9.6.4", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.6.4.tgz", + "integrity": "sha512-fCCB6XMrr6CqJiHNjfFNGT0v//dxOBMrOMqUIzpPc/mmITweLEyhvMpY9bF+jZ9z3vaMAau5E8B68DW77QMXkg==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + } + }, + "dependencies": { + "@types/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==", + "dev": true, + "requires": { + "base-x": "^3.0.6" + } + }, + "base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, + "js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "multiformats": { + "version": "9.6.4", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.6.4.tgz", + "integrity": "sha512-fCCB6XMrr6CqJiHNjfFNGT0v//dxOBMrOMqUIzpPc/mmITweLEyhvMpY9bF+jZ9z3vaMAau5E8B68DW77QMXkg==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } +} diff --git a/packages/address/package.json b/packages/address/package.json index 886497a1..1af3f7ee 100644 --- a/packages/address/package.json +++ b/packages/address/package.json @@ -27,8 +27,9 @@ }, "gitHead": "16442bef09f90dd9a83d4c9e1a347de3e80575e4", "dependencies": { - "bs58": "4.0.1", - "js-sha256": "^0.9.0" + "bs58": "^5.0.0", + "js-sha256": "^0.9.0", + "multiformats": "^9.6.4" }, "devDependencies": { "@types/bs58": "4.0.1" diff --git a/packages/address/src/KeyTypes.ts b/packages/address/src/KeyTypes.ts index 1676ab41..1d243ec9 100644 --- a/packages/address/src/KeyTypes.ts +++ b/packages/address/src/KeyTypes.ts @@ -1,9 +1,11 @@ export const ECC_COMPACT_KEY_TYPE = 0 export const ED25519_KEY_TYPE = 1 +export const MULTISIG_KEY_TYPE = 2 export const SUPPORTED_KEY_TYPES = [ ECC_COMPACT_KEY_TYPE, ED25519_KEY_TYPE, + MULTISIG_KEY_TYPE, ] export type KeyType = number diff --git a/packages/address/src/MultisigAddress.ts b/packages/address/src/MultisigAddress.ts new file mode 100644 index 00000000..2ecf58c7 --- /dev/null +++ b/packages/address/src/MultisigAddress.ts @@ -0,0 +1,98 @@ +import { sha256 } from 'multiformats/hashes/sha2' +import { + bs58M, + bs58N, + bs58Version, + bs58MultisigPublicKey, + bs58NetType, + byteToNetType, + byteToKeyType, + sortAddresses, + bs58KeyType, +} from './utils' +import { MULTISIG_KEY_TYPE } from './KeyTypes' +import { NetType, MAINNET } from './NetTypes' +import Address from './Address' + +export default class MultisigAddress extends Address { + public M!: number + + public N!: number + + public constructor( + version: number, netType: NetType, M: number, N: number, publicKey: Uint8Array, + ) { + if (M > 256) { + throw new Error('required signers cannot exceed 256') + } + if (N > 256) { + throw new Error('total signers cannot exceed 256') + } + if (M > N) { + throw new Error('required signers cannot exceed total signers') + } + super(version, netType, MULTISIG_KEY_TYPE, publicKey) + this.M = M + this.N = N + } + + get bin(): Buffer { + return Buffer.concat([ + // eslint-disable-next-line no-bitwise + Buffer.from([this.netType | this.keyType]), + Buffer.from(new Uint8Array([this.M])), + Buffer.from(new Uint8Array([this.N])), + Buffer.from(this.publicKey), + ]) + } + + static fromB58(b58: string): MultisigAddress { + const keyType = bs58KeyType(b58) + if (keyType !== MULTISIG_KEY_TYPE) { + throw new Error('invalid keytype for multisig address') + } + const version = bs58Version(b58) + const netType = bs58NetType(b58) + const M = bs58M(b58) + const N = bs58N(b58) + const publicKey = bs58MultisigPublicKey(b58) + return new MultisigAddress(version, netType, M, N, publicKey) + } + + static fromBin(bin: Buffer): MultisigAddress { + const version = 0 + const byte = bin[0] + const netType = byteToNetType(byte) + const keyType = byteToKeyType(byte) + if (keyType !== MULTISIG_KEY_TYPE) { + throw new Error('invalid keytype for multisig address') + } + const M = bin[1] + const N = bin[2] + const publicKey = bin.slice(3, bin.length) + return new MultisigAddress(version, netType, M, N, publicKey) + } + + public static async create( + addresses: Address[], M: number, netType?: NetType, + ): Promise { + if (addresses.some((addr) => addr.keyType === MULTISIG_KEY_TYPE)) { + return Promise.reject(new Error('cannot create multisig with invalid child keytype')) + } + const version = 0 + const multisigPubKeysBin = sortAddresses(addresses).map((address) => address.bin) + const publicKey = (await sha256.digest(multisigPubKeysBin.reduce( + (acc, curVal) => new Uint8Array([...acc, ...curVal]), new Uint8Array(), + ))) + return new MultisigAddress(version, netType || MAINNET, M, addresses.length, publicKey.bytes) + } + + static isValid(b58: string): boolean { + try { + MultisigAddress.fromB58(b58) + return true + } catch (error) { + return false + } + } +} diff --git a/packages/address/src/__tests__/MultisigAddress.spec.ts b/packages/address/src/__tests__/MultisigAddress.spec.ts new file mode 100644 index 00000000..8b4e0142 --- /dev/null +++ b/packages/address/src/__tests__/MultisigAddress.spec.ts @@ -0,0 +1,92 @@ +import Address from '..' +import MultisigAddress from '../MultisigAddress' +import { + bobB58, + aliceB58, + bobAliceMultisig1of2B58, + bobAliceMultisig2of2B58, + testnetBobAliceMultisig2of2B58, +} from '../../../../integration_tests/fixtures/users' +import { TESTNET } from '../NetTypes' + +describe('multisig b58', () => { + it('returns a b58 check encoded representation of a multisig address', async () => { + const addressMultisig2of2 = await MultisigAddress.create( + [Address.fromB58(bobB58), Address.fromB58(aliceB58)], 2, + ) + expect(addressMultisig2of2.b58).toBe(bobAliceMultisig2of2B58) + }) + + it('supports multisig addresses', () => { + const addressMultisig2of2 = MultisigAddress.fromB58(bobAliceMultisig2of2B58) + expect(addressMultisig2of2.b58).toBe(bobAliceMultisig2of2B58) + }) +}) + +describe('bin', () => { + it('returns a binary representation of the multisig ddress', async () => { + const addressMultisig2of2 = await MultisigAddress.create( + [Address.fromB58(bobB58), Address.fromB58(aliceB58)], 2, + ) + expect(addressMultisig2of2.bin[0]).toBe(2) + }) +}) + +describe('fromBin', () => { + it('builds a MultisigAddress from a binary representation', async () => { + const multisigAddress = await MultisigAddress.create( + [Address.fromB58(bobB58), Address.fromB58(aliceB58)], 2, + ) + const multisigAddressFromBin = MultisigAddress.fromBin(multisigAddress.bin) + expect(multisigAddressFromBin.b58).toBe(multisigAddress.b58) + }) +}) + +describe('fromB58', () => { + it('builds an Address from a b58 string', () => { + const multisigAddressFromB58 = Address.fromB58(bobAliceMultisig2of2B58) + expect(multisigAddressFromB58.b58).toBe(bobAliceMultisig2of2B58) + }) +}) + +describe('unsupported child key types', () => { + it('throws an error if creating address with multisig key type', async () => { + expect(async () => { + await MultisigAddress.create( + [MultisigAddress.fromB58(bobAliceMultisig2of2B58), Address.fromB58(aliceB58)], 2, + ) + }).rejects.toThrow() + }) +}) + +describe('isValid', () => { + it('returns true if the address is valid and supported', () => { + expect(MultisigAddress.isValid(bobAliceMultisig2of2B58)).toBeTruthy() + expect(MultisigAddress.isValid(bobAliceMultisig1of2B58)).toBeTruthy() + }) +}) + +describe('testnet addresses', () => { + it('decodes testnet addresses from b58', async () => { + const address = MultisigAddress.fromB58(testnetBobAliceMultisig2of2B58) + expect(address.netType).toBe(TESTNET) + }) +}) + +describe('testnet addresses', () => { + it('decodes testnet addresses from b58', async () => { + const address = MultisigAddress.fromB58(testnetBobAliceMultisig2of2B58) + expect(address.netType).toBe(TESTNET) + }) +}) + +describe('erlang interop', () => { + it('makes the same multisig key as the erlang lib ', async () => { + const keys = [ + Address.fromB58('11MJXxoWFp2bMsqKM6QZin6ync9DQ3fjjFjUrFiRCaKunmBEBhK'), + Address.fromB58('11x7jP9yAnyk5jeYywmsYDFdYq5xvKLKjP2zjhGzCwDSQtxcUDt'), + ] + const address = await MultisigAddress.create(keys, 1) + expect(address.b58).toBe('1SVRdbaAev7zSpUsMjvQrbRBGFHLXEa63SGntYCqChC4CTpqwftTPGbZ') + }) +}) diff --git a/packages/address/src/__tests__/Utils.spec.ts b/packages/address/src/__tests__/Utils.spec.ts index 30f325a9..6f3430c9 100644 --- a/packages/address/src/__tests__/Utils.spec.ts +++ b/packages/address/src/__tests__/Utils.spec.ts @@ -22,7 +22,7 @@ describe('bs58ToBin', () => { const address = new Address(0, NetTypes.MAINNET, 1, bob.publicKey).b58 const bin = bs58.decode(address) const vPayload = bin.slice(0, -4) - const checksum = bin.slice(-4) + const checksum = Buffer.from(bin.slice(-4)) const checksumVerify = sha256(Buffer.from(sha256.digest(vPayload))) const checksumVerifyBytes = Buffer.alloc(4, checksumVerify, 'hex') expect(checksumVerifyBytes).toStrictEqual(checksum) diff --git a/packages/address/src/index.ts b/packages/address/src/index.ts index a1fc2e61..25fd2d03 100644 --- a/packages/address/src/index.ts +++ b/packages/address/src/index.ts @@ -5,6 +5,7 @@ */ export { default } from './Address' +export { default as MultisigAddress } from './MultisigAddress' export * as NetTypes from './NetTypes' export * as KeyTypes from './KeyTypes' export * as utils from './utils' diff --git a/packages/address/src/utils.ts b/packages/address/src/utils.ts index 672d47a6..7fedc3ab 100644 --- a/packages/address/src/utils.ts +++ b/packages/address/src/utils.ts @@ -3,6 +3,7 @@ import { sha256 } from 'js-sha256' import bs58 from 'bs58' import { KeyType } from './KeyTypes' import { NetType } from './NetTypes' +import Address from './Address' export const bs58CheckEncode = (version: number, binary: Buffer | Uint8Array): string => { const vPayload = Buffer.concat([ @@ -63,3 +64,30 @@ export const bs58PublicKey = (bs58Address: string): Buffer => { const publicKey = Buffer.from(bin).slice(1) return publicKey } + +export const bs58M = (bs58Address: string): number => { + const bin = bs58ToBin(bs58Address) + const M = bin[1] + return M +} + +export const bs58N = (bs58Address: string): number => { + const bin = bs58ToBin(bs58Address) + const N = bin[2] + return N +} + +export const bs58MultisigPublicKey = (bs58Address: string): Buffer => { + const bin = bs58ToBin(bs58Address) + const publicKey = Buffer.from(bin).slice(3) + return publicKey +} + +export const sortAddresses = (addresses: Address[]): Address[] => { + const addressMap = addresses.map((address) => { + const charCodeArray = Array.from(address.b58).map((character):number => character.charCodeAt(0)) + return { address, buffer: new Uint8Array(charCodeArray) } + }) + + return addressMap.sort((a, b) => Buffer.compare(a.buffer, b.buffer)).map((obj) => obj.address) +} diff --git a/packages/address/yarn.lock b/packages/address/yarn.lock index 46585c6c..ae2af5bb 100644 --- a/packages/address/yarn.lock +++ b/packages/address/yarn.lock @@ -4,31 +4,41 @@ "@types/bs58@4.0.1": version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/bs58/-/bs58-4.0.1.tgz#3d51222aab067786d3bc3740a84a7f5a0effaa37" + resolved "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz" integrity sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA== dependencies: base-x "^3.0.6" -base-x@^3.0.2, base-x@^3.0.6: +base-x@^3.0.6: version "3.0.9" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + resolved "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz" integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== dependencies: safe-buffer "^5.0.1" -bs58@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" - integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= +base-x@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" + integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== + +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== dependencies: - base-x "^3.0.2" + base-x "^4.0.0" js-sha256@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== +multiformats@^9.6.4: + version "9.6.4" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.6.4.tgz#5dce1f11a407dbb69aa612cb7e5076069bb759ca" + integrity sha512-fCCB6XMrr6CqJiHNjfFNGT0v//dxOBMrOMqUIzpPc/mmITweLEyhvMpY9bF+jZ9z3vaMAau5E8B68DW77QMXkg== + safe-buffer@^5.0.1: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== diff --git a/packages/crypto/src/KeySignature.ts b/packages/crypto/src/KeySignature.ts new file mode 100644 index 00000000..928b6ded --- /dev/null +++ b/packages/crypto/src/KeySignature.ts @@ -0,0 +1,33 @@ +import Address, { KeyTypes, utils } from '@helium/address' + +export default class KeySignature { + public index!: number + + public signature!: Uint8Array + + constructor(index: number, signature: Uint8Array) { + this.index = index + this.signature = signature + } + + public static new(addresses: Address[], address: Address, signature: Uint8Array): KeySignature { + if (address.keyType === KeyTypes.MULTISIG_KEY_TYPE) { + throw new Error('invalid keytype for multisig KeySignature') + } + return new KeySignature( + utils.sortAddresses(addresses).findIndex((addr) => addr.publicKey === address.publicKey), + signature, + ) + } + + public static fromMap( + addresses: Address[], + signatureMap: Map, + ): KeySignature[] { + return Array.from(signatureMap).map((value) => KeySignature.new(addresses, value[0], value[1])) + } + + get bin(): Uint8Array { + return new Uint8Array([this.index, this.signature.length, ...this.signature]) + } +} diff --git a/packages/crypto/src/MultisigSignature.ts b/packages/crypto/src/MultisigSignature.ts new file mode 100644 index 00000000..8cb18bb3 --- /dev/null +++ b/packages/crypto/src/MultisigSignature.ts @@ -0,0 +1,101 @@ +import Address, { MultisigAddress, utils } from '@helium/address' +import { verify as verifySignature } from './utils' +import KeySignature from './KeySignature' + +const PUBLIC_KEY_LENGTH = 33 + +export default class MultisigSignature { + public addresses!: Address[] + + public signatures!: KeySignature[] + + constructor(addresses: Address[], signatures: KeySignature[]) { + this.addresses = utils.sortAddresses(addresses) + this.signatures = signatures + } + + public async isValid(address: MultisigAddress): Promise { + if (address.M > this.signatures.length) { + return false + } + if (address.N !== this.addresses.length) { + return false + } + const thisAddress = await MultisigAddress.create(this.addresses, address.M, address.netType) + if (thisAddress.publicKey !== address.publicKey) { + return false + } + return true + } + + public async verify(message: string | Uint8Array): Promise { + const verifiedSignatures = await Promise.all( + this.signatures.map((sig) => + verifySignature(sig.signature, message, this.addresses[sig.index].publicKey), + ), + ) + return verifiedSignatures.filter((isVerified) => isVerified === true).length + } + + public get bin(): Uint8Array { + return new Uint8Array([...this.serializedAddresses(), ...this.serlializedSignatures()]) + } + + public static fromBin(multisigAddress: MultisigAddress, input: Uint8Array): MultisigSignature { + const addresses = this.addressesFromBin(multisigAddress.N, input) + const signatures = this.signaturesFromBin(input.slice(PUBLIC_KEY_LENGTH * multisigAddress.N)) + return new MultisigSignature(addresses, signatures) + } + + static isValid(multisigAddress: MultisigAddress, input: Uint8Array): boolean { + try { + MultisigSignature.fromBin(multisigAddress, input) + return true + } catch (error) { + return false + } + } + + private serializedAddresses(): Uint8Array { + return this.addresses.reduce( + (acc, curVal) => new Uint8Array([...acc, ...curVal.bin]), + new Uint8Array(), + ) + } + + private serlializedSignatures() { + return this.signatures + .sort((a, b) => (a.bin > b.bin ? 1 : -1)) + .reduce((acc, curVal) => new Uint8Array([...acc, ...curVal.bin]), new Uint8Array()) + } + + private static addressesFromBin(N: number, input: Uint8Array): Address[] { + return Array(N) + .fill(null) + .map((_, i) => + Address.fromBin( + Buffer.from(input.slice(PUBLIC_KEY_LENGTH * i, PUBLIC_KEY_LENGTH * (i + 1))), + ), + ) + } + + private static signaturesFromBin(input: Uint8Array): KeySignature[] { + let index = 0 + const signatureList: KeySignature[] = [] + do { + const addressIndex = input[index] + const start = index + 2 + const end = start + input[index + 1] + signatureList.push(new KeySignature(addressIndex, input.slice(start, end))) + index += input[index + 1] + 2 + } while (index < input.length) + return signatureList + } + + async sign(message: string | Uint8Array): Promise { + if (await this.verify(message) < 1) { + throw new Error('no valid signatures for message') + } + return this.bin + } +} diff --git a/packages/crypto/src/__tests__/MultisigSignature.spec.ts b/packages/crypto/src/__tests__/MultisigSignature.spec.ts new file mode 100644 index 00000000..ad7a19cc --- /dev/null +++ b/packages/crypto/src/__tests__/MultisigSignature.spec.ts @@ -0,0 +1,161 @@ +import { MultisigAddress } from '@helium/address' +import { KeySignature, MultisigSignature } from '..' +import { usersFixture } from '../../../../integration_tests/fixtures/users' + +describe('create', () => { + it('invalid if not enough signatures', async () => { + const { bob, alice } = await usersFixture() + const multisigAddress = await MultisigAddress.create([bob.address, alice.address], 1) + const signatures = KeySignature.fromMap([bob.address, alice.address], new Map()) + const multisigSignature = new MultisigSignature([bob.address, alice.address], signatures) + expect(await multisigSignature.isValid(multisigAddress)).toBeFalsy() + }) + + it('invalid if not enough addresses', async () => { + const { bob, alice } = await usersFixture() + const signatureMap = new Map([[bob.address, await bob.sign('Hello')]]) + const multisigAddress = await MultisigAddress.create([bob.address, alice.address], 1) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSignature = new MultisigSignature([bob.address], signatures) + expect(await multisigSignature.isValid(multisigAddress)).toBeFalsy() + }) + + it('invalid if too many (unique) addresses', async () => { + const { bob, alice } = await usersFixture() + const signatureMap = new Map([[bob.address, await bob.sign('Hello')]]) + const multisigAddress = await MultisigAddress.create([bob.address, alice.address], 1) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSignature = new MultisigSignature( + [bob.address, bob.address, alice.address], signatures, + ) + expect(await multisigSignature.isValid(multisigAddress)).toBeFalsy() + }) + + it('create a multisig signature', async () => { + const message = Buffer.from('Hello') + const { bob, alice } = await usersFixture() + const multisigAddress = await MultisigAddress.create([bob.address, alice.address], 1) + + const signatureMap = new Map([[bob.address, await bob.sign(message)]]) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSignature = new MultisigSignature([bob.address, alice.address], signatures) + + expect(multisigSignature.isValid(multisigAddress)).toBeTruthy() + expect(multisigSignature.signatures.length).toBe(1) + expect(multisigSignature.signatures[0].index).toBe(0) + expect(multisigSignature.addresses.length).toBe(2) + expect(multisigSignature.addresses[0].b58).toBe(bob.address.b58) + expect(multisigSignature.addresses[1].b58).toBe(alice.address.b58) + }) +}) + +describe('verify', () => { + it('verify one of two signature', async () => { + const message = Buffer.from('Hello') + const { bob, alice } = await usersFixture() + const signatureMap = new Map([[bob.address, await bob.sign(message)]]) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSig = new MultisigSignature([bob.address, alice.address], signatures) + expect(await multisigSig.verify(message)).toBe(1) + }) + + it('verify fail one of two signature', async () => { + const message = Buffer.from('Hello') + const { bob, alice } = await usersFixture() + const signatureMap = new Map([[bob.address, await bob.sign(Buffer.from('Oops'))]]) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSig = new MultisigSignature([bob.address, alice.address], signatures) + expect(await multisigSig.verify(message)).toBe(0) + }) + + it('verify two of two signature', async () => { + const message = Buffer.from('Hello') + const { bob, alice } = await usersFixture() + const signatureMap = new Map( + [[bob.address, await bob.sign(message)], [alice.address, await alice.sign(message)]], + ) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSig = new MultisigSignature([bob.address, alice.address], signatures) + expect(await multisigSig.verify(message)).toBe(2) + }) + + it('verify fail two of two signature', async () => { + const message = Buffer.from('Hello') + const { bob, alice } = await usersFixture() + const signatureMap = new Map( + [[bob.address, await bob.sign(message)], [alice.address, await alice.sign(Buffer.from('Oops'))]], + ) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSig = new MultisigSignature([bob.address, alice.address], signatures) + expect(await multisigSig.verify(message)).toBe(1) + }) +}) + +describe('bin', () => { + it('serialize appropriately', async () => { + const message = Buffer.from('Hello') + const { bob, alice } = await usersFixture() + const signatureMap = new Map([[bob.address, await bob.sign(message)]]) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSig = new MultisigSignature([bob.address, alice.address], signatures) + expect(multisigSig.bin[0]).toBe(1) + }) + + it('serialize with signatures in correct sorted order', async () => { + const message = Buffer.from('Hello') + const { bob, alice } = await usersFixture() + const multisigAddress = await MultisigAddress.create([bob.address, alice.address], 1) + + const signatureMap = new Map( + [[alice.address, await alice.sign(message)], [bob.address, await bob.sign(message)]], + ) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSig = new MultisigSignature([bob.address, alice.address], signatures) + + // Signatures not sorted + expect(multisigSig.signatures[1].index).toBe(0) + expect(multisigSig.signatures[0].index).toBe(1) + + const multisigSignatureTest = MultisigSignature.fromBin(multisigAddress, multisigSig.bin) + expect(await multisigSignatureTest.verify(message)).toBe(2) + expect(multisigSignatureTest.signatures[1].signature).toStrictEqual(await alice.sign(message)) + + // Signatures sorted + expect(multisigSignatureTest.signatures[0].index).toBe(0) + expect(multisigSignatureTest.signatures[1].index).toBe(1) + }) +}) + +describe('fromBin', () => { + it('deserialize appropriately', async () => { + const message = Buffer.from('Hello') + const { bob, alice } = await usersFixture() + const multisigAddress = await MultisigAddress.create([bob.address, alice.address], 1) + const signatureMap = new Map([[bob.address, await bob.sign(message)]]) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSig = new MultisigSignature([bob.address, alice.address], signatures) + const multisigSignatureFromBin = MultisigSignature.fromBin(multisigAddress, multisigSig.bin) + expect(await multisigSignatureFromBin.verify(message)).toBe(1) + expect(multisigSignatureFromBin.signatures[0].signature).toStrictEqual(await bob.sign(message)) + }) +}) + +describe('isValid', () => { + it('is valid if can deserialize', async () => { + const message = Buffer.from('Hello') + const { bob, alice } = await usersFixture() + const multisigAddress = await MultisigAddress.create([bob.address, alice.address], 1) + const signatureMap = new Map([[bob.address, await bob.sign(message)]]) + const signatures = KeySignature.fromMap([bob.address, alice.address], signatureMap) + const multisigSig = new MultisigSignature([bob.address, alice.address], signatures) + expect(MultisigSignature.isValid(multisigAddress, multisigSig.bin)).toBe(true) + }) + + it('is not valid if cannot deserialize', async () => { + const { bob, alice } = await usersFixture() + const multisigAddress = await MultisigAddress.create([bob.address, alice.address], 1) + expect(MultisigSignature.isValid( + multisigAddress, new Uint8Array(Buffer.from('notavalidmultisigserialization')), + )).toBe(false) + }) +}) diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index de087b59..8c947d54 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -6,4 +6,6 @@ export { default as Mnemonic } from './Mnemonic' export { default as Keypair } from './Keypair' +export { default as KeySignature } from './KeySignature' +export { default as MultisigSignature } from './MultisigSignature' export * as utils from './utils' diff --git a/packages/crypto/src/utils.ts b/packages/crypto/src/utils.ts index eb355616..2e6e0c39 100644 --- a/packages/crypto/src/utils.ts +++ b/packages/crypto/src/utils.ts @@ -8,7 +8,8 @@ export const randomBytes = async (n: number): Promise => { return Buffer.from(sodium.randombytes_buf(n)) } -export const sha256 = (buffer: Buffer | string): Buffer => createHash('sha256').update(buffer).digest() +export const sha256 = (buffer: Buffer | string): Buffer => + createHash('sha256').update(buffer).digest() export const lpad = (str: string | any[], padString: string, length: number) => { let strOut = str @@ -16,9 +17,10 @@ export const lpad = (str: string | any[], padString: string, length: number) => return strOut } -export const bytesToBinary = (bytes: any[]) => bytes - .map((x: { toString: (arg0: number) => string | any[] }) => lpad(x.toString(2), '0', 8)) - .join('') +export const bytesToBinary = (bytes: any[]) => + bytes + .map((x: { toString: (arg0: number) => string | any[] }) => lpad(x.toString(2), '0', 8)) + .join('') export const binaryToByte = (bin: string) => parseInt(bin, 2) diff --git a/packages/crypto/yarn.lock b/packages/crypto/yarn.lock index 1918082b..14436063 100644 --- a/packages/crypto/yarn.lock +++ b/packages/crypto/yarn.lock @@ -2,12 +2,13 @@ # yarn lockfile v1 -"@types/bs58@4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/bs58/-/bs58-4.0.1.tgz#3d51222aab067786d3bc3740a84a7f5a0effaa37" - integrity sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA== +"@helium/address@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.0.0.tgz#041c075bf6b750e52cdbee4a45950bfbf42e085a" + integrity sha512-rkQNI98lifSTc2I9GfVYRukThUCY+9ba77EadhaiFI9SHOJ/lSFp93zHOHkcyr1AI8DpKgZQGx19hqFOL7QpcA== dependencies: - base-x "^3.0.6" + bs58 "4.0.1" + js-sha256 "^0.9.0" "@types/create-hash@1.2.2": version "1.2.2" @@ -26,14 +27,14 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7" integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g== -base-x@^3.0.2, base-x@^3.0.6: +base-x@^3.0.2: version "3.0.8" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== dependencies: safe-buffer "^5.0.1" -bs58@^4.0.1: +bs58@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= @@ -72,6 +73,11 @@ inherits@^2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +js-sha256@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" + integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== + libsodium-wrappers@^0.7.6: version "0.7.6" resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.6.tgz#baed4c16d4bf9610104875ad8a8e164d259d48fb" diff --git a/packages/currency/src/currency_types/BaseCurrencyType.ts b/packages/currency/src/currency_types/BaseCurrencyType.ts index f00f04ac..f59a4c08 100644 --- a/packages/currency/src/currency_types/BaseCurrencyType.ts +++ b/packages/currency/src/currency_types/BaseCurrencyType.ts @@ -8,7 +8,9 @@ function makeCoefficient(decimalPlaces: BigNumber): BigNumber { export default class BaseCurrencyType { public ticker: string + public decimalPlaces: BigNumber + public coefficient: BigNumber constructor(ticker: string, decimalPlaces: number) { diff --git a/packages/currency/src/currency_types/DataCredits.ts b/packages/currency/src/currency_types/DataCredits.ts index 89eb4ccc..aa6ed2f5 100644 --- a/packages/currency/src/currency_types/DataCredits.ts +++ b/packages/currency/src/currency_types/DataCredits.ts @@ -7,4 +7,3 @@ export default class DataCredits extends BaseCurrencyType { super(TICKER, 0) } } - diff --git a/packages/currency/src/currency_types/NetworkTokens.ts b/packages/currency/src/currency_types/NetworkTokens.ts index 368d98b6..a0bd8bc3 100644 --- a/packages/currency/src/currency_types/NetworkTokens.ts +++ b/packages/currency/src/currency_types/NetworkTokens.ts @@ -7,4 +7,3 @@ export default class NetworkTokens extends BaseCurrencyType { super(TICKER, 8) } } - diff --git a/packages/currency/src/currency_types/SecurityTokens.ts b/packages/currency/src/currency_types/SecurityTokens.ts index c16bef3e..39d18ac9 100644 --- a/packages/currency/src/currency_types/SecurityTokens.ts +++ b/packages/currency/src/currency_types/SecurityTokens.ts @@ -7,4 +7,3 @@ export default class SecurityTokens extends BaseCurrencyType { super(TICKER, 8) } } - diff --git a/packages/http/package.json b/packages/http/package.json index 66223c97..c78f17dd 100644 --- a/packages/http/package.json +++ b/packages/http/package.json @@ -35,7 +35,8 @@ "snakecase-keys": "^5.1.0" }, "devDependencies": { - "@types/qs": "6.9.5" + "@types/qs": "6.9.5", + "nock": "^13.2.4" }, "gitHead": "16442bef09f90dd9a83d4c9e1a347de3e80575e4" } diff --git a/packages/http/src/ResourceList.ts b/packages/http/src/ResourceList.ts index 0f0b9e54..c26517fa 100644 --- a/packages/http/src/ResourceList.ts +++ b/packages/http/src/ResourceList.ts @@ -31,7 +31,8 @@ export default class ResourceList { return !!this.cursor && !!this.fetchMore } - async *[Symbol.asyncIterator](): AsyncGenerator { + async* [Symbol.asyncIterator](): AsyncGenerator { + // eslint-disable-next-line no-restricted-syntax for (const item of this.data) { yield item } @@ -45,6 +46,7 @@ export default class ResourceList { } const values = [] while (values.length < count) { + // eslint-disable-next-line no-await-in-loop const { value, done } = await this.takeIterator.next() if (value !== undefined) values.push(value) if (done) return values diff --git a/packages/http/src/__tests__/Client.spec.ts b/packages/http/src/__tests__/Client.spec.ts index 97bba031..070e508e 100644 --- a/packages/http/src/__tests__/Client.spec.ts +++ b/packages/http/src/__tests__/Client.spec.ts @@ -1,21 +1,21 @@ import nock from 'nock' -import Client, { Network } from '..' +import HttpClient, { Network } from '..' test('exposes a client instance with default options', () => { const prodUrl = 'https://api.helium.io' - const client = new Client() + const client = new HttpClient() expect(client.network.baseURL).toBe(prodUrl) }) test('configure client with different endpoint', () => { const stagingUrl = 'https://api.helium.wtf' - const client = new Client(Network.staging) + const client = new HttpClient(Network.staging) expect(client.network.baseURL).toBe(stagingUrl) }) test('configure client with different endpoint', () => { const stagingUrl = 'https://api.helium.wtf' - const client = new Client(Network.staging) + const client = new HttpClient(Network.staging) expect(client.network.baseURL).toBe(stagingUrl) }) @@ -25,7 +25,7 @@ describe('get', () => { greeting: 'hello', }) - const client = new Client() + const client = new HttpClient() const { data } = await client.get('/greeting') @@ -41,7 +41,8 @@ describe('get', () => { greeting: 'hello', }) - const client = new Client(Network.production, { name: nameHeader, userAgent: userAgentHeader }) + const client = new HttpClient(Network.production, + { name: nameHeader, userAgent: userAgentHeader }) const response = await client.get('/greeting') expect(response.data.greeting).toBe('hello') @@ -61,7 +62,7 @@ it('client passes default headers to GET request', async () => { }) const opts = { name: nameHeader, userAgent: userAgentHeader, headers: { network } } - const client = new Client(Network.production, opts) + const client = new HttpClient(Network.production, opts) const response = await client.get('/greeting') expect(response.data.greeting).toBe('hello') @@ -75,7 +76,7 @@ describe('post', () => { nock('https://api.helium.io').post('/v1/greeting', { greeting: 'hello' }).reply(200, { response: 'hey there!', }) - const client = new Client() + const client = new HttpClient() const params = { greeting: 'hello' } const { data } = await client.post('/greeting', params) @@ -88,7 +89,8 @@ describe('post', () => { nock('https://api.helium.io').post('/v1/greeting', { greeting: 'hello' }).reply(200, { response: 'hey there!', }) - const client = new Client(Network.production, { name: nameHeader, userAgent: userAgentHeader }) + const client = new HttpClient(Network.production, + { name: nameHeader, userAgent: userAgentHeader }) const params = { greeting: 'hello' } const response = await client.post('/greeting', params) @@ -105,7 +107,7 @@ describe('post', () => { response: 'hey there!', }) const opts = { name: nameHeader, userAgent: userAgentHeader, headers: { network } } - const client = new Client(Network.production, opts) + const client = new HttpClient(Network.production, opts) const params = { greeting: 'hello' } const response = await client.post('/greeting', params) @@ -123,7 +125,7 @@ describe('retry logic', () => { }) it('retries requests with exponential backoff', async () => { - const client = new Client() + const client = new HttpClient() expect(client.retry).toBe(5) const { data } = await client.get('/greeting') expect(data.greeting).toBe('hello') @@ -135,7 +137,7 @@ describe('retry disabled', () => { nock('https://api.helium.io').get('/v1/farewell').times(1).reply(200, 'good response') it('make request with retry disabled', async () => { - const client = new Client(Network.production, { retry: 0 }) + const client = new HttpClient(Network.production, { retry: 0 }) expect(client.retry).toBe(0) const makeRequest = async () => { await client.get('/farewell') @@ -146,7 +148,7 @@ describe('retry disabled', () => { describe('name', () => { it('is initialized with a client name', () => { - const client = new Client(Network.production, { name: 'Test Client' }) + const client = new HttpClient(Network.production, { name: 'Test Client' }) expect(client.name).toBe('Test Client') }) @@ -155,7 +157,7 @@ describe('name', () => { greeting: 'hello', }) - const client = new Client(Network.production, { name: 'Test Client' }) + const client = new HttpClient(Network.production, { name: 'Test Client' }) const { request } = await client.get('/greeting') expect(request.headers['x-client-name']).toBe('Test Client') }) @@ -165,7 +167,7 @@ describe('name', () => { response: 'hey there!', }) - const client = new Client(Network.production, { name: 'Test Client' }) + const client = new HttpClient(Network.production, { name: 'Test Client' }) const params = { greeting: 'hello' } const { request } = await client.post('/greeting', params) expect(request.headers['x-client-name']).toBe('Test Client') diff --git a/packages/http/src/__tests__/Network.spec.ts b/packages/http/src/__tests__/Network.spec.ts index 1c361956..f7139bf7 100644 --- a/packages/http/src/__tests__/Network.spec.ts +++ b/packages/http/src/__tests__/Network.spec.ts @@ -1,4 +1,4 @@ -import { Network } from '../' +import { Network } from '..' describe('production', () => { it('returns the production network', () => { diff --git a/packages/http/src/__tests__/ResourceList.spec.ts b/packages/http/src/__tests__/ResourceList.spec.ts index 311cf105..2b6720f2 100644 --- a/packages/http/src/__tests__/ResourceList.spec.ts +++ b/packages/http/src/__tests__/ResourceList.spec.ts @@ -10,6 +10,7 @@ describe('auto pagination', () => { ) const list = new ResourceList(data) const fetchedData = [] + // eslint-disable-next-line no-restricted-syntax for await (const item of list) { fetchedData.push(item) } @@ -43,6 +44,7 @@ describe('auto pagination', () => { const list = new ResourceList(firstPageData, fetchMore, 'cursor-1') const fetchedData = [] + // eslint-disable-next-line no-restricted-syntax for await (const item of list) { fetchedData.push(item) } diff --git a/packages/http/src/models/Challenge.ts b/packages/http/src/models/Challenge.ts index d2ceba29..d0c59878 100644 --- a/packages/http/src/models/Challenge.ts +++ b/packages/http/src/models/Challenge.ts @@ -140,7 +140,8 @@ const constructPath = (path: HTTPPathObject[]): Path[] => { const hasValidWitness = pathObject.witnesses.some(isValidWitness) const hasReceiptOrValidWitnesses = hasReceipt || hasValidWitness const nextElement = path[i + 1] - const nextElementHasReceiptOrValidWitness = nextElement && (nextElement.receipt || nextElement.witnesses.some(isValidWitness)) + const nextElementHasReceiptOrValidWitness = nextElement + && (nextElement.receipt || nextElement.witnesses.some(isValidWitness)) const isFirstElement = i === 0 const isValidBeacon = isBeacon && hasValidWitness const isValidChallenge = !isBeacon @@ -157,22 +158,21 @@ const constructPath = (path: HTTPPathObject[]): Path[] => { } return { witnesses: pathObject.witnesses.map( - (witness) => - ({ - timestamp: witness.timestamp, - snr: witness.snr, - signal: witness.signal, - packetHash: witness.packet_hash, - owner: witness.owner, - location: witness.location, - locationHex: witness.location_hex, - isValid: isValidWitness(witness), - ...(witness.hasOwnProperty('invalid_reason') && { invalidReason: witness.invalid_reason }), - gateway: witness.gateway, - frequency: witness.frequency, - datarate: witness.datarate, - channel: witness.channel, - } as Witness), + (witness) => ({ + timestamp: witness.timestamp, + snr: witness.snr, + signal: witness.signal, + packetHash: witness.packet_hash, + owner: witness.owner, + location: witness.location, + locationHex: witness.location_hex, + isValid: isValidWitness(witness), + ...(Object.prototype.hasOwnProperty.call(witness, 'invalid_reason') && { invalidReason: witness.invalid_reason }), + gateway: witness.gateway, + frequency: witness.frequency, + datarate: witness.datarate, + channel: witness.channel, + } as Witness), ) as Witness[], receipt: hasReceipt ? ({ diff --git a/packages/http/src/models/OraclePricePrediction.ts b/packages/http/src/models/OraclePricePrediction.ts index 8db9ded3..b524e8a2 100644 --- a/packages/http/src/models/OraclePricePrediction.ts +++ b/packages/http/src/models/OraclePricePrediction.ts @@ -1,4 +1,3 @@ - import Balance, { CurrencyType, USDollars } from '../../../currency/build' import type Client from '../Client' import DataModel from './DataModel' diff --git a/packages/http/src/models/__tests__/City.spec.ts b/packages/http/src/models/__tests__/City.spec.ts index 88d5ac2c..dd0f6831 100644 --- a/packages/http/src/models/__tests__/City.spec.ts +++ b/packages/http/src/models/__tests__/City.spec.ts @@ -1,7 +1,6 @@ import City from '../City' import Client from '../../Client' - test('create City from HTTP response', () => { const client = new Client() const city = new City(client, { diff --git a/packages/http/src/resources/Blocks.ts b/packages/http/src/resources/Blocks.ts index 36b5a138..de61cda2 100644 --- a/packages/http/src/resources/Blocks.ts +++ b/packages/http/src/resources/Blocks.ts @@ -27,7 +27,7 @@ export default class Blocks { } if (typeof heightOrHash === 'string') { if (stringIsInt(heightOrHash)) { - return new Block(this.client, { height: parseInt(heightOrHash) }) + return new Block(this.client, { height: parseInt(heightOrHash, 10) }) } return new Block(this.client, { hash: heightOrHash }) } @@ -48,7 +48,7 @@ export default class Blocks { url = `/blocks/${heightOrHash}` } else if (typeof heightOrHash === 'string') { if (stringIsInt(heightOrHash)) { - url = `/blocks/${parseInt(heightOrHash)}` + url = `/blocks/${parseInt(heightOrHash, 10)}` } else { url = `/blocks/hash/${heightOrHash}` } diff --git a/packages/http/src/resources/Transactions.ts b/packages/http/src/resources/Transactions.ts index 87b58cf0..0703be9a 100644 --- a/packages/http/src/resources/Transactions.ts +++ b/packages/http/src/resources/Transactions.ts @@ -1,3 +1,5 @@ +import camelcaseKeys from 'camelcase-keys' +import snakecaseKeys from 'snakecase-keys' import type Client from '../Client' import type { AnyTransaction, TxnJsonObject } from '../models/Transaction' import Block from '../models/Block' @@ -7,8 +9,6 @@ import PendingTransaction from '../models/PendingTransaction' import ResourceList from '../ResourceList' import Hotspot from '../models/Hotspot' import Validator from '../models/Validator' -import camelcaseKeys from 'camelcase-keys' -import snakecaseKeys from 'snakecase-keys' export type NaturalDate = string // in the format "-${number} ${Bucket}" eg "-1 day" diff --git a/packages/http/src/resources/__tests__/Accounts.spec.ts b/packages/http/src/resources/__tests__/Accounts.spec.ts index 46e7799f..b6d2b900 100644 --- a/packages/http/src/resources/__tests__/Accounts.spec.ts +++ b/packages/http/src/resources/__tests__/Accounts.spec.ts @@ -143,6 +143,7 @@ describe('list', () => { const addresses = [] + // eslint-disable-next-line no-restricted-syntax for await (const account of accounts) { addresses.push(account.address) } diff --git a/packages/http/src/resources/__tests__/Challenges.spec.ts b/packages/http/src/resources/__tests__/Challenges.spec.ts index 9c33c8df..8f3a0f80 100644 --- a/packages/http/src/resources/__tests__/Challenges.spec.ts +++ b/packages/http/src/resources/__tests__/Challenges.spec.ts @@ -4,6 +4,7 @@ import Client from '../../Client' Address.fromB58 = jest.fn(() => new Address(0, NetTypes.MAINNET, 0, new Uint8Array())) +// eslint-disable-next-line import/prefer-default-export export const challengeFixture = (params = {}) => ({ type: 'poc_receipts_v1', time: 1589918979, diff --git a/packages/http/tsconfig.json b/packages/http/tsconfig.json index 5dd43feb..fb01644e 100644 --- a/packages/http/tsconfig.json +++ b/packages/http/tsconfig.json @@ -30,9 +30,7 @@ "src/**/*" ], "exclude": [ - "__tests__", - "src/**/*.spec.ts", - "src/**/*.js" + "node_modules", ], "jsdoc": { "out": "support/jsdoc", diff --git a/packages/http/yarn.lock b/packages/http/yarn.lock index 89e15e9d..521bb93f 100644 --- a/packages/http/yarn.lock +++ b/packages/http/yarn.lock @@ -2,22 +2,6 @@ # yarn lockfile v1 -"@helium/crypto@^3.45.0": - version "3.45.0" - resolved "https://registry.yarnpkg.com/@helium/crypto/-/crypto-3.45.0.tgz#2892aaac8f35865cd32a91222fd5ffbe9f973e08" - integrity sha512-J89U4GsLLjODPEPTD5m6q6w6zjbtJjYTxrXAF+Sp+Aqq9nPSOTvQ4gx12dfKXfPT/Rq1vrZ5/CIVXYPhQ7PTJQ== - dependencies: - bs58 "^4.0.1" - create-hash "^1.2.0" - libsodium-wrappers "^0.7.6" - -"@helium/currency@^3.45.0": - version "3.45.0" - resolved "https://registry.yarnpkg.com/@helium/currency/-/currency-3.45.0.tgz#326933225047601f447209fb9c77c6d4b114c381" - integrity sha512-6hajnB+L5OQgbQJyGzzYrHlcpRnt1j9thjt+zKhLP672VgfqJWaBs5unI+yhqHvF3JPB2/DsLj3/oT4hL0z84A== - dependencies: - bignumber.js "^9.0.0" - "@types/qs@6.9.5": version "6.9.5" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" @@ -30,25 +14,6 @@ axios@^0.21.1: dependencies: follow-redirects "^1.10.0" -base-x@^3.0.2: - version "3.0.9" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" - integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== - dependencies: - safe-buffer "^5.0.1" - -bignumber.js@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" - integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== - -bs58@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" - integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= - dependencies: - base-x "^3.0.2" - camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" @@ -63,24 +28,12 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -cipher-base@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== +debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" + ms "2.1.2" dot-case@^3.0.4: version "3.0.4" @@ -95,31 +48,15 @@ follow-redirects@^1.10.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -libsodium-wrappers@^0.7.6: - version "0.7.9" - resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#4ffc2b69b8f7c7c7c5594a93a4803f80f6d0f346" - integrity sha512-9HaAeBGk1nKTRFRHkt7nzxqCvnkWTjn1pdjKgcUnZxj0FyOP4CnhgFhMdrFfgNsukijBGyBLpP2m2uKT1vuWhQ== - dependencies: - libsodium "^0.7.0" +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -libsodium@^0.7.0: - version "0.7.9" - resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.9.tgz#4bb7bcbf662ddd920d8795c227ae25bbbfa3821b" - integrity sha512-gfeADtR4D/CM0oRUviKBViMGXZDgnFdMKMzHsvBdqLBHd9ySi6EtYnmuhHVDDYgYpAO8eU8hEY+F8vIUAPh08A== +lodash.set@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" + integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= lower-case@^2.0.2: version "2.0.2" @@ -138,14 +75,10 @@ map-obj@^4.1.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== no-case@^3.0.4: version "3.0.4" @@ -155,6 +88,21 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +nock@^13.2.4: + version "13.2.4" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.4.tgz#43a309d93143ee5cdcca91358614e7bde56d20e1" + integrity sha512-8GPznwxcPNCH/h8B+XZcKjYPXnUV5clOKCjAqyjsiqA++MpNx9E9+t8YPp0MbThO+KauRo7aZJ1WuIZmOrT2Ug== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + lodash.set "^4.3.2" + propagate "^2.0.0" + +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + qs@^6.9.3: version "6.9.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" @@ -165,41 +113,11 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - retry-axios@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-2.1.2.tgz#78a8041bc34047a205aa4098af23f943ec58abd2" integrity sha512-MVNOizOgcM+dXK/kJCMnHPCGjp2Z0o3Ngznh7SfJkIVrqemHmDcgUEAbEHs3RyR3lbJtSyamq9k3gmgGSaow1g== -ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -sha.js@^2.4.0: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - snake-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" @@ -217,13 +135,6 @@ snakecase-keys@^5.1.0: snake-case "^3.0.4" type-fest "^2.3.4" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - tslib@^2.0.3: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" @@ -233,8 +144,3 @@ type-fest@^2.3.4: version "2.5.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.5.1.tgz#17ba4f36a6abfabf0a92005d045dca77564607b0" integrity sha512-JDcsxbLR6Z6OcL7TnGAAAGQrY4g7Q4EEALMT4Kp6FQuIc0JLQvOF3l7ejFvx8o5GmLlfMseTWUL++sYFP+o8kw== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= diff --git a/packages/transactions/src/AddGatewayV1.ts b/packages/transactions/src/AddGatewayV1.ts index 275ea255..5c037643 100644 --- a/packages/transactions/src/AddGatewayV1.ts +++ b/packages/transactions/src/AddGatewayV1.ts @@ -1,6 +1,8 @@ import proto from '@helium/proto' import Transaction from './Transaction' -import { EMPTY_SIGNATURE, toAddressable, toNumber, toUint8Array } from './utils' +import { + EMPTY_SIGNATURE, toAddressable, toNumber, toUint8Array, +} from './utils' import { Addressable, SignableKeypair } from './types' interface AddGatewayOptions { diff --git a/packages/transactions/src/AssertLocationV1.ts b/packages/transactions/src/AssertLocationV1.ts index 7a6e884e..84f264fb 100644 --- a/packages/transactions/src/AssertLocationV1.ts +++ b/packages/transactions/src/AssertLocationV1.ts @@ -1,6 +1,8 @@ import proto from '@helium/proto' import Transaction from './Transaction' -import { toUint8Array, EMPTY_SIGNATURE, toAddressable, toNumber } from './utils' +import { + toUint8Array, EMPTY_SIGNATURE, toAddressable, toNumber, +} from './utils' import { Addressable, SignableKeypair } from './types' interface AssertLocationOptions { diff --git a/packages/transactions/src/AssertLocationV2.ts b/packages/transactions/src/AssertLocationV2.ts index 9f320e1f..02168ddd 100644 --- a/packages/transactions/src/AssertLocationV2.ts +++ b/packages/transactions/src/AssertLocationV2.ts @@ -1,6 +1,8 @@ import proto from '@helium/proto' import Transaction from './Transaction' -import { toUint8Array, EMPTY_SIGNATURE, toAddressable, toNumber } from './utils' +import { + toUint8Array, EMPTY_SIGNATURE, toAddressable, toNumber, +} from './utils' import { Addressable, SignableKeypair } from './types' interface AssertLocationOptions { diff --git a/packages/transactions/src/PaymentV2.ts b/packages/transactions/src/PaymentV2.ts index 867bbfd2..b198daa9 100644 --- a/packages/transactions/src/PaymentV2.ts +++ b/packages/transactions/src/PaymentV2.ts @@ -53,7 +53,6 @@ export default class PaymentV2 extends Transaction { serialize(): Uint8Array { const BlockchainTxn = proto.helium.blockchain_txn - const paymentV2 = this.toProto() const txn = BlockchainTxn.create({ paymentV2 }) return BlockchainTxn.encode(txn).finish() diff --git a/packages/transactions/src/TokenBurnV1.ts b/packages/transactions/src/TokenBurnV1.ts index 2fdf5406..74d73ddb 100644 --- a/packages/transactions/src/TokenBurnV1.ts +++ b/packages/transactions/src/TokenBurnV1.ts @@ -51,7 +51,6 @@ export default class TokenBurnV1 extends Transaction { serialize(): Uint8Array { const BlockchainTxn = proto.helium.blockchain_txn - const tokenBurn = this.toProto() const txn = BlockchainTxn.create({ tokenBurn }) return BlockchainTxn.encode(txn).finish() diff --git a/packages/transactions/src/__tests__/Transaction.spec.ts b/packages/transactions/src/__tests__/Transaction.spec.ts index 08116724..c4f2c9ab 100644 --- a/packages/transactions/src/__tests__/Transaction.spec.ts +++ b/packages/transactions/src/__tests__/Transaction.spec.ts @@ -32,10 +32,8 @@ describe('config', () => { describe('stringType', () => { it('returns the type of a serialized transaction', () => { - const serializedAddGwTxn = - 'CrMCCiEBHph7m4n8je5IHzLmg544qkxQb+K1g3efKHufp0dKURYSIQGySNPajUxhsIp5CIsV2et+Kx1XwECUOCUd4BBjekSeQxpAYCTigGLV8ch+5WmbbhO14L7mM2Djhidhl19b5zgE/Uo7T7j8OSa+Egir7oX3gkhs8frsUT4uNDrfi48ezN3tAiJAAqe3gcYc5sj3XWl0oUyVbHFhZSRu8gDcXV5+IeN6jwK6amQNm4clp1wR/JprHbI3kYbinzEwWIqzQs6miKWiByohAS85rAe4whjJEsnzyByyxV8UPRHvjl74cMb1+LadnbUjMkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOMC4Ag==' - const serializedAssertV2 = - 'mgLQAQo0ATEzTThkVWJ4eW1FM3h0aUFYc3pSa0dNbWV6TWhCUzhMaTd3RXNNb2pMZGI0U2R4YzR3YxI0ATE0OGQ4S1RSY0tBNUpLUGVrQmNLRmQ0S2Z2cHJ2RlJwakd0aXZodG1SbW5aOE1GWW5QMxo0ATEzTThkVWJ4eW1FM3h0aUFYc3pSa0dNbWV6TWhCUzhMaTd3RXNNb2pMZGI0U2R4YzR3YyIJsomesignaturKg2yiZ6i2F6uyKCdq26tMghsb2NhdGlvbjgBQAJIA1AFWAQ=' + const serializedAddGwTxn = 'CrMCCiEBHph7m4n8je5IHzLmg544qkxQb+K1g3efKHufp0dKURYSIQGySNPajUxhsIp5CIsV2et+Kx1XwECUOCUd4BBjekSeQxpAYCTigGLV8ch+5WmbbhO14L7mM2Djhidhl19b5zgE/Uo7T7j8OSa+Egir7oX3gkhs8frsUT4uNDrfi48ezN3tAiJAAqe3gcYc5sj3XWl0oUyVbHFhZSRu8gDcXV5+IeN6jwK6amQNm4clp1wR/JprHbI3kYbinzEwWIqzQs6miKWiByohAS85rAe4whjJEsnzyByyxV8UPRHvjl74cMb1+LadnbUjMkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOMC4Ag==' + const serializedAssertV2 = 'mgLQAQo0ATEzTThkVWJ4eW1FM3h0aUFYc3pSa0dNbWV6TWhCUzhMaTd3RXNNb2pMZGI0U2R4YzR3YxI0ATE0OGQ4S1RSY0tBNUpLUGVrQmNLRmQ0S2Z2cHJ2RlJwakd0aXZodG1SbW5aOE1GWW5QMxo0ATEzTThkVWJ4eW1FM3h0aUFYc3pSa0dNbWV6TWhCUzhMaTd3RXNNb2pMZGI0U2R4YzR3YyIJsomesignaturKg2yiZ6i2F6uyKCdq26tMghsb2NhdGlvbjgBQAJIA1AFWAQ=' expect(Transaction.stringType(serializedAddGwTxn)).toBe('addGateway') expect(Transaction.stringType(serializedAssertV2)).toBe('assertLocationV2')