diff --git a/modules/sdk-coin-icp/src/icp.ts b/modules/sdk-coin-icp/src/icp.ts index 75b8303d62..2d6cf6dd70 100644 --- a/modules/sdk-coin-icp/src/icp.ts +++ b/modules/sdk-coin-icp/src/icp.ts @@ -31,6 +31,7 @@ import * as mpc from '@bitgo/sdk-lib-mpc'; import { CurveType, + IcpCertificate, LEDGER_CANISTER_ID, TESTNET_LEDGER_CANISTER_ID, PayloadsData, @@ -38,6 +39,8 @@ import { PublicNodeSubmitResponse, RecoveryOptions, RecoveryTransaction, + REQUEST_STATUS_REJECTED, + REQUEST_STATUS_REPLIED, ROOT_PATH, Signatures, SigningPayload, @@ -284,8 +287,18 @@ export class Icp extends BaseCoin { const decodedResponse = utils.cborDecode(response.data) as PublicNodeSubmitResponse; - if (decodedResponse.status === 'replied') { - // it is considered a success because ICP returns response in a CBOR map with a status of 'replied' + if (decodedResponse.status === REQUEST_STATUS_REPLIED) { + // The outer status 'replied' means the IC accepted and processed the HTTP call. + // The certificate contains the actual canister execution result. A canister can + // still reject the call (e.g. insufficient funds) even when the outer status is + // 'replied', so we must inspect the certificate's request_status subtree. + if (decodedResponse.certificate) { + const certificate = utils.cborDecode(Buffer.from(decodedResponse.certificate)) as IcpCertificate; + const { requestStatus, rejectMessage } = utils.extractRequestStatusFromCertificate(certificate); + if (requestStatus === REQUEST_STATUS_REJECTED) { + throw new Error(`Transaction rejected by canister: ${rejectMessage ?? 'unknown reason'}`); + } + } return {}; // returned empty object as ICP does not return a txid } else { throw new Error(`Unexpected response status from node: ${decodedResponse.status}`); diff --git a/modules/sdk-coin-icp/src/lib/iface.ts b/modules/sdk-coin-icp/src/lib/iface.ts index f1a83621d6..e3440d271c 100644 --- a/modules/sdk-coin-icp/src/lib/iface.ts +++ b/modules/sdk-coin-icp/src/lib/iface.ts @@ -4,6 +4,9 @@ import { TssVerifyAddressOptions, } from '@bitgo/sdk-core'; +export const REQUEST_STATUS_REPLIED = 'replied'; +export const REQUEST_STATUS_REJECTED = 'rejected'; + export const MAX_INGRESS_TTL = 5 * 60 * 1000_000_000; // 5 minutes in nanoseconds export const PERMITTED_DRIFT = 60 * 1000_000_000; // 60 seconds in nanoseconds export const LEDGER_CANISTER_ID = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2, 1, 1]); // Uint8Array value for "00000000000000020101" and the string value is "ryjl3-tyaaa-aaaaa-aaaba-cai" @@ -207,7 +210,26 @@ export interface UnsignedSweepRecoveryTransaction { export interface PublicNodeSubmitResponse { status: string; -} + certificate?: Uint8Array; +} + +export interface IcpCertificate { + tree: IcpHashTree; + signature: Uint8Array; +} + +// ICP labeled hash tree node types per the IC HTTP spec: +// [0] = Empty +// [1, left, right] = Fork +// [2, label, subtree] = Labeled +// [3, value] = Leaf +// [4, hash] = Pruned +export type IcpHashTree = + | [0] + | [1, IcpHashTree, IcpHashTree] + | [2, Uint8Array, IcpHashTree] + | [3, Uint8Array] + | [4, Uint8Array]; export interface AccountIdentifierHash { hash: Buffer; diff --git a/modules/sdk-coin-icp/src/lib/utils.ts b/modules/sdk-coin-icp/src/lib/utils.ts index 3cb81f0dc8..765a3fdd25 100644 --- a/modules/sdk-coin-icp/src/lib/utils.ts +++ b/modules/sdk-coin-icp/src/lib/utils.ts @@ -21,6 +21,8 @@ import { CurveType, AccountIdentifierHash, CborUnsignedTransaction, + IcpCertificate, + IcpHashTree, MAX_INGRESS_TTL, } from './iface'; import { KeyPair as IcpKeyPair } from './keyPair'; @@ -774,6 +776,143 @@ export class Utils implements BaseUtils { } throw new Error('Invalid accountHash format'); } + + /** + * Looks up a path in an ICP labeled hash tree. + * + * The ICP state tree uses a specific CBOR array encoding: + * [0] = Empty + * [1, l, r] = Fork + * [2, label, t] = Labeled + * [3, value] = Leaf + * [4, hash] = Pruned + * + * @param tree The root node of the ICP labeled hash tree. + * @param path Ordered path segments (as Buffers or strings). + * @returns The leaf value as a Buffer, or undefined if not found. + */ + lookupPathInTree(tree: IcpHashTree, path: (Buffer | string)[]): Buffer | undefined { + if (path.length === 0) { + if (tree[0] === 3) { + return Buffer.from(tree[1] as Uint8Array); + } + return undefined; + } + + const [head, ...rest] = path; + const labelBytes = typeof head === 'string' ? Buffer.from(head, 'utf8') : head; + + return this.findLabeledSubtree(tree, labelBytes, rest); + } + + private findLabeledSubtree(tree: IcpHashTree, label: Buffer, remainingPath: (Buffer | string)[]): Buffer | undefined { + const nodeType = tree[0]; + + if (nodeType === 1) { + // Fork: search both sides + const left = this.findLabeledSubtree(tree[1] as IcpHashTree, label, remainingPath); + if (left !== undefined) return left; + return this.findLabeledSubtree(tree[2] as IcpHashTree, label, remainingPath); + } + + if (nodeType === 2) { + // Labeled: check if this label matches + const nodeLabel = Buffer.from(tree[1] as Uint8Array); + if (nodeLabel.equals(label)) { + return this.lookupPathInTree(tree[2] as IcpHashTree, remainingPath); + } + } + + return undefined; + } + + /** + * Extracts the request status from an ICP v3 API call response certificate. + * + * For a `/api/v3/canister/call` response where the outer status is 'replied', + * the certificate tree contains the actual request outcome at the path + * ["request_status", , "status"]. + * + * @param certificate The decoded ICP certificate object. + * @returns Object containing the request status and optional reject message. + */ + extractRequestStatusFromCertificate(certificate: IcpCertificate): { + requestStatus: string; + rejectMessage?: string; + rejectCode?: number; + } { + const tree = certificate.tree; + + // The status node is nested under ["request_status", , "status"]. + // We don't know the request_id in advance, so we search the tree for any "status" + // leaf inside the "request_status" subtree, skipping the intermediate request_id level. + const statusFromSubtree = this.searchForLeafInSubtree(tree, 'request_status', 'status'); + if (statusFromSubtree === undefined) { + // No request_status/status found — treat as unknown (not a rejection) + return { requestStatus: 'unknown' }; + } + + const requestStatus = statusFromSubtree.toString('utf8'); + + if (requestStatus === 'rejected') { + const rejectMsg = this.searchForLeafInSubtree(tree, 'request_status', 'reject_message'); + const rejectCodeBuf = this.searchForLeafInSubtree(tree, 'request_status', 'reject_code'); + return { + requestStatus, + rejectMessage: rejectMsg?.toString('utf8'), + rejectCode: rejectCodeBuf ? rejectCodeBuf.readUIntBE(0, rejectCodeBuf.length) : undefined, + }; + } + + return { requestStatus }; + } + + /** + * Searches for a labeled leaf nested inside a named outer path segment. + * Used to traverse the request_status subtree without knowing the intermediate + * request_id hash key. + */ + private searchForLeafInSubtree(tree: IcpHashTree, outerLabel: string, innerLabel: string): Buffer | undefined { + const outer = Buffer.from(outerLabel, 'utf8'); + const inner = Buffer.from(innerLabel, 'utf8'); + return this.findNestedLabel(tree, outer, inner, false); + } + + private findNestedLabel( + tree: IcpHashTree, + outerLabel: Buffer, + innerLabel: Buffer, + insideOuter: boolean + ): Buffer | undefined { + const nodeType = tree[0]; + + if (nodeType === 1) { + // Fork + const left = this.findNestedLabel(tree[1] as IcpHashTree, outerLabel, innerLabel, insideOuter); + if (left !== undefined) return left; + return this.findNestedLabel(tree[2] as IcpHashTree, outerLabel, innerLabel, insideOuter); + } + + if (nodeType === 2) { + const nodeLabel = Buffer.from(tree[1] as Uint8Array); + if (!insideOuter && nodeLabel.equals(outerLabel)) { + // Entered the outer subtree — now search for innerLabel anywhere inside + return this.findNestedLabel(tree[2] as IcpHashTree, outerLabel, innerLabel, true); + } + if (insideOuter && nodeLabel.equals(innerLabel)) { + // Found the inner label inside the outer subtree + const subtree = tree[2] as IcpHashTree; + if (subtree[0] === 3) { + return Buffer.from(subtree[1] as Uint8Array); + } + return undefined; + } + // Continue searching inside same subtree + return this.findNestedLabel(tree[2] as IcpHashTree, outerLabel, innerLabel, insideOuter); + } + + return undefined; + } } const utils = new Utils(); diff --git a/modules/sdk-coin-icp/test/resources/icp.ts b/modules/sdk-coin-icp/test/resources/icp.ts index 554668cd95..48ebf4c313 100644 --- a/modules/sdk-coin-icp/test/resources/icp.ts +++ b/modules/sdk-coin-icp/test/resources/icp.ts @@ -376,9 +376,16 @@ export const TxnIdWithDefaultMemo = 'a8c20ea0d9c984400f01b7f9d5325773862751c3277 export const TxnIdWithMemo = 'ac2157cdca60e8c54c127434893dc6f65548aefa0603d3f3232f01ec8f2e0c82'; -export const PublicNodeApiBroadcastResponse = +// CBOR-encoded ICP v3 /call response where the outer status is 'replied' but the +// certificate's request_status tree shows 'rejected' (e.g. insufficient funds). +// Used to verify that broadcastTransaction correctly surfaces canister rejections. +export const PublicNodeApiBroadcastResponseRejected = 'd9d9f7a266737461747573677265706c6965646b6365727469666963617465590317d9d9f7a2647472656583018301820458200548bee8d4a0ada144f09841a1814e0c7b06a9c525e19d07990c27a8d5dfebe983018204582059964f7dcd455bb5333f0424188250c1bafdfee505b6d4bffce6b93d84b02fbc83024e726571756573745f7374617475738301820458208ec4679d2b1049ab4154b9113c88a7bacc1772831a799d0337e650fe093339648302582086ab628166c7c823c4fac064f42bcb6c6939aec9a63ce88a2eef553475fd55958301830183024a6572726f725f636f646582034649433035303383024b72656a6563745f636f646582034105830183024e72656a6563745f6d65737361676582035901984572726f722066726f6d2043616e69737465722072796a6c332d74796161612d61616161612d61616162612d6361693a2043616e69737465722063616c6c656420606963302e74726170602077697468206d6573736167653a20746865206465626974206163636f756e7420646f65736e2774206861766520656e6f7567682066756e647320746f20636f6d706c65746520746865207472616e73616374696f6e2c2063757272656e742062616c616e63653a20302e303030303030303020546f6b656e2e0a436f6e736964657220677261636566756c6c792068616e646c696e67206661696c757265732066726f6d20746869732063616e6973746572206f7220616c746572696e67207468652063616e697374657220746f2068616e646c6520657863657074696f6e732e2053656520646f63756d656e746174696f6e3a20687474703a2f2f696e7465726e6574636f6d70757465722e6f72672f646f63732f63757272656e742f7265666572656e6365732f657865637574696f6e2d6572726f727323747261707065642d6578706c696369746c7983024673746174757382034872656a6563746564830182045820535b0b886d8f16a1d4909618e38af89fa407ef6f606c1e667ab31015f35db57f83024474696d658203498ad7d790e682f69918697369676e61747572655830a26f5ae0c1396f6005487223031062886416c7d4ea37a1b2a7b64a287b376a5f4495cffd99a69bf4f4fd95e53794f415'; +// CBOR-encoded ICP v3 /call success response: status='replied' with no certificate field. +// Represents a synchronous canister call that was accepted and completed successfully. +export const PublicNodeApiBroadcastResponse = 'd9d9f7a166737461747573677265706c696564'; + export const RecoverySignedTransactionWithDefaultMemo = 'b9000367636f6e74656e74b900066c726571756573745f747970656463616c6c6b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f70626361726758430a02080012080a0608f0c5eadc031a0308904e2a220a20c3d30f404955975adaba89f2e1ebc75c1f44a6a204578afce8f3780d64fe252e3a0a0880a48596eb92b599186673656e646572581db32b534dcc87771406ce0d93595733ca9c635f0f2f3e29559e8806a1026e696e67726573735f6578706972791b1832d4ce93deb2006d73656e6465725f7075626b6579d84058583056301006072a8648ce3d020106052b8104000a0342000482ef63ee315d6323c616970c93267ca92ee81a691027f9c856ccd23f414f32c5209b7b6f64f42e66c9c5adf064cf0f372ae1b27c9a0fcbc4dcaa8fb5cd5ba0e66a73656e6465725f7369675840cd8f6cde4f72597767b740c3eae6bc8374afa73fd099a474c7d02e60dee5b3f3239829c223f0aec28ca6fe28d735068b7ab77bef6ef89f0a7307e9fdb5f15d09'; diff --git a/modules/sdk-coin-icp/test/unit/transactionBuilder/transactionRecover.ts b/modules/sdk-coin-icp/test/unit/transactionBuilder/transactionRecover.ts index e0ede7f526..babd9f162b 100644 --- a/modules/sdk-coin-icp/test/unit/transactionBuilder/transactionRecover.ts +++ b/modules/sdk-coin-icp/test/unit/transactionBuilder/transactionRecover.ts @@ -148,6 +148,13 @@ describe('ICP transaction recovery', async () => { ); }); + it('should fail to recover if canister rejects the transaction (e.g. insufficient funds)', async () => { + nock.cleanAll(); + const rejectedResponse = Buffer.from(testData.PublicNodeApiBroadcastResponseRejected, 'hex'); + nock(nodeUrl).post(broadcastEndpoint).reply(200, rejectedResponse); + await icp.recover(recoveryParams).should.rejectedWith(/Transaction rejected by canister/); + }); + it('should fail to recover txn if balance is low', async () => { sinon.restore(); setupLowBalanceStubs(); diff --git a/yarn.lock b/yarn.lock index e78d7ef1f2..536d661695 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8055,23 +8055,23 @@ bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz#16a9e409616b23fef3ccbedb8d42f13bff80295e" integrity sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w== -body-parser@1.20.3, body-parser@1.20.6, body-parser@^1.19.0, body-parser@^1.20.3: - version "1.20.6" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76" - integrity sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g== +body-parser@1.20.3, body-parser@^1.19.0, body-parser@^1.20.3: + version "1.20.3" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: - bytes "~3.1.2" + bytes "3.1.2" content-type "~1.0.5" debug "2.6.9" depd "2.0.0" - destroy "~1.2.0" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - on-finished "~2.4.1" - qs "~6.15.1" - raw-body "~2.5.3" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.13.0" + raw-body "2.5.2" type-is "~1.6.18" - unpipe "~1.0.0" + unpipe "1.0.0" bonjour-service@^1.2.1: version "1.3.0" @@ -8411,7 +8411,7 @@ bytebuffer@5.0.1: dependencies: long "~3" -bytes@3.1.2, bytes@~3.1.2: +bytes@3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== @@ -10097,7 +10097,7 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destroy@1.2.0, destroy@~1.2.0: +destroy@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== @@ -12790,17 +12790,6 @@ http-errors@~1.6.1, http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-errors@~2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" - integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== - dependencies: - depd "~2.0.0" - inherits "~2.0.4" - setprototypeof "~1.2.0" - statuses "~2.0.2" - toidentifier "~1.0.1" - http-parser-js@>=0.5.1: version "0.5.10" resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz" @@ -12929,6 +12918,13 @@ ic0@^0.3.2: "@dfinity/principal" "^2.1.3" cross-fetch "^3.1.5" +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" @@ -12943,13 +12939,6 @@ iconv-lite@^0.7.0, iconv-lite@^0.7.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -iconv-lite@~0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz" @@ -17477,10 +17466,10 @@ propagate@^2.0.0: resolved "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz" integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== -protobufjs@7.2.5, protobufjs@7.6.5, protobufjs@^6.8.8, protobufjs@^7.5.5, protobufjs@^7.5.8, protobufjs@~6.11.2, protobufjs@~6.11.3: - version "7.6.5" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz#7b9250cdaf4a06139a9f0fe468a40d7d4febca71" - integrity sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw== +protobufjs@7.2.5, protobufjs@7.6.4, protobufjs@^6.8.8, protobufjs@^7.5.5, protobufjs@^7.5.8, protobufjs@~6.11.2, protobufjs@~6.11.3: + version "7.6.4" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz#8bb000300026efd63eb7951d26e5dbb38f5658f2" + integrity sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -17654,7 +17643,7 @@ qrcode@^1.5.1: pngjs "^5.0.0" yargs "^15.3.1" -qs@6.13.0, qs@6.14.0, qs@6.15.2, qs@^6.11.0, qs@^6.11.2, qs@^6.12.3, qs@^6.5.1, qs@~6.15.1: +qs@6.13.0, qs@6.14.0, qs@6.15.2, qs@^6.11.0, qs@^6.11.2, qs@^6.12.3, qs@^6.5.1: version "6.15.2" resolved "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz#fd55426d710403ddccc45e0f9eab16db7727ece9" integrity sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw== @@ -17725,15 +17714,15 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@~2.5.3: - version "2.5.3" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" - integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== dependencies: - bytes "~3.1.2" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - unpipe "~1.0.0" + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" react-base16-styling@^0.6.0: version "0.6.0" @@ -18685,7 +18674,7 @@ setprototypeof@1.1.1: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== -setprototypeof@1.2.0, setprototypeof@~1.2.0: +setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== @@ -19275,11 +19264,6 @@ statuses@2.0.1: resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -statuses@~2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" - integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== - stellar-base@^8.2.2: version "8.2.2" resolved "https://registry.npmjs.org/stellar-base/-/stellar-base-8.2.2.tgz" @@ -20069,7 +20053,7 @@ toidentifier@1.0.0: resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -toidentifier@1.0.1, toidentifier@~1.0.1: +toidentifier@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== @@ -20586,9 +20570,9 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unpipe@~1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== untildify@^4.0.0: