diff --git a/.changeset/blue-suns-accept.md b/.changeset/blue-suns-accept.md new file mode 100644 index 000000000..7771344ad --- /dev/null +++ b/.changeset/blue-suns-accept.md @@ -0,0 +1,5 @@ +--- +"@onflow/kit": patch +--- + +Make `useFlowChainId` args consistent with other hooks diff --git a/package-lock.json b/package-lock.json index e82c146b2..fe68196cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9161,34 +9161,8 @@ "react-native": "*" } }, - "node_modules/@react-stately/flags": { - "version": "3.1.1", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, - "node_modules/@react-stately/utils": { - "version": "3.10.6", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-types/shared": { - "version": "3.29.0", - "license": "Apache-2.0", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, "node_modules/@reown/appkit": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@reown/appkit/-/appkit-1.7.3.tgz", - "integrity": "sha512-aA/UIwi/dVzxEB62xlw3qxHa3RK1YcPMjNxoGj/fHNCqL2qWmbcOXT7coCUa9RG7/Bh26FZ3vdVT2v71j6hebQ==", "license": "Apache-2.0", "dependencies": { "@reown/appkit-common": "1.7.3", @@ -9207,8 +9181,6 @@ }, "node_modules/@reown/appkit-common": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@reown/appkit-common/-/appkit-common-1.7.3.tgz", - "integrity": "sha512-wKTr6N3z8ly17cc51xBEVkZK4zAd8J1m7RubgsdQ1olFY9YJGe61RYoNv9yFjt6tUVeYT+z7iMUwPhX2PziefQ==", "license": "Apache-2.0", "dependencies": { "big.js": "6.2.2", @@ -9218,8 +9190,6 @@ }, "node_modules/@reown/appkit-common/node_modules/@noble/curves": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", - "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", "license": "MIT", "dependencies": { "@noble/hashes": "1.7.2" @@ -9233,8 +9203,6 @@ }, "node_modules/@reown/appkit-common/node_modules/@noble/hashes": { "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -9245,29 +9213,10 @@ }, "node_modules/@reown/appkit-common/node_modules/eventemitter3": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/@reown/appkit-common/node_modules/isows": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, "node_modules/@reown/appkit-common/node_modules/ox": { "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", "funding": [ { "type": "github", @@ -9294,9 +9243,7 @@ } }, "node_modules/@reown/appkit-common/node_modules/viem": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.30.0.tgz", - "integrity": "sha512-hvO4l5JIOnYPL8imULoFQiVTSkebIqzGHmIfsdMfIHpAgBaCx8rJJH9cXAxQeWCqsFuTmjEj1cX912N7HSCgpQ==", + "version": "2.29.0", "funding": [ { "type": "github", @@ -9310,7 +9257,7 @@ "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", - "isows": "1.0.7", + "isows": "1.0.6", "ox": "0.6.9", "ws": "8.18.1" }, @@ -9325,8 +9272,6 @@ }, "node_modules/@reown/appkit-common/node_modules/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -9346,8 +9291,6 @@ }, "node_modules/@reown/appkit-controllers": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@reown/appkit-controllers/-/appkit-controllers-1.7.3.tgz", - "integrity": "sha512-aqAcX/nZe0gwqjncyCkVrAk3lEw0qZ9xGrdLOmA207RreO4J0Vxu8OJXCBn4C2AUI2OpBxCPah+vyuKTUJTeHQ==", "license": "Apache-2.0", "dependencies": { "@reown/appkit-common": "1.7.3", @@ -9359,8 +9302,6 @@ }, "node_modules/@reown/appkit-controllers/node_modules/@noble/curves": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", - "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", "license": "MIT", "dependencies": { "@noble/hashes": "1.7.2" @@ -9374,8 +9315,6 @@ }, "node_modules/@reown/appkit-controllers/node_modules/@noble/hashes": { "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -9386,29 +9325,10 @@ }, "node_modules/@reown/appkit-controllers/node_modules/eventemitter3": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/@reown/appkit-controllers/node_modules/isows": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, "node_modules/@reown/appkit-controllers/node_modules/ox": { "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", "funding": [ { "type": "github", @@ -9436,14 +9356,10 @@ }, "node_modules/@reown/appkit-controllers/node_modules/proxy-compare": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.6.0.tgz", - "integrity": "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw==", "license": "MIT" }, "node_modules/@reown/appkit-controllers/node_modules/use-sync-external-store": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" @@ -9451,8 +9367,6 @@ }, "node_modules/@reown/appkit-controllers/node_modules/valtio": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", - "integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==", "license": "MIT", "dependencies": { "derive-valtio": "0.1.0", @@ -9476,9 +9390,7 @@ } }, "node_modules/@reown/appkit-controllers/node_modules/viem": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.30.0.tgz", - "integrity": "sha512-hvO4l5JIOnYPL8imULoFQiVTSkebIqzGHmIfsdMfIHpAgBaCx8rJJH9cXAxQeWCqsFuTmjEj1cX912N7HSCgpQ==", + "version": "2.29.0", "funding": [ { "type": "github", @@ -9492,7 +9404,7 @@ "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", - "isows": "1.0.7", + "isows": "1.0.6", "ox": "0.6.9", "ws": "8.18.1" }, @@ -9507,8 +9419,6 @@ }, "node_modules/@reown/appkit-controllers/node_modules/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -9528,8 +9438,6 @@ }, "node_modules/@reown/appkit-polyfills": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@reown/appkit-polyfills/-/appkit-polyfills-1.7.3.tgz", - "integrity": "sha512-vQUiAyI7WiNTUV4iNwv27iigdeg8JJTEo6ftUowIrKZ2/gtE2YdMtGpavuztT/qrXhrIlTjDGp5CIyv9WOTu4g==", "license": "Apache-2.0", "dependencies": { "buffer": "6.0.3" @@ -9537,8 +9445,6 @@ }, "node_modules/@reown/appkit-scaffold-ui": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@reown/appkit-scaffold-ui/-/appkit-scaffold-ui-1.7.3.tgz", - "integrity": "sha512-ssB15fcjmoKQ+VfoCo7JIIK66a4SXFpCH8uK1CsMmXmKIKqPN54ohLo291fniV6mKtnJxh5Xm68slGtGrO3bmA==", "license": "Apache-2.0", "dependencies": { "@reown/appkit-common": "1.7.3", @@ -9551,8 +9457,6 @@ }, "node_modules/@reown/appkit-scaffold-ui/node_modules/@lit/reactive-element": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.0.tgz", - "integrity": "sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA==", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0" @@ -9560,8 +9464,6 @@ }, "node_modules/@reown/appkit-scaffold-ui/node_modules/lit": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.0.tgz", - "integrity": "sha512-rzo/hmUqX8zmOdamDAeydfjsGXbbdtAFqMhmocnh2j9aDYqbu0fjXygjCa0T99Od9VQ/2itwaGrjZz/ZELVl7w==", "license": "BSD-3-Clause", "dependencies": { "@lit/reactive-element": "^2.0.0", @@ -9571,8 +9473,6 @@ }, "node_modules/@reown/appkit-scaffold-ui/node_modules/lit-element": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.0.tgz", - "integrity": "sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0", @@ -9582,8 +9482,6 @@ }, "node_modules/@reown/appkit-scaffold-ui/node_modules/lit-html": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", - "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==", "license": "BSD-3-Clause", "dependencies": { "@types/trusted-types": "^2.0.2" @@ -9591,8 +9489,6 @@ }, "node_modules/@reown/appkit-ui": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@reown/appkit-ui/-/appkit-ui-1.7.3.tgz", - "integrity": "sha512-zKmFIjLp0X24pF9KtPtSHmdsh/RjEWIvz+faIbPGm4tQbwcxdg9A35HeoP0rMgKYx49SX51LgPwVXne2gYacqQ==", "license": "Apache-2.0", "dependencies": { "@reown/appkit-common": "1.7.3", @@ -9604,8 +9500,6 @@ }, "node_modules/@reown/appkit-ui/node_modules/@lit/reactive-element": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.0.tgz", - "integrity": "sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA==", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0" @@ -9613,8 +9507,6 @@ }, "node_modules/@reown/appkit-ui/node_modules/lit": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.0.tgz", - "integrity": "sha512-rzo/hmUqX8zmOdamDAeydfjsGXbbdtAFqMhmocnh2j9aDYqbu0fjXygjCa0T99Od9VQ/2itwaGrjZz/ZELVl7w==", "license": "BSD-3-Clause", "dependencies": { "@lit/reactive-element": "^2.0.0", @@ -9624,8 +9516,6 @@ }, "node_modules/@reown/appkit-ui/node_modules/lit-element": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.0.tgz", - "integrity": "sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0", @@ -9635,8 +9525,6 @@ }, "node_modules/@reown/appkit-ui/node_modules/lit-html": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", - "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==", "license": "BSD-3-Clause", "dependencies": { "@types/trusted-types": "^2.0.2" @@ -9644,8 +9532,6 @@ }, "node_modules/@reown/appkit-utils": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@reown/appkit-utils/-/appkit-utils-1.7.3.tgz", - "integrity": "sha512-8/MNhmfri+2uu8WzBhZ5jm5llofOIa1dyXDXRC/hfrmGmCFJdrQKPpuqOFYoimo2s2g70pK4PYefvOKgZOWzgg==", "license": "Apache-2.0", "dependencies": { "@reown/appkit-common": "1.7.3", @@ -9663,8 +9549,6 @@ }, "node_modules/@reown/appkit-utils/node_modules/@noble/curves": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", - "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", "license": "MIT", "dependencies": { "@noble/hashes": "1.7.2" @@ -9678,8 +9562,6 @@ }, "node_modules/@reown/appkit-utils/node_modules/@noble/hashes": { "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -9690,29 +9572,10 @@ }, "node_modules/@reown/appkit-utils/node_modules/eventemitter3": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/@reown/appkit-utils/node_modules/isows": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, "node_modules/@reown/appkit-utils/node_modules/ox": { "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", "funding": [ { "type": "github", @@ -9740,14 +9603,10 @@ }, "node_modules/@reown/appkit-utils/node_modules/proxy-compare": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.6.0.tgz", - "integrity": "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw==", "license": "MIT" }, "node_modules/@reown/appkit-utils/node_modules/use-sync-external-store": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" @@ -9755,8 +9614,6 @@ }, "node_modules/@reown/appkit-utils/node_modules/valtio": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", - "integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==", "license": "MIT", "dependencies": { "derive-valtio": "0.1.0", @@ -9780,9 +9637,7 @@ } }, "node_modules/@reown/appkit-utils/node_modules/viem": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.30.0.tgz", - "integrity": "sha512-hvO4l5JIOnYPL8imULoFQiVTSkebIqzGHmIfsdMfIHpAgBaCx8rJJH9cXAxQeWCqsFuTmjEj1cX912N7HSCgpQ==", + "version": "2.29.0", "funding": [ { "type": "github", @@ -9796,7 +9651,7 @@ "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", - "isows": "1.0.7", + "isows": "1.0.6", "ox": "0.6.9", "ws": "8.18.1" }, @@ -9811,8 +9666,6 @@ }, "node_modules/@reown/appkit-utils/node_modules/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -9832,8 +9685,6 @@ }, "node_modules/@reown/appkit-wallet": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@reown/appkit-wallet/-/appkit-wallet-1.7.3.tgz", - "integrity": "sha512-D0pExd0QUE71ursQPp3pq/0iFrz2oz87tOyFifrPANvH5X0RQCYn/34/kXr+BFVQzNFfCBDlYP+CniNA/S0KiQ==", "license": "Apache-2.0", "dependencies": { "@reown/appkit-common": "1.7.3", @@ -9844,8 +9695,6 @@ }, "node_modules/@reown/appkit-wallet/node_modules/zod": { "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -9853,8 +9702,6 @@ }, "node_modules/@reown/appkit/node_modules/@noble/curves": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", - "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", "license": "MIT", "dependencies": { "@noble/hashes": "1.7.2" @@ -9868,8 +9715,6 @@ }, "node_modules/@reown/appkit/node_modules/@noble/hashes": { "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -9880,29 +9725,10 @@ }, "node_modules/@reown/appkit/node_modules/eventemitter3": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/@reown/appkit/node_modules/isows": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, "node_modules/@reown/appkit/node_modules/ox": { "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", "funding": [ { "type": "github", @@ -9930,14 +9756,10 @@ }, "node_modules/@reown/appkit/node_modules/proxy-compare": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.6.0.tgz", - "integrity": "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw==", "license": "MIT" }, "node_modules/@reown/appkit/node_modules/use-sync-external-store": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" @@ -9945,8 +9767,6 @@ }, "node_modules/@reown/appkit/node_modules/valtio": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", - "integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==", "license": "MIT", "dependencies": { "derive-valtio": "0.1.0", @@ -9970,9 +9790,7 @@ } }, "node_modules/@reown/appkit/node_modules/viem": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.30.0.tgz", - "integrity": "sha512-hvO4l5JIOnYPL8imULoFQiVTSkebIqzGHmIfsdMfIHpAgBaCx8rJJH9cXAxQeWCqsFuTmjEj1cX912N7HSCgpQ==", + "version": "2.29.0", "funding": [ { "type": "github", @@ -9986,7 +9804,7 @@ "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", - "isows": "1.0.7", + "isows": "1.0.6", "ox": "0.6.9", "ws": "8.18.1" }, @@ -10001,8 +9819,6 @@ }, "node_modules/@reown/appkit/node_modules/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -10993,8 +10809,6 @@ }, "node_modules/@types/prop-types": { "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", "dev": true, "license": "MIT" }, @@ -11020,8 +10834,6 @@ }, "node_modules/@types/scheduler": { "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", "dev": true, "license": "MIT" }, @@ -13086,8 +12898,6 @@ }, "node_modules/big.js": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", - "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", "license": "MIT", "engines": { "node": "*" @@ -14911,8 +14721,6 @@ }, "node_modules/derive-valtio": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/derive-valtio/-/derive-valtio-0.1.0.tgz", - "integrity": "sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A==", "license": "MIT", "peerDependencies": { "valtio": "*" @@ -20891,8 +20699,6 @@ }, "node_modules/jest-websocket-mock": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.5.0.tgz", - "integrity": "sha512-a+UJGfowNIWvtIKIQBHoEWIUqRxxQHFx4CXT+R5KxxKBtEQ5rS3pPOV/5299sHzqbmeCzxxY5qE4+yfXePePig==", "dev": true, "license": "MIT", "dependencies": { @@ -23670,8 +23476,6 @@ }, "node_modules/mock-socket": { "version": "9.3.1", - "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", - "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", "dev": true, "license": "MIT", "engines": { @@ -26552,8 +26356,6 @@ }, "node_modules/query-string": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", - "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", "license": "MIT", "dependencies": { "decode-uri-component": "^0.2.2", @@ -28667,8 +28469,6 @@ }, "node_modules/strict-uri-encode": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", "license": "MIT", "engines": { "node": ">=4" @@ -28895,7 +28695,6 @@ "node_modules/sucrase": { "version": "3.34.0", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -28916,7 +28715,6 @@ "node_modules/sucrase/node_modules/brace-expansion": { "version": "1.1.11", "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -28925,7 +28723,6 @@ "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", "license": "MIT", - "peer": true, "engines": { "node": ">= 6" } @@ -28933,7 +28730,6 @@ "node_modules/sucrase/node_modules/glob": { "version": "7.1.6", "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -28951,13 +28747,11 @@ }, "node_modules/sucrase/node_modules/lines-and-columns": { "version": "1.2.4", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/sucrase/node_modules/minimatch": { "version": "3.1.2", "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -29057,6 +28851,16 @@ "version": "6.2.0", "license": "MIT" }, + "node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.17", "license": "MIT", @@ -29167,8 +28971,8 @@ } } }, - "node_modules/tailwindcss/node_modules/sucrase": { - "version": "3.35.0", + "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.3", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -31292,16 +31096,16 @@ }, "packages/fcl": { "name": "@onflow/fcl", - "version": "1.18.0-alpha.1", + "version": "1.18.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.2", - "@onflow/fcl-core": "1.19.0-alpha.1", - "@onflow/fcl-wc": "6.0.3-alpha.1", + "@onflow/fcl-core": "1.19.0", + "@onflow/fcl-wc": "6.0.3", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.9.0-alpha.1", + "@onflow/sdk": "1.9.0", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", @@ -31319,7 +31123,7 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/typedefs": "1.6.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", @@ -31367,7 +31171,7 @@ }, "packages/fcl-core": { "name": "@onflow/fcl-core", - "version": "1.19.0-alpha.1", + "version": "1.19.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -31375,8 +31179,8 @@ "@onflow/config": "1.5.2", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.9.0-alpha.1", - "@onflow/transport-http": "1.13.0-alpha.0", + "@onflow/sdk": "1.9.0", + "@onflow/transport-http": "1.13.0", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", @@ -31390,7 +31194,7 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/typedefs": "1.6.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", @@ -31422,14 +31226,14 @@ }, "packages/fcl-ethereum-provider": { "name": "@onflow/fcl-ethereum-provider", - "version": "0.0.5-alpha.1", + "version": "0.0.5", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", - "@onflow/fcl-wc": "6.0.3-alpha.1", + "@onflow/fcl-wc": "6.0.3", "@onflow/rlp": "^1.2.3", "@walletconnect/ethereum-provider": "^2.20.2", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -31441,7 +31245,7 @@ "devDependencies": { "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "^1.6.0-alpha.0", + "@onflow/typedefs": "^1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -31450,13 +31254,11 @@ "jest": "^29.7.0" }, "peerDependencies": { - "@onflow/fcl": "1.18.0-alpha.1" + "@onflow/fcl": "1.18.0" } }, "packages/fcl-ethereum-provider/node_modules/@react-native-async-storage/async-storage": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", - "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", "license": "MIT", "optional": true, "peer": true, @@ -31468,9 +31270,7 @@ } }, "packages/fcl-ethereum-provider/node_modules/@walletconnect/core": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.20.3.tgz", - "integrity": "sha512-2xMopmR6Inx4d4PIwChnfQxdpb828WAwXWj+DodcDpt1aqusknVfkoDtx/PL1+KyqEbyiUdlW09krjnRvT82KQ==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/heartbeat": "1.2.2", @@ -31484,8 +31284,8 @@ "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.20.3", - "@walletconnect/utils": "2.20.3", + "@walletconnect/types": "2.20.2", + "@walletconnect/utils": "2.20.2", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", @@ -31496,9 +31296,7 @@ } }, "packages/fcl-ethereum-provider/node_modules/@walletconnect/ethereum-provider": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.20.3.tgz", - "integrity": "sha512-LwgrKEjoOg+6lb3JnZ3iIetAarW1TbsTl60IL5USTHiY3eThMq3YIhkB2FlxJI1zJ5rDSFmlB1KM91lo/zN5gA==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { "@reown/appkit": "1.7.3", @@ -31507,17 +31305,15 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/sign-client": "2.20.3", - "@walletconnect/types": "2.20.3", - "@walletconnect/universal-provider": "2.20.3", - "@walletconnect/utils": "2.20.3", + "@walletconnect/sign-client": "2.20.2", + "@walletconnect/types": "2.20.2", + "@walletconnect/universal-provider": "2.20.2", + "@walletconnect/utils": "2.20.2", "events": "3.3.0" } }, "packages/fcl-ethereum-provider/node_modules/@walletconnect/keyvaluestorage": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", - "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", "license": "MIT", "dependencies": { "@walletconnect/safe-json": "^1.0.1", @@ -31534,26 +31330,22 @@ } }, "packages/fcl-ethereum-provider/node_modules/@walletconnect/sign-client": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.20.3.tgz", - "integrity": "sha512-78nW9UCRRvXgcp0M8lpbRBuu301LZb+jAGeFYIhVat0JGOzxHPVl+Wu+ruVrA6e4a8JYns7V1443r3qEc3Pj2Q==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { - "@walletconnect/core": "2.20.3", + "@walletconnect/core": "2.20.2", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.20.3", - "@walletconnect/utils": "2.20.3", + "@walletconnect/types": "2.20.2", + "@walletconnect/utils": "2.20.2", "events": "3.3.0" } }, "packages/fcl-ethereum-provider/node_modules/@walletconnect/types": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.20.3.tgz", - "integrity": "sha512-Onco0uRzXnyK8zcwygjrip2CuWqJ7NhuRDPqoNQEjKma9CNdWxOhHu6a+iV+4nJ/gQrpZYSaEfJLVw+UdUPxKA==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/events": "1.0.1", @@ -31565,9 +31357,7 @@ } }, "packages/fcl-ethereum-provider/node_modules/@walletconnect/universal-provider": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.20.3.tgz", - "integrity": "sha512-w1FoD1adYuCPZRL5sKmm8mFx82lMb9cIouO+mo4qJ3si23OiFou0t9da9OyziWJtkKyhC2kxHaJXcUvxVzM3Qw==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/events": "1.0.1", @@ -31577,17 +31367,15 @@ "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.20.3", - "@walletconnect/types": "2.20.3", - "@walletconnect/utils": "2.20.3", + "@walletconnect/sign-client": "2.20.2", + "@walletconnect/types": "2.20.2", + "@walletconnect/utils": "2.20.2", "es-toolkit": "1.33.0", "events": "3.3.0" } }, "packages/fcl-ethereum-provider/node_modules/@walletconnect/utils": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.20.3.tgz", - "integrity": "sha512-16/5UpR60lKWsyIMP9XeYup3XCOIxgPDOqxCRhm9iAT2XMFGqzBi8CL8U8AEreJelNZ0map4McgLKNaGZCU1NA==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { "@noble/ciphers": "1.2.1", @@ -31599,7 +31387,7 @@ "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.20.3", + "@walletconnect/types": "2.20.2", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "bs58": "6.0.0", @@ -31611,14 +31399,14 @@ }, "packages/fcl-rainbowkit-adapter": { "name": "@onflow/fcl-rainbowkit-adapter", - "version": "0.2.1-alpha.1", + "version": "0.2.1", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.5-alpha.1", - "@onflow/fcl-wagmi-adapter": "0.0.5-alpha.1", + "@onflow/fcl-ethereum-provider": "0.0.5", + "@onflow/fcl-wagmi-adapter": "0.0.5", "@onflow/rlp": "^1.2.3", "@wagmi/core": "^2.16.3", "mipd": "^0.0.7", @@ -31629,7 +31417,7 @@ "devDependencies": { "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "^1.6.0-alpha.0", + "@onflow/typedefs": "^1.6.0", "@types/jest": "^29.5.13", "@types/react": "^16.0.0", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -31639,15 +31427,13 @@ "jest": "^29.7.0" }, "peerDependencies": { - "@onflow/fcl": "1.18.0-alpha.1", + "@onflow/fcl": "1.18.0", "@rainbow-me/rainbowkit": "^2.2.3", "react": "17.x || 18.x || 19.x" } }, "packages/fcl-rainbowkit-adapter/node_modules/@types/react": { - "version": "16.14.64", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.64.tgz", - "integrity": "sha512-CcJ89hvLdpjYzV9QHYAbD2IVggc8RYEd7Km40HzJPuN3TiKv9sfSNYXj8zohrIGJPiKMClMIXJE+XJX1qZXpnw==", + "version": "16.14.63", "dev": true, "license": "MIT", "dependencies": { @@ -31658,15 +31444,15 @@ }, "packages/fcl-react-native": { "name": "@onflow/fcl-react-native", - "version": "1.12.0-alpha.1", + "version": "1.12.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.2", - "@onflow/fcl-core": "1.19.0-alpha.1", + "@onflow/fcl-core": "1.19.0", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.9.0-alpha.1", + "@onflow/sdk": "1.9.0", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", @@ -31679,7 +31465,7 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/typedefs": "1.6.0", "@types/estree": "^1.0.6", "@types/node": "^18.19.57", "eslint": "^8.57.1", @@ -31717,20 +31503,20 @@ }, "packages/fcl-wagmi-adapter": { "name": "@onflow/fcl-wagmi-adapter", - "version": "0.0.5-alpha.1", + "version": "0.0.5", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.5-alpha.1", + "@onflow/fcl-ethereum-provider": "0.0.5", "@onflow/rlp": "^1.2.3", "viem": "^2.22.21" }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "^1.6.0-alpha.0", + "@onflow/typedefs": "^1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -31739,13 +31525,13 @@ "jest": "^29.7.0" }, "peerDependencies": { - "@onflow/fcl": "1.18.0-alpha.1", + "@onflow/fcl": "1.18.0", "@wagmi/core": "^2.16.3" } }, "packages/fcl-wc": { "name": "@onflow/fcl-wc", - "version": "6.0.3-alpha.1", + "version": "6.0.3", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -31764,7 +31550,7 @@ "@babel/plugin-transform-react-jsx": "^7.25.9", "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/typedefs": "1.6.0", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", @@ -31772,13 +31558,11 @@ "jest-preset-preact": "^4.1.1" }, "peerDependencies": { - "@onflow/fcl-core": "1.19.0-alpha.1" + "@onflow/fcl-core": "1.19.0" } }, "packages/fcl-wc/node_modules/@react-native-async-storage/async-storage": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", - "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", "license": "MIT", "optional": true, "peer": true, @@ -31790,9 +31574,7 @@ } }, "packages/fcl-wc/node_modules/@walletconnect/core": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.20.3.tgz", - "integrity": "sha512-2xMopmR6Inx4d4PIwChnfQxdpb828WAwXWj+DodcDpt1aqusknVfkoDtx/PL1+KyqEbyiUdlW09krjnRvT82KQ==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/heartbeat": "1.2.2", @@ -31806,8 +31588,8 @@ "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.20.3", - "@walletconnect/utils": "2.20.3", + "@walletconnect/types": "2.20.2", + "@walletconnect/utils": "2.20.2", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", @@ -31819,8 +31601,6 @@ }, "packages/fcl-wc/node_modules/@walletconnect/keyvaluestorage": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", - "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", "license": "MIT", "dependencies": { "@walletconnect/safe-json": "^1.0.1", @@ -31837,26 +31617,22 @@ } }, "packages/fcl-wc/node_modules/@walletconnect/sign-client": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.20.3.tgz", - "integrity": "sha512-78nW9UCRRvXgcp0M8lpbRBuu301LZb+jAGeFYIhVat0JGOzxHPVl+Wu+ruVrA6e4a8JYns7V1443r3qEc3Pj2Q==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { - "@walletconnect/core": "2.20.3", + "@walletconnect/core": "2.20.2", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.20.3", - "@walletconnect/utils": "2.20.3", + "@walletconnect/types": "2.20.2", + "@walletconnect/utils": "2.20.2", "events": "3.3.0" } }, "packages/fcl-wc/node_modules/@walletconnect/types": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.20.3.tgz", - "integrity": "sha512-Onco0uRzXnyK8zcwygjrip2CuWqJ7NhuRDPqoNQEjKma9CNdWxOhHu6a+iV+4nJ/gQrpZYSaEfJLVw+UdUPxKA==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/events": "1.0.1", @@ -31868,9 +31644,7 @@ } }, "packages/fcl-wc/node_modules/@walletconnect/universal-provider": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.20.3.tgz", - "integrity": "sha512-w1FoD1adYuCPZRL5sKmm8mFx82lMb9cIouO+mo4qJ3si23OiFou0t9da9OyziWJtkKyhC2kxHaJXcUvxVzM3Qw==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/events": "1.0.1", @@ -31880,17 +31654,15 @@ "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.20.3", - "@walletconnect/types": "2.20.3", - "@walletconnect/utils": "2.20.3", + "@walletconnect/sign-client": "2.20.2", + "@walletconnect/types": "2.20.2", + "@walletconnect/utils": "2.20.2", "es-toolkit": "1.33.0", "events": "3.3.0" } }, "packages/fcl-wc/node_modules/@walletconnect/utils": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.20.3.tgz", - "integrity": "sha512-16/5UpR60lKWsyIMP9XeYup3XCOIxgPDOqxCRhm9iAT2XMFGqzBi8CL8U8AEreJelNZ0map4McgLKNaGZCU1NA==", + "version": "2.20.2", "license": "Apache-2.0", "dependencies": { "@noble/ciphers": "1.2.1", @@ -31902,7 +31674,7 @@ "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.20.3", + "@walletconnect/types": "2.20.2", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "bs58": "6.0.0", @@ -31933,23 +31705,22 @@ }, "packages/kit": { "name": "@onflow/kit", - "version": "0.2.0-alpha.1", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@headlessui/react": "^2.2.2", "@tanstack/react-query": "^5.67.3", "@testing-library/react": "^16.2.0", - "postcss-cli": "^11.0.0", - "tailwind-merge": "^2.2.1", - "viem": "^2.28.1" + "tailwind-merge": "^3.3.1", + "viem": "^2.29.2" }, "devDependencies": { "@babel/preset-env": "^7.26.9", "@babel/preset-react": "^7.26.3", "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "^1.6.0-alpha.0", + "@onflow/typedefs": "^1.6.0", "@testing-library/dom": "^10.4.0", "@types/jest": "^29.5.13", "@types/react": "^19.0.10", @@ -31964,15 +31735,13 @@ "tailwindcss": "^3.4.14" }, "peerDependencies": { - "@onflow/fcl": ">=1.18.0-alpha.1", + "@onflow/fcl": ">=1.18.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "packages/kit/node_modules/@noble/curves": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", - "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", "license": "MIT", "dependencies": { "@noble/hashes": "1.7.2" @@ -31986,8 +31755,6 @@ }, "packages/kit/node_modules/@noble/hashes": { "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -31998,8 +31765,6 @@ }, "packages/kit/node_modules/eventemitter3": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, "packages/kit/node_modules/isows": { @@ -32019,8 +31784,6 @@ }, "packages/kit/node_modules/ox": { "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", "funding": [ { "type": "github", @@ -32046,14 +31809,6 @@ } } }, - "packages/kit/node_modules/tailwind-merge": { - "version": "2.6.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, "packages/kit/node_modules/viem": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/viem/-/viem-2.30.0.tgz", @@ -32086,8 +31841,6 @@ }, "packages/kit/node_modules/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -32173,14 +31926,14 @@ }, "packages/sdk": { "name": "@onflow/sdk", - "version": "1.9.0-alpha.1", + "version": "1.9.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.2", "@onflow/rlp": "1.2.3", - "@onflow/transport-http": "1.13.0-alpha.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/transport-http": "1.13.0", + "@onflow/typedefs": "1.6.0", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", @@ -32229,13 +31982,13 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.7.0", - "@onflow/sdk": "1.9.0-alpha.1", + "@onflow/sdk": "1.9.0", "jest": "^29.7.0" } }, "packages/transport-http": { "name": "@onflow/transport-http", - "version": "1.13.0-alpha.0", + "version": "1.13.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -32253,7 +32006,7 @@ "devDependencies": { "@onflow/fcl-bundle": "1.7.0", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.9.0-alpha.1", + "@onflow/sdk": "1.9.0", "@onflow/types": "1.4.1", "jest": "^29.7.0", "jest-websocket-mock": "^2.5.0", @@ -32269,7 +32022,7 @@ }, "packages/typedefs": { "name": "@onflow/typedefs", - "version": "1.6.0-alpha.0", + "version": "1.6.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7" diff --git a/packages/fcl-core/CHANGELOG.md b/packages/fcl-core/CHANGELOG.md index 5b8851031..c18bb4d68 100644 --- a/packages/fcl-core/CHANGELOG.md +++ b/packages/fcl-core/CHANGELOG.md @@ -1,5 +1,40 @@ # @onflow/fcl +## 1.19.0 + +### Minor Changes + +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `subscribeRaw`. + + These are only available when using a REST API endpoint and not supported by the deprecated GRPC transport. + + The following topics are now available: + + - `blocks` + - `block_headers` + - `block_digests` + - `transaction_statues` + - `events` + - `account_statuses` + + Developers using `fcl.tx` and `fcl.events` will not need to make any changes to their existing app to realize the latency improvements of this change and will automatically benefit by upgrading to this version. + + Please see the [Flow Developer Documentation](https://developers.flow.com/clients/fcl-js/) for more details on how to use these new methods. + +### Patch Changes + +- [#2461](https://github.com/onflow/fcl-js/pull/2461) [`2637889fdb47a2294ad2db9d06a16fac1d805a12`](https://github.com/onflow/fcl-js/commit/2637889fdb47a2294ad2db9d06a16fac1d805a12) Thanks [@jribbink](https://github.com/jribbink)! - Unsubscribe from transaction statuses when sealed + +- Updated dependencies [[`3ac616d64c9abcda32f0c450119f22fa479d5e89`](https://github.com/onflow/fcl-js/commit/3ac616d64c9abcda32f0c450119f22fa479d5e89), [`0b83658f62a428a70074d33875f264fbd48aff1e`](https://github.com/onflow/fcl-js/commit/0b83658f62a428a70074d33875f264fbd48aff1e), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`694cd76807b7ca4441d1f8425ac4f8426cbc18fa`](https://github.com/onflow/fcl-js/commit/694cd76807b7ca4441d1f8425ac4f8426cbc18fa), [`4d3bb084c1442552d6a1de1f53435d1aa3f600b0`](https://github.com/onflow/fcl-js/commit/4d3bb084c1442552d6a1de1f53435d1aa3f600b0)]: + - @onflow/sdk@1.9.0 + - @onflow/transport-http@1.13.0 + +## 1.19.0-alpha.2 + +### Patch Changes + +- [#2461](https://github.com/onflow/fcl-js/pull/2461) [`2637889fdb47a2294ad2db9d06a16fac1d805a12`](https://github.com/onflow/fcl-js/commit/2637889fdb47a2294ad2db9d06a16fac1d805a12) Thanks [@jribbink](https://github.com/jribbink)! - Unsubscribe from transaction statuses when sealed + ## 1.19.0-alpha.1 ### Patch Changes @@ -11,7 +46,7 @@ ### Minor Changes -- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `rawSubscribe`. +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `subscribeRaw`. These are only available when using a REST API endpoint and not supported by the deprecated GRPC trasnport. diff --git a/packages/fcl-core/package.json b/packages/fcl-core/package.json index 463a4352c..ded6a2947 100644 --- a/packages/fcl-core/package.json +++ b/packages/fcl-core/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-core", - "version": "1.19.0-alpha.1", + "version": "1.19.0", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -20,7 +20,7 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/typedefs": "1.6.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", @@ -52,8 +52,8 @@ "@onflow/config": "1.5.2", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.9.0-alpha.1", - "@onflow/transport-http": "1.13.0-alpha.0", + "@onflow/sdk": "1.9.0", + "@onflow/transport-http": "1.13.0", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", diff --git a/packages/fcl-core/src/fcl-core.ts b/packages/fcl-core/src/fcl-core.ts index abe43be38..1a764cb6f 100644 --- a/packages/fcl-core/src/fcl-core.ts +++ b/packages/fcl-core/src/fcl-core.ts @@ -72,7 +72,7 @@ export {invariant} from "@onflow/sdk" // Subscriptions export {subscribe} from "@onflow/sdk" -export {rawSubscribe} from "@onflow/sdk" +export {subscribeRaw} from "@onflow/sdk" import {watchForChainIdChanges} from "./utils" diff --git a/packages/fcl-core/src/transaction/transaction.test.ts b/packages/fcl-core/src/transaction/transaction.test.ts index 83bf251b1..9f0723950 100644 --- a/packages/fcl-core/src/transaction/transaction.test.ts +++ b/packages/fcl-core/src/transaction/transaction.test.ts @@ -120,6 +120,46 @@ describe("transaction", () => { expect(() => transaction(txId)).toThrow("Invalid transactionId") }) + test("should unsubscribe once the transaction has sealed", async () => { + jest.resetModules() + const txId = + "4234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + const callback = jest.fn() + transaction(txId).subscribe(callback) + + // Flush the event loop + await new Promise(resolve => setTimeout(resolve, 0)) + + // Mock the observable to emit a SEALED status + const subscribeParams = jest.mocked(subscribe).mock + .calls[0][0] as Parameters< + typeof subscribe + >[0] + + subscribeParams.onData({ + status: TransactionExecutionStatus.PENDING, + blockId: "", + statusCode: 0, + errorMessage: "", + events: [], + statusString: "PENDING", + }) + + subscribeParams.onData({ + status: TransactionExecutionStatus.SEALED, + blockId: "", + statusCode: 0, + errorMessage: "", + events: [], + statusString: "SEALED", + }) + + await new Promise(resolve => setTimeout(resolve, 100)) + + const unsubMock = jest.mocked(subscribe).mock.results[0].value + expect(unsubMock.unsubscribe).toHaveBeenCalledTimes(1) + }) + test("subscribe should fallback to polling if real-time streaming is not supported", async () => { const txId = "2234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" diff --git a/packages/fcl-core/src/transaction/transaction.ts b/packages/fcl-core/src/transaction/transaction.ts index 791013040..8ff6acce8 100644 --- a/packages/fcl-core/src/transaction/transaction.ts +++ b/packages/fcl-core/src/transaction/transaction.ts @@ -84,29 +84,26 @@ export function transaction( return function innerOnce(opts = {suppress: false}) { const suppress = opts.suppress || false return new Promise((resolve, reject) => { - const unsub = subscribe((( - txStatus?: TransactionStatus, - error?: Error - ) => { - if ((error || txStatus?.statusCode) && !suppress) { - if (error != null) { - reject(error) - unsub() - } else if (txStatus?.statusCode === 1) { + const unsub = subscribe( + (txStatus: TransactionStatus) => { + if (txStatus.statusCode === 1) { const transactionError = TransactionError.fromErrorMessage( txStatus.errorMessage ) reject(transactionError) unsub() + } else if (predicate(txStatus)) { + resolve(txStatus) + unsub() + } + }, + err => { + if (!suppress) { + reject(err) + unsub() } - return - } - - if (predicate(txStatus!)) { - resolve(txStatus!) - unsub() } - }) as any) + ) }) as Promise } } @@ -168,7 +165,7 @@ function createObservable( // Subscribe to transaction status updates function subscribeTransactionStatuses() { // Subscribe to transaction status updates - sdkSubscribe({ + const subscription = sdkSubscribe({ topic: SubscriptionTopic.TRANSACTION_STATUSES, args: {transactionId: txId}, onData: txStatus => { @@ -176,6 +173,16 @@ function createObservable( value = txStatus next(txStatus) } + + // Clean up the subscription if the transaction is sealed + // Wait for next tick to ensure unsubscribe is defined + if (isSealed(txStatus)) { + new Promise(resolve => setTimeout(resolve, 0)).then(() => { + if (isSealed(txStatus)) { + subscription.unsubscribe() + } + }) + } }, onError: (err: Error) => { if (err instanceof SubscriptionsNotSupportedError) { @@ -192,13 +199,21 @@ function createObservable( function fallbackLegacyPolling() { // Poll for transaction status updates - legacyTransaction(txId, opts).subscribe( + const unsubscribe = legacyTransaction(txId, opts).subscribe( (txStatus?: TransactionStatus, err?: Error) => { if (err) { error(err) - } else if (txStatus) { + } else if (txStatus && isDiff(value, txStatus)) { value = txStatus next(txStatus) + + // Clean up the subscription if the transaction is sealed + // Wait for next tick to ensure unsubscribe is defined + if (isSealed(txStatus)) { + new Promise(resolve => setTimeout(resolve, 0)).then(() => { + unsubscribe() + }) + } } } ) diff --git a/packages/fcl-ethereum-provider/CHANGELOG.md b/packages/fcl-ethereum-provider/CHANGELOG.md index 88f5507f6..f1050ddfa 100644 --- a/packages/fcl-ethereum-provider/CHANGELOG.md +++ b/packages/fcl-ethereum-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @onflow/fcl-ethereum-provider +## 0.0.5 + +### Patch Changes + +- Updated dependencies [[`0b83658f62a428a70074d33875f264fbd48aff1e`](https://github.com/onflow/fcl-js/commit/0b83658f62a428a70074d33875f264fbd48aff1e), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab)]: + - @onflow/fcl@1.18.0 + - @onflow/fcl-wc@6.0.3 + +## 0.0.5-alpha.2 + +### Patch Changes + +- Updated dependencies []: + - @onflow/fcl@1.18.0-alpha.2 + - @onflow/fcl-wc@6.0.3-alpha.2 + ## 0.0.5-alpha.1 ### Patch Changes diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index 3ba912c0b..5eafc3c9a 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-ethereum-provider", - "version": "0.0.5-alpha.1", + "version": "0.0.5", "description": "Ethereum provider for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -15,7 +15,7 @@ "devDependencies": { "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "^1.6.0-alpha.0", + "@onflow/typedefs": "^1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -40,7 +40,7 @@ "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", - "@onflow/fcl-wc": "6.0.3-alpha.1", + "@onflow/fcl-wc": "6.0.3", "@onflow/rlp": "^1.2.3", "@walletconnect/ethereum-provider": "^2.20.2", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -50,6 +50,6 @@ "@walletconnect/utils": "^2.20.2" }, "peerDependencies": { - "@onflow/fcl": "1.18.0-alpha.1" + "@onflow/fcl": "1.18.0" } } diff --git a/packages/fcl-rainbowkit-adapter/CHANGELOG.md b/packages/fcl-rainbowkit-adapter/CHANGELOG.md index acb4d9edd..0dbf2550e 100644 --- a/packages/fcl-rainbowkit-adapter/CHANGELOG.md +++ b/packages/fcl-rainbowkit-adapter/CHANGELOG.md @@ -1,5 +1,23 @@ # @onflow/fcl-rainbowkit-adapter +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`0b83658f62a428a70074d33875f264fbd48aff1e`](https://github.com/onflow/fcl-js/commit/0b83658f62a428a70074d33875f264fbd48aff1e), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab)]: + - @onflow/fcl@1.18.0 + - @onflow/fcl-ethereum-provider@0.0.5 + - @onflow/fcl-wagmi-adapter@0.0.5 + +## 0.2.1-alpha.2 + +### Patch Changes + +- Updated dependencies []: + - @onflow/fcl@1.18.0-alpha.2 + - @onflow/fcl-ethereum-provider@0.0.5-alpha.2 + - @onflow/fcl-wagmi-adapter@0.0.5-alpha.2 + ## 0.2.1-alpha.1 ### Patch Changes diff --git a/packages/fcl-rainbowkit-adapter/package.json b/packages/fcl-rainbowkit-adapter/package.json index 3a972f35f..d37a201bf 100644 --- a/packages/fcl-rainbowkit-adapter/package.json +++ b/packages/fcl-rainbowkit-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-rainbowkit-adapter", - "version": "0.2.1-alpha.1", + "version": "0.2.1", "description": "Rainbowkit adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -15,7 +15,7 @@ "devDependencies": { "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "^1.6.0-alpha.0", + "@onflow/typedefs": "^1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -40,8 +40,8 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.5-alpha.1", - "@onflow/fcl-wagmi-adapter": "0.0.5-alpha.1", + "@onflow/fcl-ethereum-provider": "0.0.5", + "@onflow/fcl-wagmi-adapter": "0.0.5", "@onflow/rlp": "^1.2.3", "@wagmi/core": "^2.16.3", "mipd": "^0.0.7", @@ -50,7 +50,7 @@ "wagmi": "^2.14.11" }, "peerDependencies": { - "@onflow/fcl": "1.18.0-alpha.1", + "@onflow/fcl": "1.18.0", "@rainbow-me/rainbowkit": "^2.2.3", "react": "17.x || 18.x || 19.x" } diff --git a/packages/fcl-react-native/CHANGELOG.md b/packages/fcl-react-native/CHANGELOG.md index 86da9fcaa..ce41c0fcb 100644 --- a/packages/fcl-react-native/CHANGELOG.md +++ b/packages/fcl-react-native/CHANGELOG.md @@ -1,5 +1,39 @@ # @onflow/fcl-react-native +## 1.12.0 + +### Minor Changes + +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `subscribeRaw`. + + These are only available when using a REST API endpoint and not supported by the deprecated GRPC transport. + + The following topics are now available: + + - `blocks` + - `block_headers` + - `block_digests` + - `transaction_statues` + - `events` + - `account_statuses` + + Developers using `fcl.tx` and `fcl.events` will not need to make any changes to their existing app to realize the latency improvements of this change and will automatically benefit by upgrading to this version. + + Please see the [Flow Developer Documentation](https://developers.flow.com/clients/fcl-js/) for more details on how to use these new methods. + +### Patch Changes + +- Updated dependencies [[`3ac616d64c9abcda32f0c450119f22fa479d5e89`](https://github.com/onflow/fcl-js/commit/3ac616d64c9abcda32f0c450119f22fa479d5e89), [`0b83658f62a428a70074d33875f264fbd48aff1e`](https://github.com/onflow/fcl-js/commit/0b83658f62a428a70074d33875f264fbd48aff1e), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`2637889fdb47a2294ad2db9d06a16fac1d805a12`](https://github.com/onflow/fcl-js/commit/2637889fdb47a2294ad2db9d06a16fac1d805a12), [`4d3bb084c1442552d6a1de1f53435d1aa3f600b0`](https://github.com/onflow/fcl-js/commit/4d3bb084c1442552d6a1de1f53435d1aa3f600b0)]: + - @onflow/sdk@1.9.0 + - @onflow/fcl-core@1.19.0 + +## 1.12.0-alpha.2 + +### Patch Changes + +- Updated dependencies [[`2637889fdb47a2294ad2db9d06a16fac1d805a12`](https://github.com/onflow/fcl-js/commit/2637889fdb47a2294ad2db9d06a16fac1d805a12)]: + - @onflow/fcl-core@1.19.0-alpha.2 + ## 1.12.0-alpha.1 ### Patch Changes @@ -12,7 +46,7 @@ ### Minor Changes -- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `rawSubscribe`. +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `subscribeRaw`. These are only available when using a REST API endpoint and not supported by the deprecated GRPC trasnport. diff --git a/packages/fcl-react-native/package.json b/packages/fcl-react-native/package.json index 6d7bb74bd..f2cfd70a5 100644 --- a/packages/fcl-react-native/package.json +++ b/packages/fcl-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-react-native", - "version": "1.12.0-alpha.1", + "version": "1.12.0", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -20,7 +20,7 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/typedefs": "1.6.0", "@types/estree": "^1.0.6", "@types/node": "^18.19.57", "eslint": "^8.57.1", @@ -48,10 +48,10 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.2", - "@onflow/fcl-core": "1.19.0-alpha.1", + "@onflow/fcl-core": "1.19.0", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.9.0-alpha.1", + "@onflow/sdk": "1.9.0", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", diff --git a/packages/fcl-react-native/src/fcl-react-native.ts b/packages/fcl-react-native/src/fcl-react-native.ts index 9cf87a27a..bc11379fa 100644 --- a/packages/fcl-react-native/src/fcl-react-native.ts +++ b/packages/fcl-react-native/src/fcl-react-native.ts @@ -111,4 +111,4 @@ export {useServiceDiscovery, ServiceDiscovery} // Subscriptions export {subscribe} from "@onflow/fcl-core" -export {rawSubscribe} from "@onflow/fcl-core" +export {subscribeRaw} from "@onflow/fcl-core" diff --git a/packages/fcl-wagmi-adapter/CHANGELOG.md b/packages/fcl-wagmi-adapter/CHANGELOG.md index a78f1ad7d..49a99850c 100644 --- a/packages/fcl-wagmi-adapter/CHANGELOG.md +++ b/packages/fcl-wagmi-adapter/CHANGELOG.md @@ -1,5 +1,21 @@ # @onflow/fcl-wagmi-adapter +## 0.0.5 + +### Patch Changes + +- Updated dependencies [[`0b83658f62a428a70074d33875f264fbd48aff1e`](https://github.com/onflow/fcl-js/commit/0b83658f62a428a70074d33875f264fbd48aff1e), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab)]: + - @onflow/fcl@1.18.0 + - @onflow/fcl-ethereum-provider@0.0.5 + +## 0.0.5-alpha.2 + +### Patch Changes + +- Updated dependencies []: + - @onflow/fcl@1.18.0-alpha.2 + - @onflow/fcl-ethereum-provider@0.0.5-alpha.2 + ## 0.0.5-alpha.1 ### Patch Changes diff --git a/packages/fcl-wagmi-adapter/package.json b/packages/fcl-wagmi-adapter/package.json index 0315c5b79..a35b3cf3f 100644 --- a/packages/fcl-wagmi-adapter/package.json +++ b/packages/fcl-wagmi-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wagmi-adapter", - "version": "0.0.5-alpha.1", + "version": "0.0.5", "description": "Wagmi adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -15,7 +15,7 @@ "devDependencies": { "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "^1.6.0-alpha.0", + "@onflow/typedefs": "^1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -39,12 +39,12 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.5-alpha.1", + "@onflow/fcl-ethereum-provider": "0.0.5", "@onflow/rlp": "^1.2.3", "viem": "^2.22.21" }, "peerDependencies": { - "@onflow/fcl": "1.18.0-alpha.1", + "@onflow/fcl": "1.18.0", "@wagmi/core": "^2.16.3" } } diff --git a/packages/fcl-wc/CHANGELOG.md b/packages/fcl-wc/CHANGELOG.md index 25cb3b010..4526dc619 100644 --- a/packages/fcl-wc/CHANGELOG.md +++ b/packages/fcl-wc/CHANGELOG.md @@ -1,5 +1,19 @@ # @onflow/fcl-wc +## 6.0.3 + +### Patch Changes + +- Updated dependencies [[`0b83658f62a428a70074d33875f264fbd48aff1e`](https://github.com/onflow/fcl-js/commit/0b83658f62a428a70074d33875f264fbd48aff1e), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`2637889fdb47a2294ad2db9d06a16fac1d805a12`](https://github.com/onflow/fcl-js/commit/2637889fdb47a2294ad2db9d06a16fac1d805a12)]: + - @onflow/fcl-core@1.19.0 + +## 6.0.3-alpha.2 + +### Patch Changes + +- Updated dependencies [[`2637889fdb47a2294ad2db9d06a16fac1d805a12`](https://github.com/onflow/fcl-js/commit/2637889fdb47a2294ad2db9d06a16fac1d805a12)]: + - @onflow/fcl-core@1.19.0-alpha.2 + ## 6.0.3-alpha.1 ### Patch Changes diff --git a/packages/fcl-wc/package.json b/packages/fcl-wc/package.json index a20fbe55f..b39cd7698 100644 --- a/packages/fcl-wc/package.json +++ b/packages/fcl-wc/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wc", - "version": "6.0.3-alpha.1", + "version": "6.0.3", "description": "WalletConnect adapter for FCL", "license": "Apache-2.0", "author": "Flow Foundation", @@ -31,7 +31,7 @@ "@babel/plugin-transform-react-jsx": "^7.25.9", "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/typedefs": "1.6.0", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", @@ -52,6 +52,6 @@ "tailwindcss": "^3.4.14" }, "peerDependencies": { - "@onflow/fcl-core": "1.19.0-alpha.1" + "@onflow/fcl-core": "1.19.0" } } diff --git a/packages/fcl/CHANGELOG.md b/packages/fcl/CHANGELOG.md index 436ae1cfc..41a723f7d 100644 --- a/packages/fcl/CHANGELOG.md +++ b/packages/fcl/CHANGELOG.md @@ -1,5 +1,41 @@ # @onflow/fcl +## 1.18.0 + +### Minor Changes + +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `subscribeRaw`. + + These are only available when using a REST API endpoint and not supported by the deprecated GRPC transport. + + The following topics are now available: + + - `blocks` + - `block_headers` + - `block_digests` + - `transaction_statues` + - `events` + - `account_statuses` + + Developers using `fcl.tx` and `fcl.events` will not need to make any changes to their existing app to realize the latency improvements of this change and will automatically benefit by upgrading to this version. + + Please see the [Flow Developer Documentation](https://developers.flow.com/clients/fcl-js/) for more details on how to use these new methods. + +### Patch Changes + +- Updated dependencies [[`3ac616d64c9abcda32f0c450119f22fa479d5e89`](https://github.com/onflow/fcl-js/commit/3ac616d64c9abcda32f0c450119f22fa479d5e89), [`0b83658f62a428a70074d33875f264fbd48aff1e`](https://github.com/onflow/fcl-js/commit/0b83658f62a428a70074d33875f264fbd48aff1e), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`2637889fdb47a2294ad2db9d06a16fac1d805a12`](https://github.com/onflow/fcl-js/commit/2637889fdb47a2294ad2db9d06a16fac1d805a12), [`4d3bb084c1442552d6a1de1f53435d1aa3f600b0`](https://github.com/onflow/fcl-js/commit/4d3bb084c1442552d6a1de1f53435d1aa3f600b0)]: + - @onflow/sdk@1.9.0 + - @onflow/fcl-core@1.19.0 + - @onflow/fcl-wc@6.0.3 + +## 1.18.0-alpha.2 + +### Patch Changes + +- Updated dependencies [[`2637889fdb47a2294ad2db9d06a16fac1d805a12`](https://github.com/onflow/fcl-js/commit/2637889fdb47a2294ad2db9d06a16fac1d805a12)]: + - @onflow/fcl-core@1.19.0-alpha.2 + - @onflow/fcl-wc@6.0.3-alpha.2 + ## 1.18.0-alpha.1 ### Patch Changes @@ -13,7 +49,7 @@ ### Minor Changes -- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `rawSubscribe`. +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `subscribeRaw`. These are only available when using a REST API endpoint and not supported by the deprecated GRPC trasnport. @@ -35,7 +71,7 @@ - Updated dependencies [[`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`4d3bb084c1442552d6a1de1f53435d1aa3f600b0`](https://github.com/onflow/fcl-js/commit/4d3bb084c1442552d6a1de1f53435d1aa3f600b0)]: - @onflow/fcl-core@1.19.0-alpha.0 - @onflow/sdk@1.9.0-alpha.0 - - @onflow/fcl-wc@7.0.0-alpha.0 + - @onflow/fcl-wc@6.0.3-alpha.0 ## 1.17.0 diff --git a/packages/fcl/package.json b/packages/fcl/package.json index 3cdb03dde..fbf699d26 100644 --- a/packages/fcl/package.json +++ b/packages/fcl/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl", - "version": "1.18.0-alpha.1", + "version": "1.18.0", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -20,7 +20,7 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/typedefs": "1.6.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", @@ -49,11 +49,11 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.2", - "@onflow/fcl-core": "1.19.0-alpha.1", - "@onflow/fcl-wc": "6.0.3-alpha.1", + "@onflow/fcl-core": "1.19.0", + "@onflow/fcl-wc": "6.0.3", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.9.0-alpha.1", + "@onflow/sdk": "1.9.0", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", diff --git a/packages/fcl/src/fcl.ts b/packages/fcl/src/fcl.ts index f2d71799a..78508a077 100644 --- a/packages/fcl/src/fcl.ts +++ b/packages/fcl/src/fcl.ts @@ -116,4 +116,4 @@ initFclWcLoader() export {LOCAL_STORAGE, SESSION_STORAGE} from "./utils/web" // Subscriptions -export {subscribe, rawSubscribe} from "@onflow/fcl-core" +export {subscribe, subscribeRaw} from "@onflow/fcl-core" diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md index 880a3e8f8..972d393e3 100644 --- a/packages/kit/CHANGELOG.md +++ b/packages/kit/CHANGELOG.md @@ -1,5 +1,62 @@ # @onflow/kit +## 0.4.0 + +### Minor Changes + +- [#2503](https://github.com/onflow/fcl-js/pull/2503) [`29a2c99b08d6f5a427bef5362e5d4e7ada9d51e7`](https://github.com/onflow/fcl-js/commit/29a2c99b08d6f5a427bef5362e5d4e7ada9d51e7) Thanks [@jribbink](https://github.com/jribbink)! - **BREAKING** Update `useCrossVmBatchTransaction` result data to the Cadence transaction ID instead of waiting for the EVM transaction hash. + + This change ensures consistency with the existing `useFlowMutate` response format and latencies, as waiting for the transaction execution for EVM results adds unnecessary delays and harms user experience. + + Developers should instead manually subscribe to the Cadence transaction status to track execution status and determine the EVM transaction results. + +- [#2460](https://github.com/onflow/fcl-js/pull/2460) [`d7b673e2ea97f6ab5ec2b81d2186b3e9799460cf`](https://github.com/onflow/fcl-js/commit/d7b673e2ea97f6ab5ec2b81d2186b3e9799460cf) Thanks [@jribbink](https://github.com/jribbink)! - Add `useCrossVmSpendNft` hook + +- [#2503](https://github.com/onflow/fcl-js/pull/2503) [`f1a7eeab04a46e78b34a7a19aa4d8d93f3add452`](https://github.com/onflow/fcl-js/commit/f1a7eeab04a46e78b34a7a19aa4d8d93f3add452) Thanks [@jribbink](https://github.com/jribbink)! - Add `useCrossVmSpendToken` hook + +## 0.3.1 + +### Patch Changes + +- [#2491](https://github.com/onflow/fcl-js/pull/2491) [`ff07e0ea38845f188f0bbbcb9a365cad96cfb8b7`](https://github.com/onflow/fcl-js/commit/ff07e0ea38845f188f0bbbcb9a365cad96cfb8b7) Thanks [@jribbink](https://github.com/jribbink)! - Export `useCrossVmTokenBalance` hook + +## 0.3.0 + +### Minor Changes + +- [#2398](https://github.com/onflow/fcl-js/pull/2398) [`99510059485ffb2d741407a573f3be076c77e044`](https://github.com/onflow/fcl-js/commit/99510059485ffb2d741407a573f3be076c77e044) Thanks [@jribbink](https://github.com/jribbink)! - Add `useCrossVmTokenBalance` hook to get full token balance across both Cadence and EVM accounts + +### Patch Changes + +- [#2486](https://github.com/onflow/fcl-js/pull/2486) [`3f5d5037882d2da03713ece0ff4f6b7e9d3693b2`](https://github.com/onflow/fcl-js/commit/3f5d5037882d2da03713ece0ff4f6b7e9d3693b2) Thanks [@jribbink](https://github.com/jribbink)! - Switch `contractIdentifier` to `vaultIdentifier` in `useCrossVmTokenBalance` hook + +## 0.2.1 + +### Patch Changes + +- [#2433](https://github.com/onflow/fcl-js/pull/2433) [`9f9e18b5381d455ef4546b6521ea37c5eef3063c`](https://github.com/onflow/fcl-js/commit/9f9e18b5381d455ef4546b6521ea37c5eef3063c) Thanks [@jribbink](https://github.com/jribbink)! - Fix script args query key + +## 0.2.0 + +### Minor Changes + +- [#2439](https://github.com/onflow/fcl-js/pull/2439) [`a36d78ee5283ceb9a2f411e6da9ddf0373777c24`](https://github.com/onflow/fcl-js/commit/a36d78ee5283ceb9a2f411e6da9ddf0373777c24) Thanks [@jribbink](https://github.com/jribbink)! - Make `txId` optional for `useFlowTransactionStatus` + +- [#2368](https://github.com/onflow/fcl-js/pull/2368) [`eca4617c2d4d10d85bad0324f6c6064489c3d1c3`](https://github.com/onflow/fcl-js/commit/eca4617c2d4d10d85bad0324f6c6064489c3d1c3) Thanks [@jribbink](https://github.com/jribbink)! - Add `useCrossVmBatchTransaction` function + +- [#2414](https://github.com/onflow/fcl-js/pull/2414) [`605f66c7a78f9ff1474a18b70298956b92f90bc1`](https://github.com/onflow/fcl-js/commit/605f66c7a78f9ff1474a18b70298956b92f90bc1) Thanks [@chasefleming](https://github.com/chasefleming)! - **BREAKING**: Rename `useFlowTransaction` as `useFlowTransactionStatus` + +- [#2367](https://github.com/onflow/fcl-js/pull/2367) [`9595af75eeffb0c91f9bb94b70fb0adf4db40eec`](https://github.com/onflow/fcl-js/commit/9595af75eeffb0c91f9bb94b70fb0adf4db40eec) Thanks [@jribbink](https://github.com/jribbink)! - Add `useFlowChainId` hook to the `@onflow/kit` package. + +### Patch Changes + +- [#2419](https://github.com/onflow/fcl-js/pull/2419) [`f498aa9fdb0739aef8905593bdbd05af9db3267a`](https://github.com/onflow/fcl-js/commit/f498aa9fdb0739aef8905593bdbd05af9db3267a) Thanks [@chasefleming](https://github.com/chasefleming)! - Update readme with `useFlowRevertibleRandom` hook + +- [#2417](https://github.com/onflow/fcl-js/pull/2417) [`8608416f4d26e40d3bfa464da7e988c8beb35336`](https://github.com/onflow/fcl-js/commit/8608416f4d26e40d3bfa464da7e988c8beb35336) Thanks [@jribbink](https://github.com/jribbink)! - Fix `useFlowRevertibleRandom` range + +- Updated dependencies [[`0b83658f62a428a70074d33875f264fbd48aff1e`](https://github.com/onflow/fcl-js/commit/0b83658f62a428a70074d33875f264fbd48aff1e), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab)]: + - @onflow/fcl@1.18.0 + ## 0.2.0-alpha.1 ### Minor Changes diff --git a/packages/kit/README.md b/packages/kit/README.md index 1440cdbce..60311b90a 100644 --- a/packages/kit/README.md +++ b/packages/kit/README.md @@ -42,6 +42,7 @@ import flowJson from "../flow.json" - `useFlowRevertibleRandom` - `useFlowMutate` - `useFlowTransaction` +- `useCrossVmTokenBalance` ## 📚 Full Documentation diff --git a/packages/kit/jest.config.js b/packages/kit/jest.config.js index b9911f0fa..82d748e40 100644 --- a/packages/kit/jest.config.js +++ b/packages/kit/jest.config.js @@ -5,4 +5,9 @@ module.exports = { "^preact": "/src/__mocks__/noop.ts", "\\.(css|less)$": "/src/mocks/file-mock.ts", }, + // This is a workaround with Jest v29 issues related to BigInt serialization + // It can be removed once Jest v30 is released and upgraded + // https://github.com/jestjs/jest/issues/11617 + workerThreads: true, + setupFiles: ["/src/jest-setup.ts"], } diff --git a/packages/kit/package.json b/packages/kit/package.json index 7fcf3d553..608624cac 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/kit", - "version": "0.2.0-alpha.1", + "version": "0.4.0", "description": "React library for interacting with the Flow blockchain", "license": "Apache-2.0", "author": "Flow Foundation", @@ -30,7 +30,7 @@ "@babel/preset-react": "^7.26.3", "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.7.0", - "@onflow/typedefs": "^1.6.0-alpha.0", + "@onflow/typedefs": "^1.6.0", "@testing-library/dom": "^10.4.0", "@types/jest": "^29.5.13", "@types/react": "^19.0.10", @@ -49,12 +49,11 @@ "@headlessui/react": "^2.2.2", "@tanstack/react-query": "^5.67.3", "@testing-library/react": "^16.2.0", - "postcss-cli": "^11.0.0", - "tailwind-merge": "^2.2.1", - "viem": "^2.28.1" + "tailwind-merge": "^3.3.1", + "viem": "^2.29.2" }, "peerDependencies": { - "@onflow/fcl": ">=1.18.0-alpha.1", + "@onflow/fcl": ">=1.18.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } diff --git a/packages/kit/src/constants.ts b/packages/kit/src/constants.ts new file mode 100644 index 000000000..4dfcb4eee --- /dev/null +++ b/packages/kit/src/constants.ts @@ -0,0 +1,32 @@ +export const CONTRACT_ADDRESSES = { + testnet: { + EVM: "0x8c5303eaa26202d6", + FungibleToken: "0x9a0766d93b6608b7", + NonFungibleToken: "0x631e88ae7f1d7c20", + ViewResolver: "0x631e88ae7f1d7c20", + MetadataViews: "0x631e88ae7f1d7c20", + FlowToken: "0x7e60df042a9c0868", + ScopedFTProviders: "0xdfc20aee650fcbdf", + FlowEVMBridge: "0xdfc20aee650fcbdf", + FlowEVMBridgeUtils: "0xdfc20aee650fcbdf", + FlowEVMBridgeConfig: "0xdfc20aee650fcbdf", + FungibleTokenMetadataViews: "0x9a0766d93b6608b7", + }, + mainnet: { + EVM: "0xe467b9dd11fa00df", + FungibleToken: "0xf233dcee88fe0abe", + NonFungibleToken: "0x1d7e57aa55817448", + ViewResolver: "0x1d7e57aa55817448", + MetadataViews: "0x1d7e57aa55817448", + FlowToken: "0x1654653399040a61", + ScopedFTProviders: "0x1e4aa0b87d10b141", + FlowEVMBridge: "0x1e4aa0b87d10b141", + FlowEVMBridgeUtils: "0x1e4aa0b87d10b141", + FlowEVMBridgeConfig: "0x1e4aa0b87d10b141", + FungibleTokenMetadataViews: "0xf233dcee88fe0abe", + }, +} + +export const CADENCE_UFIX64_PRECISION = 8 + +export const DEFAULT_EVM_GAS_LIMIT = "15000000" diff --git a/packages/kit/src/hooks/index.ts b/packages/kit/src/hooks/index.ts index 597e5ddca..30033aa51 100644 --- a/packages/kit/src/hooks/index.ts +++ b/packages/kit/src/hooks/index.ts @@ -7,4 +7,7 @@ export {useFlowMutate} from "./useFlowMutate" export {useFlowQuery} from "./useFlowQuery" export {useFlowRevertibleRandom} from "./useFlowRevertibleRandom" export {useCrossVmBatchTransaction} from "./useCrossVmBatchTransaction" +export {useCrossVmTokenBalance} from "./useCrossVmTokenBalance" export {useFlowTransactionStatus} from "./useFlowTransactionStatus" +export {useCrossVmSpendNft} from "./useCrossVmSpendNft" +export {useCrossVmSpendToken} from "./useCrossVmSpendToken" diff --git a/packages/kit/src/hooks/useCrossVmBatchTransaction.test.ts b/packages/kit/src/hooks/useCrossVmBatchTransaction.test.ts index c9ec48eee..1097aac67 100644 --- a/packages/kit/src/hooks/useCrossVmBatchTransaction.test.ts +++ b/packages/kit/src/hooks/useCrossVmBatchTransaction.test.ts @@ -21,7 +21,7 @@ describe("useBatchEvmTransaction", () => { const mockCalls = [ { address: "0x123", - abi: [{type: "function", name: "test"}], + abi: [{type: "function", name: "test"} as any], functionName: "test", args: [1, 2], gasLimit: BigInt(100000), @@ -30,18 +30,6 @@ describe("useBatchEvmTransaction", () => { ] const mockTxId = "0x123" - const mockTxResult = { - events: [ - { - type: "TransactionExecuted", - data: { - hash: ["1", "2", "3"], - errorCode: "0", - errorMessage: "", - }, - }, - ], - } beforeEach(() => { jest.clearAllMocks() @@ -53,15 +41,15 @@ describe("useBatchEvmTransaction", () => { describe("encodeCalls", () => { it("should encode calls correctly", () => { - const result = encodeCalls(mockCalls as any) + const result = encodeCalls(mockCalls) expect(result).toEqual([ - [ - {key: "to", value: "0x123"}, - {key: "data", value: ""}, - {key: "gasLimit", value: "100000"}, - {key: "value", value: "0"}, - ], + { + to: "0x123", + data: "", + gasLimit: "100000", + value: "0", + }, ]) }) }) @@ -92,9 +80,6 @@ describe("useBatchEvmTransaction", () => { describe("useCrossVmBatchTransaction", () => { test("should handle successful transaction", async () => { jest.mocked(fcl.mutate).mockResolvedValue(mockTxId) - jest.mocked(fcl.tx).mockReturnValue({ - onceExecuted: jest.fn().mockResolvedValue(mockTxResult), - } as any) let result: any let rerender: any @@ -112,18 +97,11 @@ describe("useBatchEvmTransaction", () => { await waitFor(() => result.current.isPending === false) expect(result.current.isError).toBe(false) - expect(result.current.data?.txId).toBe(mockTxId) - expect(result.current.data?.results).toHaveLength(1) - expect(result.current.data?.results[0].status).toBe("passed") + expect(result.current.data).toBe(mockTxId) }) - test("should handle failed transaction", async () => { - jest.mocked(fcl.mutate).mockResolvedValue(mockTxId) - jest.mocked(fcl.tx).mockReturnValue({ - onceExecuted: jest - .fn() - .mockRejectedValue(new Error("Transaction failed")), - } as any) + test("should handle error transaction", async () => { + jest.mocked(fcl.mutate).mockRejectedValue(new Error("Transaction failed")) let hookResult: any @@ -140,35 +118,8 @@ describe("useBatchEvmTransaction", () => { await waitFor(() => expect(hookResult.current.isPending).toBe(false)) - expect(hookResult.current.isError).toBe(false) - expect(hookResult.current.data?.results[0].status).toBe("failed") - expect(hookResult.current.data?.results[0].errorMessage).toBe( - "Transaction reverted" - ) - }) - - test("should handle skipped calls", async () => { - jest.mocked(fcl.mutate).mockResolvedValue(mockTxId) - jest.mocked(fcl.tx).mockReturnValue({ - onceExecuted: jest.fn().mockResolvedValue({events: []}), - } as any) - - let hookResult: any - - await act(async () => { - const {result} = renderHook(() => useCrossVmBatchTransaction(), { - wrapper: FlowProvider, - }) - hookResult = result - }) - - await act(async () => { - await hookResult.current.sendBatchTransaction({calls: mockCalls}) - }) - - await waitFor(() => expect(hookResult.current.isPending).toBe(false)) - - expect(hookResult.current.data?.results[0].status).toBe("skipped") + expect(hookResult.current.isError).toBe(true) + expect(hookResult.current.error?.message).toBe("Transaction failed") }) it("should handle missing chain ID", async () => { diff --git a/packages/kit/src/hooks/useCrossVmBatchTransaction.ts b/packages/kit/src/hooks/useCrossVmBatchTransaction.ts index 89219309d..b090c8f90 100644 --- a/packages/kit/src/hooks/useCrossVmBatchTransaction.ts +++ b/packages/kit/src/hooks/useCrossVmBatchTransaction.ts @@ -1,6 +1,7 @@ import * as fcl from "@onflow/fcl" -import {Abi, bytesToHex, encodeFunctionData} from "viem" +import {Abi, encodeFunctionData} from "viem" import { + UseMutateAsyncFunction, UseMutateFunction, useMutation, UseMutationOptions, @@ -8,56 +9,38 @@ import { } from "@tanstack/react-query" import {useFlowChainId} from "./useFlowChainId" import {useFlowQueryClient} from "../provider/FlowQueryClient" +import {DEFAULT_EVM_GAS_LIMIT} from "../constants" -interface useCrossVmBatchTransactionArgs { +interface UseCrossVmBatchTransactionMutateArgs { + calls: EvmBatchCall[] + mustPass?: boolean +} + +export interface UseCrossVmBatchTransactionArgs { mutation?: Omit< - UseMutationOptions< - { - txId: string - results: CallOutcome[] - }, - Error, - { - calls: EvmBatchCall[] - mustPass?: boolean - } - >, + UseMutationOptions, "mutationFn" > } -interface useCrossVmBatchTransactionResult +export interface UseCrossVmBatchTransactionResult extends Omit< - UseMutationResult< - { - txId: string - results: CallOutcome[] - }, - Error - >, + UseMutationResult, "mutate" | "mutateAsync" > { sendBatchTransaction: UseMutateFunction< - { - txId: string - results: CallOutcome[] - }, + string, Error, - { - calls: EvmBatchCall[] - mustPass?: boolean - } + UseCrossVmBatchTransactionMutateArgs + > + sendBatchTransactionAsync: UseMutateAsyncFunction< + string, + Error, + UseCrossVmBatchTransactionMutateArgs > - sendBatchTransactionAsync: (args: { - calls: EvmBatchCall[] - mustPass?: boolean - }) => Promise<{ - txId: string - results: CallOutcome[] - }> } -interface EvmBatchCall { +export interface EvmBatchCall { // The target EVM contract address (as a string) address: string // The contract ABI fragment @@ -71,33 +54,10 @@ interface EvmBatchCall { // The value to send with the call value?: bigint } -interface CallOutcome { - status: "passed" | "failed" | "skipped" - hash?: string - errorMessage?: string -} - -type EvmTransactionExecutedData = { - hash: string[] - index: string - type: string - payload: string[] - errorCode: string - errorMessage: string - gasConsumed: string - contractAddress: string - logs: string[] - blockHeight: string - returnedData: string[] - precompiledCalls: string[] - stateUpdateChecksum: string -} -// Helper to encode our ca lls using viem. -// Returns an array of objects with keys "address" and "data" (hex-encoded string without the "0x" prefix). export function encodeCalls( calls: EvmBatchCall[] -): Array> { +): Array<{to: string; data: string; gasLimit: string; value: string}> { return calls.map(call => { const encodedData = encodeFunctionData({ abi: call.abi, @@ -105,13 +65,13 @@ export function encodeCalls( args: call.args, }) - return [ - {key: "to", value: call.address}, - {key: "data", value: fcl.sansPrefix(encodedData) ?? ""}, - {key: "gasLimit", value: call.gasLimit?.toString() ?? "15000000"}, - {key: "value", value: call.value?.toString() ?? "0"}, - ] - }) as any + return { + to: call.address, + data: fcl.sansPrefix(encodedData) ?? "", + gasLimit: call.gasLimit?.toString() ?? DEFAULT_EVM_GAS_LIMIT, + value: call.value?.toString() ?? "0", + } + }) } const EVM_CONTRACT_ADDRESSES = { @@ -178,7 +138,7 @@ transaction(calls: [{String: AnyStruct}], mustPass: Bool) { */ export function useCrossVmBatchTransaction({ mutation: mutationOptions = {}, -}: useCrossVmBatchTransactionArgs = {}): useCrossVmBatchTransactionResult { +}: UseCrossVmBatchTransactionArgs = {}): UseCrossVmBatchTransactionResult { const chainId = useFlowChainId() const cadenceTx = chainId.data ? getCadenceBatchTransaction(chainId.data) @@ -203,7 +163,15 @@ export function useCrossVmBatchTransaction({ cadence: cadenceTx, args: (arg, t) => [ arg( - encodedCalls, + encodedCalls.map(call => [ + {key: "to", value: call.to}, + {key: "data", value: call.data}, + { + key: "gasLimit", + value: call.gasLimit, + }, + {key: "value", value: call.value}, + ]), t.Array( t.Dictionary([ {key: t.String, value: t.String}, @@ -218,50 +186,7 @@ export function useCrossVmBatchTransaction({ limit: 9999, }) - let txResult - try { - txResult = await fcl.tx(txId).onceExecuted() - } catch (txError) { - // If we land here, the transaction likely reverted. - // We can return partial or "failed" outcomes for all calls. - return { - txId, - results: calls.map(() => ({ - status: "failed" as const, - hash: undefined, - errorMessage: "Transaction reverted", - })), - } - } - - // Filter for TransactionExecuted events - const executedEvents = txResult.events.filter((e: any) => - e.type.includes("TransactionExecuted") - ) - - // Build a full outcomes array for every call. - // For any call index where no event exists, mark it as "skipped". - const results: CallOutcome[] = calls.map((_, index) => { - const eventData = executedEvents[index] - ?.data as EvmTransactionExecutedData - if (eventData) { - return { - hash: bytesToHex( - Uint8Array.from( - eventData.hash.map((x: string) => parseInt(x, 10)) - ) - ), - status: eventData.errorCode === "0" ? "passed" : "failed", - errorMessage: eventData.errorMessage, - } - } else { - return { - status: "skipped", - } - } - }) - - return {txId, results} + return txId }, retry: false, ...mutationOptions, diff --git a/packages/kit/src/hooks/useCrossVmSpendNft.test.ts b/packages/kit/src/hooks/useCrossVmSpendNft.test.ts new file mode 100644 index 000000000..298fd514a --- /dev/null +++ b/packages/kit/src/hooks/useCrossVmSpendNft.test.ts @@ -0,0 +1,167 @@ +import {renderHook, act, waitFor} from "@testing-library/react" +import * as fcl from "@onflow/fcl" +import {FlowProvider} from "../provider" +import { + getCrossVmSpendNftransaction, + useCrossVmSpendNft, +} from "./useCrossVmSpendNft" +import {useFlowChainId} from "./useFlowChainId" + +jest.mock("@onflow/fcl", () => require("../__mocks__/fcl").default) +jest.mock("./useFlowChainId", () => ({ + useFlowChainId: jest.fn(), +})) + +describe("useBatchEvmTransaction", () => { + const mockCalls = [ + { + address: "0x123", + abi: [{type: "function", name: "test"}], + functionName: "test", + args: [1, 2], + gasLimit: BigInt(100000), + value: BigInt(0), + }, + ] + + const mockTxId = "0x123" + const mockTxResult = { + events: [ + { + type: "TransactionExecuted", + data: { + hash: ["1", "2", "3"], + errorCode: "0", + errorMessage: "", + }, + }, + ], + } + + beforeEach(() => { + jest.clearAllMocks() + jest.mocked(useFlowChainId).mockReturnValue({ + data: "mainnet", + isLoading: false, + } as any) + }) + + describe("getCrossVmSpendNftTransaction", () => { + it("should return correct cadence for mainnet", () => { + const result = getCrossVmSpendNftransaction("mainnet") + expect(result).toContain("import EVM from 0xe467b9dd11fa00df") + }) + + it("should return correct cadence for testnet", () => { + const result = getCrossVmSpendNftransaction("testnet") + expect(result).toContain("import EVM from 0x8c5303eaa26202d6") + }) + + it("should throw error for unsupported chain", () => { + expect(() => getCrossVmSpendNftransaction("unsupported")).toThrow( + "Unsupported chain: unsupported" + ) + }) + }) + + describe("useCrossVmBatchTransaction", () => { + test("should handle successful transaction", async () => { + jest.mocked(fcl.mutate).mockResolvedValue(mockTxId) + jest.mocked(fcl.tx).mockReturnValue({ + onceExecuted: jest.fn().mockResolvedValue(mockTxResult), + } as any) + + let result: any + let rerender: any + await act(async () => { + ;({result, rerender} = renderHook(useCrossVmSpendNft, { + wrapper: FlowProvider, + })) + }) + + await act(async () => { + await result.current.spendNft({ + calls: mockCalls, + nftIdentifier: "nft123", + nftIds: ["1", "2"], + }) + rerender() + }) + + await waitFor(() => result.current.isPending === false) + + expect(result.current.isError).toBe(false) + expect(result.current.data).toBe(mockTxId) + }) + + it("should handle missing chain ID", async () => { + ;(useFlowChainId as jest.Mock).mockReturnValue({ + data: null, + isLoading: false, + }) + + let hookResult: any + + await act(async () => { + const {result} = renderHook(() => useCrossVmSpendNft(), { + wrapper: FlowProvider, + }) + hookResult = result + }) + + await act(async () => { + await hookResult.current.spendNft({calls: mockCalls}) + }) + + await waitFor(() => expect(hookResult.current.isError).toBe(true)) + expect(hookResult.current.error?.message).toBe("No current chain found") + }) + + it("should handle loading chain ID", async () => { + ;(useFlowChainId as jest.Mock).mockReturnValue({ + data: null, + isLoading: true, + }) + + let hookResult: any + + await act(async () => { + const {result} = renderHook(() => useCrossVmSpendNft(), { + wrapper: FlowProvider, + }) + hookResult = result + }) + + await act(async () => { + await hookResult.current.spendNft(mockCalls) + }) + + await waitFor(() => expect(hookResult.current.isError).toBe(true)) + expect(hookResult.current.error?.message).toBe("No current chain found") + }) + + it("should handle mutation error", async () => { + ;(fcl.mutate as jest.Mock).mockRejectedValue(new Error("Mutation failed")) + + let hookResult: any + + await act(async () => { + const {result} = renderHook(() => useCrossVmSpendNft(), { + wrapper: FlowProvider, + }) + hookResult = result + }) + + await act(async () => { + await hookResult.current.spendNft({ + calls: mockCalls, + nftIdentifier: "nft123", + nftIds: ["1", "2"], + }) + }) + + await waitFor(() => expect(hookResult.current.isError).toBe(true)) + expect(hookResult.current.error?.message).toBe("Mutation failed") + }) + }) +}) diff --git a/packages/kit/src/hooks/useCrossVmSpendNft.ts b/packages/kit/src/hooks/useCrossVmSpendNft.ts new file mode 100644 index 000000000..1e3b46f3f --- /dev/null +++ b/packages/kit/src/hooks/useCrossVmSpendNft.ts @@ -0,0 +1,281 @@ +import * as fcl from "@onflow/fcl" +import { + UseMutateAsyncFunction, + UseMutateFunction, + useMutation, + UseMutationOptions, + UseMutationResult, +} from "@tanstack/react-query" +import {useFlowChainId} from "./useFlowChainId" +import {useFlowQueryClient} from "../provider/FlowQueryClient" +import {encodeCalls, EvmBatchCall} from "./useCrossVmBatchTransaction" +import {CONTRACT_ADDRESSES} from "../constants" + +export interface UseCrossVmSpendNftTxArgs { + mutation?: Omit< + UseMutationOptions, + "mutationFn" + > +} + +export interface UseCrossVmSpendNftTxMutateArgs { + nftIdentifier: string + nftIds: string[] + calls: EvmBatchCall[] +} + +export interface UseCrossVmSpendNftTxResult + extends Omit, "mutate" | "mutateAsync"> { + spendNft: UseMutateFunction + spendNftAsync: UseMutateAsyncFunction< + string, + Error, + UseCrossVmSpendNftTxMutateArgs + > +} + +// Takes a chain id and returns the cadence tx with addresses set +export const getCrossVmSpendNftransaction = (chainId: string) => { + const contractAddresses = + CONTRACT_ADDRESSES[chainId as keyof typeof CONTRACT_ADDRESSES] + if (!contractAddresses) { + throw new Error(`Unsupported chain: ${chainId}`) + } + + return ` +import FungibleToken from ${contractAddresses.FungibleToken} +import NonFungibleToken from ${contractAddresses.NonFungibleToken} +import ViewResolver from ${contractAddresses.ViewResolver} +import MetadataViews from ${contractAddresses.MetadataViews} +import FlowToken from ${contractAddresses.FlowToken} + +import ScopedFTProviders from ${contractAddresses.ScopedFTProviders} + +import EVM from ${contractAddresses.EVM} + +import FlowEVMBridge from ${contractAddresses.FlowEVMBridge} +import FlowEVMBridgeConfig from ${contractAddresses.FlowEVMBridgeConfig} +import FlowEVMBridgeUtils from ${contractAddresses.FlowEVMBridgeUtils} + +/// Bridges NFTs (from the same collection) from the signer's collection in Cadence to the signer's COA in FlowEVM +/// and then performs an arbitrary number of calls afterwards to potentially do things +/// with the bridged NFTs +/// +/// NOTE: This transaction also onboards the NFT to the bridge if necessary which may incur additional fees +/// than bridging an asset that has already been onboarded. +/// +/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier +/// @param ids: The Cadence NFT.id of the NFTs to bridge to EVM +/// @params evmContractAddressHexes, calldatas, gasLimits, values: Arrays of calldata +/// to be included in transaction calls to Flow EVM from the signer's COA. +/// The arrays are all expected to be of the same length +/// +transaction( + nftIdentifier: String, + ids: [UInt64], + evmContractAddressHexes: [String], + calldatas: [String], + gasLimits: [UInt64], + values: [UInt] +) { + let nftType: Type + let collection: auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection} + let coa: auth(EVM.Bridge, EVM.Call) &EVM.CadenceOwnedAccount + let requiresOnboarding: Bool + let scopedProvider: @ScopedFTProviders.ScopedFTProvider + + prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + pre { + (evmContractAddressHexes.length == calldatas.length) + && (calldatas.length == gasLimits.length) + && (gasLimits.length == values.length): + "Calldata array lengths must all be the same!" + } + + /* --- Reference the signer's CadenceOwnedAccount --- */ + // + // Borrow a reference to the signer's COA + self.coa = signer.storage.borrow(from: /storage/evm) + ?? panic("Could not borrow COA signer's account at path /storage/evm") + + /* --- Construct the NFT type --- */ + // + // Construct the NFT type from the provided identifier + self.nftType = CompositeType(nftIdentifier) + ?? panic("Could not construct NFT type from identifier: ".concat(nftIdentifier)) + // Parse the NFT identifier into its components + let nftContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.nftType) + ?? panic("Could not get contract address from identifier: ".concat(nftIdentifier)) + let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: self.nftType) + ?? panic("Could not get contract name from identifier: ".concat(nftIdentifier)) + + /* --- Retrieve the NFT --- */ + // + // Borrow a reference to the NFT collection, configuring if necessary + let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) + ?? panic("Could not borrow ViewResolver from NFT contract with name " + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) + let collectionData = viewResolver.resolveContractView( + resourceType: self.nftType, + viewType: Type() + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(self.nftType.identifier)) + self.collection = signer.storage.borrow( + from: collectionData.storagePath + ) ?? panic("Could not borrow a NonFungibleToken Collection from the signer's storage path " + .concat(collectionData.storagePath.toString())) + + // Withdraw the requested NFT & set a cap on the withdrawable bridge fee + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction + ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) + // Determine if the NFT requires onboarding - this impacts the fee required + self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) + ?? panic("Bridge does not support the requested asset type ".concat(nftIdentifier)) + // Add the onboarding fee if onboarding is necessary + if self.requiresOnboarding { + approxFee = approxFee + FlowEVMBridgeConfig.onboardFee + } + + /* --- Configure a ScopedFTProvider --- */ + // + // Issue and store bridge-dedicated Provider Capability in storage if necessary + if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { + let providerCap = signer.capabilities.storage.issue( + /storage/flowTokenVault + ) + signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) + } + // Copy the stored Provider capability and create a ScopedFTProvider + let providerCapCopy = signer.storage.copy>( + from: FlowEVMBridgeConfig.providerCapabilityStoragePath + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) + let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) + self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( + provider: providerCapCopy, + filters: [ providerFilter ], + expiration: getCurrentBlock().timestamp + 1.0 + ) + } + + execute { + if self.requiresOnboarding { + // Onboard the NFT to the bridge + FlowEVMBridge.onboardByType( + self.nftType, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + } + + // Iterate over requested IDs and bridge each NFT to the signer's COA in EVM + for id in ids { + // Withdraw the NFT & ensure it's the correct type + let nft <-self.collection.withdraw(withdrawID: id) + assert( + nft.getType() == self.nftType, + message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier) + .concat(", received: ").concat(nft.getType().identifier) + ) + // Execute the bridge to EVM for the current ID + self.coa.depositNFT( + nft: <-nft, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + } + + // Destroy the ScopedFTProvider + destroy self.scopedProvider + + // Perform all the calls + for index, evmAddressHex in evmContractAddressHexes { + let evmAddress = EVM.addressFromString(evmAddressHex) + + let valueBalance = EVM.Balance(attoflow: values[index]) + let callResult = self.coa.call( + to: evmAddress, + data: calldatas[index].decodeHex(), + gasLimit: gasLimits[index], + value: valueBalance + ) + assert( + callResult.status == EVM.Status.successful, + message: "Call failed with address \(evmAddressHex) and calldata \(calldatas[index]) with error \(callResult.errorMessage)" + ) + } + } +} +` +} + +/** + * Hook to send a cross-VM NFT spend transaction. This function will + * bundle multiple EVM calls into one atomic Cadence transaction and return the transaction ID. + * + * Use `useCrossVmSpendNftStatus` to watch the status of the transaction and get the transaction id + result of each EVM call. + * + * @returns The mutation object used to send the transaction. + */ +export function useCrossVmSpendNft({ + mutation: mutationOptions = {}, +}: UseCrossVmSpendNftTxArgs = {}): UseCrossVmSpendNftTxResult { + const chainId = useFlowChainId() + const cadenceTx = chainId.data + ? getCrossVmSpendNftransaction(chainId.data) + : null + + const queryClient = useFlowQueryClient() + const mutation = useMutation( + { + mutationFn: async ({ + nftIdentifier, + nftIds, + calls, + }: UseCrossVmSpendNftTxMutateArgs) => { + if (!cadenceTx) { + throw new Error("No current chain found") + } + const encodedCalls = encodeCalls(calls) + + const txId = await fcl.mutate({ + cadence: cadenceTx, + args: (arg, t) => [ + arg(nftIdentifier, t.String), + arg(nftIds, t.Array(t.UInt64)), + arg( + encodedCalls.map(call => call.to), + t.Array(t.String) + ), + arg( + encodedCalls.map(call => call.data), + t.Array(t.String) + ), + arg( + encodedCalls.map(call => call.gasLimit), + t.Array(t.UInt64) + ), + arg( + encodedCalls.map(call => call.value), + t.Array(t.UInt) + ), + ], + limit: 9999, + }) + + return txId + }, + retry: false, + ...mutationOptions, + }, + queryClient + ) + + const {mutate: spendNft, mutateAsync: spendNftAsync, ...rest} = mutation + + return { + spendNft, + spendNftAsync, + ...rest, + } +} diff --git a/packages/kit/src/hooks/useCrossVmSpendToken.test.ts b/packages/kit/src/hooks/useCrossVmSpendToken.test.ts new file mode 100644 index 000000000..b0ab06c45 --- /dev/null +++ b/packages/kit/src/hooks/useCrossVmSpendToken.test.ts @@ -0,0 +1,175 @@ +import {renderHook, act, waitFor} from "@testing-library/react" +import * as fcl from "@onflow/fcl" +import {FlowProvider} from "../provider" +import { + getCrossVmSpendTokenTransaction, + useCrossVmSpendToken, +} from "./useCrossVmSpendToken" +import {useFlowChainId} from "./useFlowChainId" + +jest.mock("@onflow/fcl", () => require("../__mocks__/fcl").default) +jest.mock("./useFlowChainId", () => ({ + useFlowChainId: jest.fn(), +})) + +describe("useCrossVmSpendToken", () => { + const mockCalls = [ + { + address: "0x123", + abi: [{type: "function", name: "test"}], + functionName: "test", + args: [1, 2], + gasLimit: BigInt(100000), + value: BigInt(0), + }, + ] + + const mockTxId = "0x123" + const mockTxResult = { + events: [ + { + type: "TransactionExecuted", + data: { + hash: ["1", "2", "3"], + errorCode: "0", + errorMessage: "", + }, + }, + ], + } + + beforeEach(() => { + jest.clearAllMocks() + jest.mocked(useFlowChainId).mockReturnValue({ + data: "mainnet", + isLoading: false, + } as any) + }) + + describe("getCrossVmSpendTokenTransaction", () => { + it("should return correct cadence for mainnet", () => { + const result = getCrossVmSpendTokenTransaction("mainnet") + expect(result).toContain("import EVM from 0xe467b9dd11fa00df") + }) + + it("should return correct cadence for testnet", () => { + const result = getCrossVmSpendTokenTransaction("testnet") + expect(result).toContain("import EVM from 0x8c5303eaa26202d6") + }) + + it("should throw error for unsupported chain", () => { + expect(() => getCrossVmSpendTokenTransaction("unsupported")).toThrow( + "Unsupported chain: unsupported" + ) + }) + }) + + describe("useCrossVmBatchTransaction", () => { + test("should handle successful transaction", async () => { + jest.mocked(fcl.mutate).mockResolvedValue(mockTxId) + jest.mocked(fcl.tx).mockReturnValue({ + onceExecuted: jest.fn().mockResolvedValue(mockTxResult), + } as any) + + let result: any + let rerender: any + await act(async () => { + ;({result, rerender} = renderHook(useCrossVmSpendToken, { + wrapper: FlowProvider, + })) + }) + + await act(async () => { + await result.current.spendToken({ + calls: mockCalls, + vaultIdentifier: "A.1234.Token.Vault", + amount: "100.0", + }) + rerender() + }) + + await waitFor(() => result.current.isPending === false) + + expect(result.current.isError).toBe(false) + expect(result.current.data).toBe(mockTxId) + }) + + it("should handle missing chain ID", async () => { + ;(useFlowChainId as jest.Mock).mockReturnValue({ + data: null, + isLoading: false, + }) + + let hookResult: any + + await act(async () => { + const {result} = renderHook(() => useCrossVmSpendToken(), { + wrapper: FlowProvider, + }) + hookResult = result + }) + + await act(async () => { + await hookResult.current.spendToken({ + calls: mockCalls, + vaultIdentifier: "A.1234.Token.Vault", + amount: "100.0", + }) + }) + + await waitFor(() => expect(hookResult.current.isError).toBe(true)) + expect(hookResult.current.error?.message).toBe("No current chain found") + }) + + it("should handle loading chain ID", async () => { + ;(useFlowChainId as jest.Mock).mockReturnValue({ + data: null, + isLoading: true, + }) + + let hookResult: any + + await act(async () => { + const {result} = renderHook(() => useCrossVmSpendToken(), { + wrapper: FlowProvider, + }) + hookResult = result + }) + + await act(async () => { + await hookResult.current.spendToken({ + calls: mockCalls, + vaultIdentifier: "A.1234.Token.Vault", + amount: "100.0", + }) + }) + + await waitFor(() => expect(hookResult.current.isError).toBe(true)) + expect(hookResult.current.error?.message).toBe("No current chain found") + }) + + it("should handle mutation error", async () => { + ;(fcl.mutate as jest.Mock).mockRejectedValue(new Error("Mutation failed")) + + let hookResult: any + + await act(async () => { + const {result} = renderHook(() => useCrossVmSpendToken(), { + wrapper: FlowProvider, + }) + hookResult = result + }) + + await act(async () => { + await hookResult.current.spendToken({ + calls: mockCalls, + vaultIdentifier: "A.1234.Token.Vault", + amount: "100.0", + }) + }) + + await waitFor(() => expect(hookResult.current.isError).toBe(true)) + expect(hookResult.current.error?.message).toBe("Mutation failed") + }) + }) +}) diff --git a/packages/kit/src/hooks/useCrossVmSpendToken.ts b/packages/kit/src/hooks/useCrossVmSpendToken.ts new file mode 100644 index 000000000..64c36e191 --- /dev/null +++ b/packages/kit/src/hooks/useCrossVmSpendToken.ts @@ -0,0 +1,272 @@ +import * as fcl from "@onflow/fcl" +import { + UseMutateAsyncFunction, + UseMutateFunction, + useMutation, + UseMutationOptions, + UseMutationResult, +} from "@tanstack/react-query" +import {useFlowChainId} from "./useFlowChainId" +import {useFlowQueryClient} from "../provider/FlowQueryClient" +import {encodeCalls, EvmBatchCall} from "./useCrossVmBatchTransaction" +import {CONTRACT_ADDRESSES} from "../constants" + +export interface UseCrossVmSpendTokenArgs { + mutation?: Omit< + UseMutationOptions, + "mutationFn" + > +} + +export interface UseCrossVmSpendTokenMutateArgs { + vaultIdentifier: string + amount: string + calls: EvmBatchCall[] +} + +export interface UseCrossVmSpendTokenResult + extends Omit, "mutate" | "mutateAsync"> { + spendToken: UseMutateFunction + spendTokenAsync: UseMutateAsyncFunction< + string, + Error, + UseCrossVmSpendTokenMutateArgs + > +} + +// Takes a chain id and returns the cadence tx with addresses set +export const getCrossVmSpendTokenTransaction = (chainId: string) => { + const contractAddresses = + CONTRACT_ADDRESSES[chainId as keyof typeof CONTRACT_ADDRESSES] + if (!contractAddresses) { + throw new Error(`Unsupported chain: ${chainId}`) + } + + return ` +import FungibleToken from ${contractAddresses.FungibleToken} +import ViewResolver from ${contractAddresses.ViewResolver} +import FungibleTokenMetadataViews from ${contractAddresses.FungibleTokenMetadataViews} +import FlowToken from ${contractAddresses.FlowToken} + +import ScopedFTProviders from ${contractAddresses.ScopedFTProviders} + +import EVM from ${contractAddresses.EVM} + +import FlowEVMBridge from ${contractAddresses.FlowEVMBridge} +import FlowEVMBridgeConfig from ${contractAddresses.FlowEVMBridgeConfig} +import FlowEVMBridgeUtils from ${contractAddresses.FlowEVMBridgeUtils} + +/// Bridges a Vault from the signer's storage to the signer's COA in EVM.Account +/// and then executes an arbitrary number of EVM transactions. +/// +/// NOTE: This transaction also onboards the Vault to the bridge if necessary which may incur additional fees +/// than bridging an asset that has already been onboarded. +/// +/// @param vaultIdentifier: The Cadence type identifier of the FungibleToken Vault to bridge +/// - e.g. vault.getType().identifier +/// @param amount: The amount of tokens to bridge from EVM +/// @params evmContractAddressHexes, calldatas, gasLimits, values: Arrays of calldata +/// to be included in transaction calls to Flow EVM from the signer's COA. +/// The arrays are all expected to be of the same length +/// +/// +transaction( + vaultIdentifier: String, + amount: UFix64, + evmContractAddressHexes: [String], + calldatas: [String], + gasLimits: [UInt64], + values: [UInt] +) { + + let sentVault: @{FungibleToken.Vault} + let coa: auth(EVM.Bridge, EVM.Call) &EVM.CadenceOwnedAccount + let requiresOnboarding: Bool + let scopedProvider: @ScopedFTProviders.ScopedFTProvider + + prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + pre { + (evmContractAddressHexes.length == calldatas.length) + && (calldatas.length == gasLimits.length) + && (gasLimits.length == values.length): + "Calldata array lengths must all be the same!" + } + + /* --- Reference the signer's CadenceOwnedAccount --- */ + // + // Borrow a reference to the signer's COA + self.coa = signer.storage.borrow(from: /storage/evm) + ?? panic("Could not borrow COA signer's account at path /storage/evm") + + /* --- Construct the Vault type --- */ + // + // Construct the Vault type from the provided identifier + let vaultType = CompositeType(vaultIdentifier) + ?? panic("Could not construct Vault type from identifier: ".concat(vaultIdentifier)) + // Parse the Vault identifier into its components + let tokenContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: vaultType) + ?? panic("Could not get contract address from identifier: ".concat(vaultIdentifier)) + let tokenContractName = FlowEVMBridgeUtils.getContractName(fromType: vaultType) + ?? panic("Could not get contract name from identifier: ".concat(vaultIdentifier)) + + /* --- Retrieve the funds --- */ + // + // Borrow a reference to the FungibleToken Vault + let viewResolver = getAccount(tokenContractAddress).contracts.borrow<&{ViewResolver}>(name: tokenContractName) + ?? panic("Could not borrow ViewResolver from FungibleToken contract with name" + .concat(tokenContractName).concat(" and address ") + .concat(tokenContractAddress.toString())) + let vaultData = viewResolver.resolveContractView( + resourceType: vaultType, + viewType: Type() + ) as! FungibleTokenMetadataViews.FTVaultData? + ?? panic("Could not resolve FTVaultData view for Vault type ".concat(vaultType.identifier)) + let vault = signer.storage.borrow( + from: vaultData.storagePath + ) ?? panic("Could not borrow FungibleToken Vault from storage path ".concat(vaultData.storagePath.toString())) + + // Withdraw the requested balance & set a cap on the withdrawable bridge fee + self.sentVault <- vault.withdraw(amount: amount) + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction + ) + // Determine if the Vault requires onboarding - this impacts the fee required + self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.sentVault.getType()) + ?? panic("Bridge does not support the requested asset type ".concat(vaultIdentifier)) + if self.requiresOnboarding { + approxFee = approxFee + FlowEVMBridgeConfig.onboardFee + } + + /* --- Configure a ScopedFTProvider --- */ + // + // Issue and store bridge-dedicated Provider Capability in storage if necessary + if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { + let providerCap = signer.capabilities.storage.issue( + /storage/flowTokenVault + ) + signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) + } + // Copy the stored Provider capability and create a ScopedFTProvider + let providerCapCopy = signer.storage.copy>( + from: FlowEVMBridgeConfig.providerCapabilityStoragePath + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) + let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) + self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( + provider: providerCapCopy, + filters: [ providerFilter ], + expiration: getCurrentBlock().timestamp + 1.0 + ) + } + + pre { + self.sentVault.getType().identifier == vaultIdentifier: + "Attempting to send invalid vault type - requested: ".concat(vaultIdentifier) + .concat(", sending: ").concat(self.sentVault.getType().identifier) + } + + execute { + if self.requiresOnboarding { + // Onboard the Vault to the bridge + FlowEVMBridge.onboardByType( + self.sentVault.getType(), + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + } + // Execute the bridge + self.coa.depositTokens( + vault: <-self.sentVault, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + // Destroy the ScopedFTProvider + destroy self.scopedProvider + + // Perform all the calls + for index, evmAddressHex in evmContractAddressHexes { + let evmAddress = EVM.addressFromString(evmAddressHex) + + let valueBalance = EVM.Balance(attoflow: values[index]) + let callResult = self.coa.call( + to: evmAddress, + data: calldatas[index].decodeHex(), + gasLimit: gasLimits[index], + value: valueBalance + ) + assert( + callResult.status == EVM.Status.successful, + message: "Call failed with address \(evmAddressHex) and calldata \(calldatas[index]) with error \(callResult.errorMessage)" + ) + } + } +} +` +} + +/** + * Hook to send a cross-VM FT spend transaction. This function will + * bundle multiple EVM calls into one atomic Cadence transaction and return the transaction ID. + * + * @returns The mutation object used to send the transaction. + */ +export function useCrossVmSpendToken({ + mutation: mutationOptions = {}, +}: UseCrossVmSpendTokenArgs = {}): UseCrossVmSpendTokenResult { + const chainId = useFlowChainId() + const cadenceTx = chainId.data + ? getCrossVmSpendTokenTransaction(chainId.data) + : null + + const queryClient = useFlowQueryClient() + const mutation = useMutation( + { + mutationFn: async ({ + vaultIdentifier, + amount, + calls, + }: UseCrossVmSpendTokenMutateArgs) => { + if (!cadenceTx) { + throw new Error("No current chain found") + } + const encodedCalls = encodeCalls(calls) + + const txId = await fcl.mutate({ + cadence: cadenceTx, + args: (arg, t) => [ + arg(vaultIdentifier, t.String), + arg(amount, t.UFix64), + arg( + encodedCalls.map(call => call.to), + t.Array(t.String) + ), + arg( + encodedCalls.map(call => call.data), + t.Array(t.String) + ), + arg( + encodedCalls.map(call => call.gasLimit), + t.Array(t.UInt64) + ), + arg( + encodedCalls.map(call => call.value), + t.Array(t.UInt) + ), + ], + limit: 9999, + }) + + return txId + }, + retry: false, + ...mutationOptions, + }, + queryClient + ) + + const {mutate: spendToken, mutateAsync: spendTokenAsync, ...rest} = mutation + + return { + spendToken, + spendTokenAsync, + ...rest, + } +} diff --git a/packages/kit/src/hooks/useCrossVmTokenBalance.test.ts b/packages/kit/src/hooks/useCrossVmTokenBalance.test.ts new file mode 100644 index 000000000..9a05a7327 --- /dev/null +++ b/packages/kit/src/hooks/useCrossVmTokenBalance.test.ts @@ -0,0 +1,123 @@ +import {FlowProvider} from "../provider/FlowProvider" +import {useFlowChainId} from "./useFlowChainId" +import {useFlowQuery} from "./useFlowQuery" +import {useCrossVmTokenBalance} from "./useCrossVmTokenBalance" +import {act, renderHook, waitFor} from "@testing-library/react" + +jest.mock("@onflow/fcl", () => require("../__mocks__/fcl").default) +jest.mock("./useFlowQuery") +jest.mock("./useFlowChainId") + +describe("useCrossVmTokenBalance", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + test("should return null when data is undefined", async () => { + const mockQueryResult = { + data: undefined, + isLoading: false, + isError: false, + error: null, + } as unknown as ReturnType + + jest.mocked(useFlowQuery).mockReturnValue(mockQueryResult) + jest.mocked(useFlowChainId).mockReturnValue({ + data: "testnet", + isLoading: false, + isError: false, + error: null, + isSuccess: true, + } as unknown as ReturnType) + + let result: ReturnType> + act(() => { + result = renderHook(useCrossVmTokenBalance, { + wrapper: FlowProvider, + initialProps: { + vaultIdentifier: "A.1234.Token.Vault", + owner: "0x5678", + }, + }) + }) + + await waitFor(() => expect(result.result.current).toBeDefined()) + + expect(jest.mocked(useFlowQuery)).toHaveBeenCalledWith( + expect.objectContaining({ + cadence: expect.any(String), + args: expect.any(Function), + query: { + enabled: true, + }, + }) + ) + + expect(result!.result.current).toEqual({ + ...mockQueryResult, + data: null, + }) + }) + + test("should return formatted balance when data is defined", async () => { + const mockQueryResult = { + data: ["4", "10.001", "200001"], + isLoading: false, + isError: false, + error: null, + } as unknown as ReturnType + + jest.mocked(useFlowQuery).mockReturnValue(mockQueryResult) + jest.mocked(useFlowChainId).mockReturnValue({ + data: "testnet", + isLoading: false, + isError: false, + error: null, + isSuccess: true, + } as unknown as ReturnType) + + let result: ReturnType> + act(() => { + result = renderHook(useCrossVmTokenBalance, { + wrapper: FlowProvider, + initialProps: { + vaultIdentifier: "A.1234.Token.Vault", + owner: "0x5678", + }, + }) + }) + + await waitFor(() => expect(result.result.current).toBeDefined()) + + expect(jest.mocked(useFlowQuery)).toHaveBeenCalledWith( + expect.objectContaining({ + cadence: expect.any(String), + args: expect.any(Function), + query: { + enabled: true, + }, + }) + ) + + expect(result!.result.current).toEqual({ + ...mockQueryResult, + data: { + cadence: { + formatted: "10.001", + value: BigInt("1000100000"), + precision: 8, + }, + evm: { + formatted: "20.0001", + value: BigInt("200001"), + precision: 4, + }, + total: { + formatted: "30.0011", + value: BigInt("3000110000"), + precision: 8, + }, + }, + }) + }) +}) diff --git a/packages/kit/src/hooks/useCrossVmTokenBalance.ts b/packages/kit/src/hooks/useCrossVmTokenBalance.ts new file mode 100644 index 000000000..457f54f4e --- /dev/null +++ b/packages/kit/src/hooks/useCrossVmTokenBalance.ts @@ -0,0 +1,238 @@ +import {UseQueryOptions, UseQueryResult} from "@tanstack/react-query" +import {useFlowQuery} from "./useFlowQuery" +import {CADENCE_UFIX64_PRECISION, CONTRACT_ADDRESSES} from "../constants" +import {useFlowChainId} from "./useFlowChainId" +import {parseUnits, formatUnits} from "viem/utils" + +interface UseCrossVmTokenBalanceArgs { + owner?: string + erc20Address?: string + vaultIdentifier?: string + query?: Omit, "queryKey" | "queryFn"> +} + +interface TokenBalance { + value: bigint + formatted: string + precision: number +} + +interface UseCrossVmTokenBalanceData { + cadence: TokenBalance + evm: TokenBalance + combined: TokenBalance +} + +const getCrossVmTokenBalance = (network: "testnet" | "mainnet") => ` +import EVM from ${CONTRACT_ADDRESSES[network].EVM} +import FlowToken from ${CONTRACT_ADDRESSES[network].FlowToken} +import FungibleToken from ${CONTRACT_ADDRESSES[network].FungibleToken} +import FlowEVMBridgeUtils from ${CONTRACT_ADDRESSES[network].FlowEVMBridgeUtils} +import FlowEVMBridgeConfig from ${CONTRACT_ADDRESSES[network].FlowEVMBridgeConfig} +import FungibleTokenMetadataViews from ${CONTRACT_ADDRESSES[network].FungibleTokenMetadataViews} + +/// Returns the balance of the owner of a given Fungible Token +/// from their Cadence account and their COA +/// Accepts multiple optional arguments, so the caller can query +/// the token by its EVM ERC20 address or by its Cadence contract address and name +/// +/// @param owner: The Flow address of the owner +/// @param contractAddressArg: The optional address of the FT contract in Cadence +/// @param contractNameArg: The optional name of the FT contract in Cadence +/// @param erc20AddressHex: The optional ERC20 address of the FT to query +/// +/// @return An array that contains the balance information for the user's accounts +/// in this order: +/// decimals (UInt256), cadence Balance (UFix64), EVM Balance (UInt256), Total Balance (UInt256) +/// + +access(all) fun main( + owner: Address, + vaultIdentifier: String?, + erc20AddressHexArg: String? +): [AnyStruct] { + pre { + vaultIdentifier == nil ? erc20AddressHexArg != nil : true: + "If the Cadence contract information is not provided, the ERC20 contract address must be provided." + } + + var typeIdentifier: String = "" + var compType: Type? = nil + var contractAddress: Address? = nil + var contractName: String? = nil + var tokenEVMAddress: String? = nil + var cadenceBalance: UFix64 = 0.0 + var coaBalance: UInt256 = 0 + var decimals: UInt8 = 0 + + // If the caller provided the Cadence information, + // Construct the composite type + if vaultIdentifier != nil { + typeIdentifier = vaultIdentifier! + compType = CompositeType(typeIdentifier) + ?? panic("Could not construct Cadence type with \(typeIdentifier)") + + // Get the EVM address of the bridged version of the Cadence FT contract + if let evmAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: compType!) { + tokenEVMAddress = evmAddress.toString() + } + } else { + // If the caller provided the EVM information, + // get the Cadence type from the bridge + // If getting the Cadence type doesn't work, then we'll just return the EVM balance + tokenEVMAddress = erc20AddressHexArg! + let address = EVM.addressFromString(tokenEVMAddress!) + compType = FlowEVMBridgeConfig.getTypeAssociated(with: address) + } + + // Parse the FT identifier into its components if necessary + if compType != nil { + contractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: compType!) + contractName = FlowEVMBridgeUtils.getContractName(fromType: compType!) + } + + if let address = contractAddress { + // Borrow a reference to the FT contract + let resolverRef = getAccount(address) + .contracts.borrow<&{FungibleToken}>(name: contractName!) + ?? panic("Could not borrow FungibleToken reference to the contract. Make sure the provided contract name (" + .concat(contractName!).concat(") and address (").concat(address.toString()).concat(") are correct!")) + + // Use that reference to retrieve the FTView + let vaultData = resolverRef.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTVaultData? + ?? panic("Could not resolve FTVaultData view. The ".concat(contractName!) + .concat(" contract needs to implement the FTVaultData Metadata view in order to execute this transaction.")) + + // Get the Cadence balance of the token + cadenceBalance = getAccount(owner).capabilities.borrow<&{FungibleToken.Balance}>( + vaultData.metadataPath + )?.balance + ?? 0.0 + } + + // Get the COA from the owner's account + if let coa = getAuthAccount(owner) + .storage.borrow( + from: /storage/evm + ) + { + if let erc20Address = tokenEVMAddress { + // Get the COA address + let coaAddress = coa.address().toString() + + // Get the ERC20 balance of the COA + coaBalance = FlowEVMBridgeUtils.balanceOf( + owner: EVM.addressFromString(coaAddress), + evmContractAddress: EVM.addressFromString(erc20Address) + ) + + // Get the token decimals of the ERC20 contract + decimals = FlowEVMBridgeUtils.getTokenDecimals( + evmContractAddress: EVM.addressFromString(erc20Address) + ) + } + + if compType == Type<@FlowToken.Vault>() { + coaBalance = coaBalance! + UInt256(coa.address().balance().inAttoFLOW()) + } + } + + let balances = [decimals, cadenceBalance, coaBalance] + + return balances +} +` + +/** + * Returns the balance of the owner of a given Fungible Token across both Cadence and EVM accounts. + * @param param0 + * @returns + */ +export function useCrossVmTokenBalance(params: UseCrossVmTokenBalanceArgs) { + const chainIdResult = useFlowChainId() + const queryResult = useFlowQuery({ + cadence: chainIdResult.data + ? getCrossVmTokenBalance(chainIdResult.data as "testnet" | "mainnet") + : "", + args: (arg, t) => [ + params.owner + ? arg(params.owner, t.Address) + : arg(null, t.Optional(t.Address)), + arg( + "vaultIdentifier" in params && params.vaultIdentifier + ? params.vaultIdentifier + : null, + t.Optional(t.String) + ), + arg( + "erc20Address" in params && params.erc20Address + ? params.erc20Address + : null, + t.Optional(t.String) + ), + ], + query: { + ...params.query, + enabled: + (params.query?.enabled ?? true) && + !!chainIdResult.data && + !!params.owner && + (!!params.vaultIdentifier || !!params.erc20Address), + }, + }) + + if (chainIdResult.isError) { + return chainIdResult + } + + const data = queryResult.data as [string, string, string, string] | undefined + if (!data) { + return { + ...queryResult, + data: null, + } as UseQueryResult + } + + const [evmDecimals, cadenceBalance, evmBalance] = data + + // Convert the values to the max precision between Cadence and EVM + // to avoid precision loss when summing the two balances + const totalPrecision = Math.max(CADENCE_UFIX64_PRECISION, Number(evmDecimals)) + const totalPrecisionCadenceBalance = parseUnits( + cadenceBalance, + totalPrecision + ) + const totalPrecisionEvmBalance = parseUnits( + formatUnits(BigInt(evmBalance), Number(evmDecimals)), + totalPrecision + ) + + return { + ...queryResult, + data: data + ? { + cadence: { + formatted: formatUnits( + parseUnits(cadenceBalance, CADENCE_UFIX64_PRECISION), + CADENCE_UFIX64_PRECISION + ), + value: parseUnits(cadenceBalance, CADENCE_UFIX64_PRECISION), + precision: CADENCE_UFIX64_PRECISION, + }, + evm: { + formatted: formatUnits(BigInt(evmBalance), Number(evmDecimals)), + precision: Number(evmDecimals), + value: BigInt(evmBalance), + }, + total: { + formatted: formatUnits( + totalPrecisionCadenceBalance + totalPrecisionEvmBalance, + totalPrecision + ), + value: totalPrecisionCadenceBalance + totalPrecisionEvmBalance, + precision: totalPrecision, + }, + } + : null, + } as UseQueryResult +} diff --git a/packages/kit/src/hooks/useFlowChainId.test.ts b/packages/kit/src/hooks/useFlowChainId.test.ts index 244629c70..b3e5424a0 100644 --- a/packages/kit/src/hooks/useFlowChainId.test.ts +++ b/packages/kit/src/hooks/useFlowChainId.test.ts @@ -96,9 +96,12 @@ describe("useFlowChainId", () => { let hookResult: any await act(async () => { - const {result} = renderHook(() => useFlowChainId(customOptions), { - wrapper: FlowProvider, - }) + const {result} = renderHook( + () => useFlowChainId({query: customOptions}), + { + wrapper: FlowProvider, + } + ) hookResult = result }) diff --git a/packages/kit/src/hooks/useFlowChainId.ts b/packages/kit/src/hooks/useFlowChainId.ts index b554f9d4f..7a20bafef 100644 --- a/packages/kit/src/hooks/useFlowChainId.ts +++ b/packages/kit/src/hooks/useFlowChainId.ts @@ -4,15 +4,16 @@ import {useFlowQueryClient} from "../provider/FlowQueryClient" import {useCallback} from "react" import {useFlowConfig} from "./useFlowConfig" +interface UseFlowChainIdArgs { + query?: Omit, "queryKey" | "queryFn"> +} + /** * Gets the Flow chain ID. */ -export function useFlowChainId( - queryOptions: Omit< - UseQueryOptions, - "queryKey" | "queryFn" - > = {} -): UseQueryResult { +export function useFlowChainId({ + query: queryOptions = {}, +}: UseFlowChainIdArgs = {}): UseQueryResult { const queryClient = useFlowQueryClient() const config = useFlowConfig() diff --git a/packages/kit/src/hooks/useFlowQuery.test.ts b/packages/kit/src/hooks/useFlowQuery.test.ts index 6c4dc8030..c4cbe9b03 100644 --- a/packages/kit/src/hooks/useFlowQuery.test.ts +++ b/packages/kit/src/hooks/useFlowQuery.test.ts @@ -146,4 +146,41 @@ describe("useFlowQuery", () => { args: argsFunction, }) }) + + test("detects args changes", async () => { + const cadenceScript = "access(all) fun main(a: Int): Int { return a }" + const initialResult = 7 + const updatedResult = 42 + const queryMock = jest.mocked(fcl.query) + queryMock.mockResolvedValueOnce(initialResult) + + const argsFunction = (arg: typeof fcl.arg, t: typeof fcl.t) => [ + arg(7, t.Int), + ] + + let hookResult: any + let hookRerender: any + + await act(async () => { + const {result, rerender} = renderHook(useFlowQuery, { + wrapper: FlowProvider, + initialProps: {cadence: cadenceScript, args: argsFunction}, + }) + hookResult = result + hookRerender = rerender + }) + + await waitFor(() => expect(hookResult.current.isLoading).toBe(false)) + expect(hookResult.current.data).toEqual(initialResult) + + queryMock.mockResolvedValueOnce(updatedResult) + await act(() => { + hookRerender({ + cadence: cadenceScript, + args: (arg: typeof fcl.arg, t: typeof fcl.t) => [arg(42, t.Int)], + }) + }) + + await waitFor(() => expect(hookResult.current.data).toEqual(updatedResult)) + }) }) diff --git a/packages/kit/src/hooks/useFlowQuery.ts b/packages/kit/src/hooks/useFlowQuery.ts index 0b715cfdc..42f7b22ca 100644 --- a/packages/kit/src/hooks/useFlowQuery.ts +++ b/packages/kit/src/hooks/useFlowQuery.ts @@ -32,9 +32,15 @@ export function useFlowQuery({ return fcl.query({cadence, args}) }, [cadence, args]) + // Encode the arguments to a JSON-CDC object so they can be deterministically + // serialized and used as the query key. + const encodedArgs = args?.(fcl.arg, fcl.t)?.map((x: any) => + x.xform.asArgument(x.value) + ) + return useQuery( { - queryKey: ["flowQuery", cadence, args], + queryKey: ["flowQuery", cadence, encodedArgs], queryFn: fetchQuery, enabled: queryOptions.enabled ?? true, ...queryOptions, diff --git a/packages/kit/src/jest-setup.ts b/packages/kit/src/jest-setup.ts new file mode 100644 index 000000000..e58025af3 --- /dev/null +++ b/packages/kit/src/jest-setup.ts @@ -0,0 +1,3 @@ +import {TextEncoder, TextDecoder} from "util" + +Object.assign(global, {TextDecoder, TextEncoder}) diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index d35035fc5..206f8a756 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -1,5 +1,34 @@ # @onflow/sdk +## 1.9.0 + +### Minor Changes + +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `subscribeRaw`. + + These are only available when using a REST API endpoint and not supported by the deprecated GRPC transport. + + The following topics are now available: + + - `blocks` + - `block_headers` + - `block_digests` + - `transaction_statues` + - `events` + - `account_statuses` + + Please see the [Flow Developer Documentation](https://developers.flow.com/clients/fcl-js/) for more details on how to use these new methods. + +- [#2352](https://github.com/onflow/fcl-js/pull/2352) [`4d3bb084c1442552d6a1de1f53435d1aa3f600b0`](https://github.com/onflow/fcl-js/commit/4d3bb084c1442552d6a1de1f53435d1aa3f600b0) Thanks [@mfbz](https://github.com/mfbz)! - Refactored onflow/sdk package to improve TypeScript support + +### Patch Changes + +- [#2434](https://github.com/onflow/fcl-js/pull/2434) [`3ac616d64c9abcda32f0c450119f22fa479d5e89`](https://github.com/onflow/fcl-js/commit/3ac616d64c9abcda32f0c450119f22fa479d5e89) Thanks [@mfbz](https://github.com/mfbz)! - Fixed fcl.mutate hanging + +- Updated dependencies [[`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`0b83658f62a428a70074d33875f264fbd48aff1e`](https://github.com/onflow/fcl-js/commit/0b83658f62a428a70074d33875f264fbd48aff1e), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab), [`694cd76807b7ca4441d1f8425ac4f8426cbc18fa`](https://github.com/onflow/fcl-js/commit/694cd76807b7ca4441d1f8425ac4f8426cbc18fa), [`4d3bb084c1442552d6a1de1f53435d1aa3f600b0`](https://github.com/onflow/fcl-js/commit/4d3bb084c1442552d6a1de1f53435d1aa3f600b0)]: + - @onflow/typedefs@1.6.0 + - @onflow/transport-http@1.13.0 + ## 1.9.0-alpha.1 ### Patch Changes @@ -10,7 +39,7 @@ ### Minor Changes -- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `rawSubscribe`. +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add real-time streaming methods `subscribe` and `subscribeRaw`. These are only available when using a REST API endpoint and not supported by the deprecated GRPC trasnport. diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 6e2df7266..af5bb5af6 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/sdk", - "version": "1.9.0-alpha.1", + "version": "1.9.0", "description": "Flow SDK", "license": "Apache-2.0", "author": "Flow Foundation", @@ -43,8 +43,8 @@ "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.2", "@onflow/rlp": "1.2.3", - "@onflow/transport-http": "1.13.0-alpha.0", - "@onflow/typedefs": "1.6.0-alpha.0", + "@onflow/transport-http": "1.13.0", + "@onflow/typedefs": "1.6.0", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts index a24a83a54..f86ee7e8c 100644 --- a/packages/sdk/src/sdk.ts +++ b/packages/sdk/src/sdk.ts @@ -5,7 +5,7 @@ export {resolve} from "./resolve/resolve" export { send, subscribe, - rawSubscribe, + subscribeRaw, SubscriptionsNotSupportedError, } from "./transport" export {decode} from "./decode/sdk-decode" diff --git a/packages/sdk/src/transport/index.ts b/packages/sdk/src/transport/index.ts index 3c22c862e..c3bd97ba7 100644 --- a/packages/sdk/src/transport/index.ts +++ b/packages/sdk/src/transport/index.ts @@ -1,5 +1,5 @@ export {send} from "./send/send" export {subscribe} from "./subscribe/subscribe" -export {rawSubscribe} from "./subscribe/raw-subscribe" +export {subscribeRaw} from "./subscribe/subscribe-raw" export {SubscriptionsNotSupportedError} from "./subscribe/errors" export * from "./subscribe/types" diff --git a/packages/sdk/src/transport/subscribe/raw-subscribe.ts b/packages/sdk/src/transport/subscribe/raw-subscribe.ts index f02a4f8d9..b51530dfd 100644 --- a/packages/sdk/src/transport/subscribe/raw-subscribe.ts +++ b/packages/sdk/src/transport/subscribe/raw-subscribe.ts @@ -2,7 +2,7 @@ import {config} from "@onflow/config" import {SdkTransport, SubscriptionTopic} from "@onflow/typedefs" import {getTransport} from "../get-transport" import {invariant} from "@onflow/util-invariant" -import {RawSubscribeParams} from "./types" +import {SubscribeRawParams} from "./types" /** * Subscribe to a topic without decoding the data. @@ -11,7 +11,7 @@ import {RawSubscribeParams} from "./types" * @returns A promise that resolves once the subscription is active. */ export function rawSubscribe( - {topic, args, onData, onError}: RawSubscribeParams, + {topic, args, onData, onError}: SubscribeRawParams, opts: { node?: string transport?: SdkTransport diff --git a/packages/sdk/src/transport/subscribe/subscribe-raw.test.ts b/packages/sdk/src/transport/subscribe/subscribe-raw.test.ts new file mode 100644 index 000000000..e5d370278 --- /dev/null +++ b/packages/sdk/src/transport/subscribe/subscribe-raw.test.ts @@ -0,0 +1,91 @@ +import {config} from "@onflow/config" +import {subscribeRaw} from "./subscribe-raw" +import { + Subscription, + SubscriptionArgs, + SubscriptionTopic, +} from "@onflow/typedefs" +import {getTransport} from "../get-transport" + +jest.mock("../get-transport") + +describe("subscribe", () => { + beforeEach(() => { + jest.resetAllMocks() + }) + + test("subscribes to a topic and returns subscription from transport", async () => { + const mockSub: jest.Mocked = { + unsubscribe: jest.fn(), + } + const mockTransport = { + subscribe: jest.fn().mockReturnValue(mockSub), + send: jest.fn(), + } + jest.mocked(getTransport).mockResolvedValue(mockTransport) + + const topic = "topic" as SubscriptionTopic + const args = {foo: "bar"} as SubscriptionArgs + const onData = jest.fn() + const onError = jest.fn() + + const sub = await config().overload( + { + "accessNode.api": "http://localhost:8080", + }, + () => { + return subscribeRaw({topic, args, onData, onError}) + } + ) + + expect(mockTransport.subscribe).toHaveBeenCalledTimes(1) + expect(mockTransport.subscribe).toHaveBeenCalledWith( + {topic, args, onData: onData, onError}, + {node: "http://localhost:8080"} + ) + + // Ensure that unsubscribe calls the transport's unsubscribe method + sub.unsubscribe() + await new Promise(setImmediate) + expect(mockSub.unsubscribe).toHaveBeenCalledTimes(1) + }) + + test("reports error from getTransport", async () => { + jest.mocked(getTransport).mockRejectedValue(new Error("Test Error")) + + const topic = "topic" as SubscriptionTopic + const args = {foo: "bar"} as SubscriptionArgs + const onData = jest.fn() + const onError = jest.fn() + + await config().overload( + { + "accessNode.api": "http://localhost:8080", + }, + () => { + return subscribeRaw({topic, args, onData, onError}) + } + ) + + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith(new Error("Test Error")) + }) + + test("reports error if accessNode.api is not defined", async () => { + const topic = "topic" as SubscriptionTopic + const args = {foo: "bar"} as SubscriptionArgs + const onData = jest.fn() + const onError = jest.fn() + + await config().overload({}, () => { + return subscribeRaw({topic, args, onData, onError}) + }) + + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + new Error( + `INVARIANT SDK Send Error: Either opts.node or "accessNode.api" in config must be defined.` + ) + ) + }) +}) diff --git a/packages/sdk/src/transport/subscribe/subscribe-raw.ts b/packages/sdk/src/transport/subscribe/subscribe-raw.ts new file mode 100644 index 000000000..f28069e5f --- /dev/null +++ b/packages/sdk/src/transport/subscribe/subscribe-raw.ts @@ -0,0 +1,58 @@ +import {config} from "@onflow/config" +import {SdkTransport, SubscriptionTopic} from "@onflow/typedefs" +import {getTransport} from "../get-transport" +import {invariant} from "@onflow/util-invariant" +import {SubscribeRawParams} from "./types" + +/** + * Subscribe to a topic without decoding the data. + * @param params - The parameters for the subscription. + * @param opts - Additional options for the subscription. + * @returns A promise that resolves once the subscription is active. + */ +export function subscribeRaw( + {topic, args, onData, onError}: SubscribeRawParams, + opts: { + node?: string + transport?: SdkTransport + } = {} +) { + async function subscribe() { + let transport: SdkTransport + let node: string + + try { + transport = await getTransport(opts) + node = opts?.node || (await config().get("accessNode.api")) + + invariant( + !!node, + `SDK Send Error: Either opts.node or "accessNode.api" in config must be defined.` + ) + } catch (e) { + onError(e instanceof Error ? e : new Error(String(e))) + return + } + + // Subscribe using the resolved transport + return transport.subscribe( + { + topic, + args, + onData, + onError, + }, + { + node, + ...opts, + } + ) + } + + let subscriptionPromise = subscribe() + return { + unsubscribe: () => { + subscriptionPromise.then(sub => sub?.unsubscribe?.()) + }, + } +} diff --git a/packages/sdk/src/transport/subscribe/subscribe.test.ts b/packages/sdk/src/transport/subscribe/subscribe.test.ts index 8ec15cc1a..1ed191aa4 100644 --- a/packages/sdk/src/transport/subscribe/subscribe.test.ts +++ b/packages/sdk/src/transport/subscribe/subscribe.test.ts @@ -5,10 +5,10 @@ import { SdkTransport, } from "@onflow/typedefs" import {subscribe} from "./subscribe" -import {rawSubscribe} from "./raw-subscribe" +import {subscribeRaw} from "./subscribe-raw" -jest.mock("./raw-subscribe") -const mockRawSubscribe = jest.mocked(rawSubscribe) +jest.mock("./subscribe-raw") +const mocksubscribeRaw = jest.mocked(subscribeRaw) describe("subscribe", () => { let mockSub: jest.Mocked = { @@ -17,7 +17,7 @@ describe("subscribe", () => { beforeEach(() => { jest.resetAllMocks() - mockRawSubscribe.mockReturnValue(mockSub) + mocksubscribeRaw.mockReturnValue(mockSub) }) test("subscribes to a topic and returns a subscription", async () => { @@ -34,8 +34,8 @@ describe("subscribe", () => { }) expect(sub).toBe(mockSub) - expect(mockRawSubscribe).toHaveBeenCalledTimes(1) - expect(mockRawSubscribe).toHaveBeenCalledWith( + expect(mocksubscribeRaw).toHaveBeenCalledTimes(1) + expect(mocksubscribeRaw).toHaveBeenCalledWith( {topic, args, onData: expect.any(Function), onError}, {} ) @@ -78,8 +78,8 @@ describe("subscribe", () => { ) expect(sub).toBe(mockSub) - expect(mockRawSubscribe).toHaveBeenCalledTimes(1) - expect(mockRawSubscribe).toHaveBeenCalledWith( + expect(mocksubscribeRaw).toHaveBeenCalledTimes(1) + expect(mocksubscribeRaw).toHaveBeenCalledWith( {topic, args, onData: expect.any(Function), onError}, {node} ) @@ -108,8 +108,8 @@ describe("subscribe", () => { ) expect(sub).toBe(mockSub) - expect(mockRawSubscribe).toHaveBeenCalledTimes(1) - expect(mockRawSubscribe).toHaveBeenCalledWith( + expect(mocksubscribeRaw).toHaveBeenCalledTimes(1) + expect(mocksubscribeRaw).toHaveBeenCalledWith( {topic, args, onData: expect.any(Function), onError}, {node, transport} ) diff --git a/packages/sdk/src/transport/subscribe/subscribe.ts b/packages/sdk/src/transport/subscribe/subscribe.ts index 0325b572d..51dfa53c3 100644 --- a/packages/sdk/src/transport/subscribe/subscribe.ts +++ b/packages/sdk/src/transport/subscribe/subscribe.ts @@ -1,5 +1,5 @@ import {SdkTransport, Subscription, SubscriptionTopic} from "@onflow/typedefs" -import {rawSubscribe} from "./raw-subscribe" +import {subscribeRaw} from "./subscribe-raw" import {decodeResponse} from "../../decode/decode" import {SubscribeParams} from "./types" @@ -16,7 +16,7 @@ export function subscribe( transport?: SdkTransport } = {} ): Subscription { - const sub = rawSubscribe( + const sub = subscribeRaw( { topic, args, diff --git a/packages/sdk/src/transport/subscribe/types.ts b/packages/sdk/src/transport/subscribe/types.ts index d228c8e2b..95cc16963 100644 --- a/packages/sdk/src/transport/subscribe/types.ts +++ b/packages/sdk/src/transport/subscribe/types.ts @@ -24,7 +24,7 @@ export type SubscribeParams = { onError: (error: Error) => void } -export type RawSubscribeParams = { +export type SubscribeRawParams = { /** * The topic to subscribe to. */ diff --git a/packages/transport-grpc/package.json b/packages/transport-grpc/package.json index ab482fd95..1bb6f07ac 100644 --- a/packages/transport-grpc/package.json +++ b/packages/transport-grpc/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.7.0", - "@onflow/sdk": "1.9.0-alpha.1", + "@onflow/sdk": "1.9.0", "jest": "^29.7.0" }, "source": "src/sdk-send-grpc.js", diff --git a/packages/transport-http/CHANGELOG.md b/packages/transport-http/CHANGELOG.md index bd7748e0e..047cdddf0 100644 --- a/packages/transport-http/CHANGELOG.md +++ b/packages/transport-http/CHANGELOG.md @@ -1,5 +1,24 @@ # @onflow/transport-http +## 1.13.0 + +### Minor Changes + +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add support for new WebSocket streaming methods. The following topics are now available: + + - `blocks` + - `block_headers` + - `block_digests` + - `transaction_statues` + - `events` + - `account_statuses` + +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Expose the `httpTransport` as a named export from `@onflow/transport-http` package. This follows the new object-style export for SDK transports and adds streaming support. + +### Patch Changes + +- [#2035](https://github.com/onflow/fcl-js/pull/2035) [`694cd76807b7ca4441d1f8425ac4f8426cbc18fa`](https://github.com/onflow/fcl-js/commit/694cd76807b7ca4441d1f8425ac4f8426cbc18fa) Thanks [@jribbink](https://github.com/jribbink)! - Add `parentVoterSignature` field to GetBlock request + ## 1.13.0-alpha.0 ### Minor Changes diff --git a/packages/transport-http/package.json b/packages/transport-http/package.json index 55a6b1c9b..d61fd54b3 100644 --- a/packages/transport-http/package.json +++ b/packages/transport-http/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/transport-http", - "version": "1.13.0-alpha.0", + "version": "1.13.0", "description": "Flow SDK HTTP Transport Module", "license": "Apache-2.0", "author": "Flow Foundation", @@ -15,7 +15,7 @@ "devDependencies": { "@onflow/fcl-bundle": "1.7.0", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.9.0-alpha.1", + "@onflow/sdk": "1.9.0", "@onflow/types": "1.4.1", "jest": "^29.7.0", "jest-websocket-mock": "^2.5.0", diff --git a/packages/typedefs/CHANGELOG.md b/packages/typedefs/CHANGELOG.md index 14575b329..49b2cbf62 100644 --- a/packages/typedefs/CHANGELOG.md +++ b/packages/typedefs/CHANGELOG.md @@ -1,5 +1,13 @@ # @onflow/typedefs +## 1.6.0 + +### Minor Changes + +- [#2201](https://github.com/onflow/fcl-js/pull/2201) [`b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab`](https://github.com/onflow/fcl-js/commit/b9c4ed3b95c2dc73698e45f353a6ef9a48f23cab) Thanks [@jribbink](https://github.com/jribbink)! - Add typedefs for streaming API + +- [#2352](https://github.com/onflow/fcl-js/pull/2352) [`4d3bb084c1442552d6a1de1f53435d1aa3f600b0`](https://github.com/onflow/fcl-js/commit/4d3bb084c1442552d6a1de1f53435d1aa3f600b0) Thanks [@mfbz](https://github.com/mfbz)! - Refactored onflow/sdk package to improve TypeScript support + ## 1.6.0-alpha.0 ### Minor Changes diff --git a/packages/typedefs/package.json b/packages/typedefs/package.json index efe6f57f3..79d2fe9ad 100644 --- a/packages/typedefs/package.json +++ b/packages/typedefs/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/typedefs", - "version": "1.6.0-alpha.0", + "version": "1.6.0", "description": "Flow JS Type Defs", "license": "Apache-2.0", "author": "Flow Foundation",