Remove @cosmjs/cosmwasm and @cosmjs/cosmwasm-launchpad
This commit is contained in:
parent
6d99d32350
commit
3912aaff88
228
.pnp.js
generated
228
.pnp.js
generated
@ -30,14 +30,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"name": "@cosmjs/cli",
|
||||
"reference": "workspace:packages/cli"
|
||||
},
|
||||
{
|
||||
"name": "@cosmjs/cosmwasm",
|
||||
"reference": "workspace:packages/cosmwasm"
|
||||
},
|
||||
{
|
||||
"name": "@cosmjs/cosmwasm-launchpad",
|
||||
"reference": "workspace:packages/cosmwasm-launchpad"
|
||||
},
|
||||
{
|
||||
"name": "@cosmjs/cosmwasm-stargate",
|
||||
"reference": "workspace:packages/cosmwasm-stargate"
|
||||
@ -104,8 +96,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"fallbackExclusionList": [
|
||||
["@cosmjs/amino", ["workspace:packages/amino"]],
|
||||
["@cosmjs/cli", ["workspace:packages/cli"]],
|
||||
["@cosmjs/cosmwasm", ["workspace:packages/cosmwasm"]],
|
||||
["@cosmjs/cosmwasm-launchpad", ["workspace:packages/cosmwasm-launchpad"]],
|
||||
["@cosmjs/cosmwasm-stargate", ["workspace:packages/cosmwasm-stargate"]],
|
||||
["@cosmjs/crypto", ["workspace:packages/crypto"]],
|
||||
["@cosmjs/encoding", ["workspace:packages/encoding"]],
|
||||
@ -236,14 +226,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"@cosmjs/cli",
|
||||
"workspace:packages/cli"
|
||||
],
|
||||
[
|
||||
"@cosmjs/cosmwasm",
|
||||
"workspace:packages/cosmwasm"
|
||||
],
|
||||
[
|
||||
"@cosmjs/cosmwasm-launchpad",
|
||||
"workspace:packages/cosmwasm-launchpad"
|
||||
],
|
||||
[
|
||||
"@cosmjs/cosmwasm-stargate",
|
||||
"workspace:packages/cosmwasm-stargate"
|
||||
@ -2873,7 +2855,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"packageLocation": "./",
|
||||
"packageDependencies": [
|
||||
["@cosmjs/amino", "workspace:packages/amino"],
|
||||
["@cosmjs/cosmwasm-launchpad", "workspace:packages/cosmwasm-launchpad"],
|
||||
["@cosmjs/cosmwasm-stargate", "workspace:packages/cosmwasm-stargate"],
|
||||
["@cosmjs/crypto", "workspace:packages/crypto"],
|
||||
["@cosmjs/encoding", "workspace:packages/encoding"],
|
||||
@ -3273,7 +3254,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"packageDependencies": [
|
||||
["@cosmjs/cli", "workspace:packages/cli"],
|
||||
["@cosmjs/amino", "workspace:packages/amino"],
|
||||
["@cosmjs/cosmwasm-launchpad", "workspace:packages/cosmwasm-launchpad"],
|
||||
["@cosmjs/cosmwasm-stargate", "workspace:packages/cosmwasm-stargate"],
|
||||
["@cosmjs/crypto", "workspace:packages/crypto"],
|
||||
["@cosmjs/encoding", "workspace:packages/encoding"],
|
||||
@ -3318,80 +3298,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"linkType": "SOFT",
|
||||
}]
|
||||
]],
|
||||
["@cosmjs/cosmwasm", [
|
||||
["workspace:packages/cosmwasm", {
|
||||
"packageLocation": "./packages/cosmwasm/",
|
||||
"packageDependencies": [
|
||||
["@cosmjs/cosmwasm", "workspace:packages/cosmwasm"],
|
||||
["@cosmjs/cosmwasm-launchpad", "workspace:packages/cosmwasm-launchpad"],
|
||||
["@types/eslint-plugin-prettier", "npm:3.1.0"],
|
||||
["@typescript-eslint/eslint-plugin", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.28.4"],
|
||||
["@typescript-eslint/parser", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.28.4"],
|
||||
["eslint", "npm:7.26.0"],
|
||||
["eslint-config-prettier", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:8.3.0"],
|
||||
["eslint-import-resolver-node", "npm:0.3.4"],
|
||||
["eslint-plugin-import", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:2.23.2"],
|
||||
["eslint-plugin-prettier", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:3.4.0"],
|
||||
["eslint-plugin-simple-import-sort", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:7.0.0"],
|
||||
["prettier", "npm:2.3.2"],
|
||||
["typedoc", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:0.21.4"],
|
||||
["typescript", "patch:typescript@npm%3A4.3.5#builtin<compat/typescript>::version=4.3.5&hash=ddfc1b"]
|
||||
],
|
||||
"linkType": "SOFT",
|
||||
}]
|
||||
]],
|
||||
["@cosmjs/cosmwasm-launchpad", [
|
||||
["workspace:packages/cosmwasm-launchpad", {
|
||||
"packageLocation": "./packages/cosmwasm-launchpad/",
|
||||
"packageDependencies": [
|
||||
["@cosmjs/cosmwasm-launchpad", "workspace:packages/cosmwasm-launchpad"],
|
||||
["@cosmjs/crypto", "workspace:packages/crypto"],
|
||||
["@cosmjs/encoding", "workspace:packages/encoding"],
|
||||
["@cosmjs/launchpad", "workspace:packages/launchpad"],
|
||||
["@cosmjs/math", "workspace:packages/math"],
|
||||
["@cosmjs/utils", "workspace:packages/utils"],
|
||||
["@istanbuljs/nyc-config-typescript", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:1.0.1"],
|
||||
["@types/eslint-plugin-prettier", "npm:3.1.0"],
|
||||
["@types/jasmine", "npm:3.7.4"],
|
||||
["@types/karma-firefox-launcher", "npm:2.1.0"],
|
||||
["@types/karma-jasmine", "npm:4.0.0"],
|
||||
["@types/karma-jasmine-html-reporter", "npm:1.5.1"],
|
||||
["@types/node", "npm:15.3.1"],
|
||||
["@types/pako", "npm:1.0.1"],
|
||||
["@typescript-eslint/eslint-plugin", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.28.4"],
|
||||
["@typescript-eslint/parser", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.28.4"],
|
||||
["eslint", "npm:7.26.0"],
|
||||
["eslint-config-prettier", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:8.3.0"],
|
||||
["eslint-import-resolver-node", "npm:0.3.4"],
|
||||
["eslint-plugin-import", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:2.23.2"],
|
||||
["eslint-plugin-prettier", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:3.4.0"],
|
||||
["eslint-plugin-simple-import-sort", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:7.0.0"],
|
||||
["esm", "npm:3.2.25"],
|
||||
["glob", "npm:7.1.7"],
|
||||
["jasmine", "npm:3.7.0"],
|
||||
["jasmine-core", "npm:3.7.1"],
|
||||
["jasmine-spec-reporter", "npm:6.0.0"],
|
||||
["karma", "npm:6.3.2"],
|
||||
["karma-chrome-launcher", "npm:3.1.0"],
|
||||
["karma-firefox-launcher", "npm:2.1.0"],
|
||||
["karma-jasmine", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.0.1"],
|
||||
["karma-jasmine-html-reporter", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:1.6.0"],
|
||||
["nyc", "npm:15.1.0"],
|
||||
["pako", "npm:2.0.3"],
|
||||
["prettier", "npm:2.3.2"],
|
||||
["readonly-date", "npm:1.0.0"],
|
||||
["ses", "npm:0.11.1"],
|
||||
["source-map-support", "npm:0.5.19"],
|
||||
["stream-browserify", "npm:3.0.0"],
|
||||
["ts-node", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:8.10.2"],
|
||||
["typedoc", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:0.21.4"],
|
||||
["typescript", "patch:typescript@npm%3A4.3.5#builtin<compat/typescript>::version=4.3.5&hash=ddfc1b"],
|
||||
["webpack", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:5.37.1"],
|
||||
["webpack-cli", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:4.7.0"]
|
||||
],
|
||||
"linkType": "SOFT",
|
||||
}]
|
||||
]],
|
||||
["@cosmjs/cosmwasm-stargate", [
|
||||
["workspace:packages/cosmwasm-stargate", {
|
||||
"packageLocation": "./packages/cosmwasm-stargate/",
|
||||
@ -5477,23 +5383,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:84adca422dcfddfebe74232825b4d9a5ef615fdab14f3f17f1a545c4531bd06dc6868a9c1d1e3abce6a67873f8d3e04c605bfaaf12cc7fa1a0495f9f023c0ec3#npm:1.0.3", {
|
||||
"packageLocation": "./.yarn/$$virtual/@webpack-cli-configtest-virtual-d4999b5de1/0/cache/@webpack-cli-configtest-npm-1.0.3-b6e357f778-df71875431.zip/node_modules/@webpack-cli/configtest/",
|
||||
"packageDependencies": [
|
||||
["@webpack-cli/configtest", "virtual:84adca422dcfddfebe74232825b4d9a5ef615fdab14f3f17f1a545c4531bd06dc6868a9c1d1e3abce6a67873f8d3e04c605bfaaf12cc7fa1a0495f9f023c0ec3#npm:1.0.3"],
|
||||
["@types/webpack", null],
|
||||
["@types/webpack-cli", null],
|
||||
["webpack", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:5.37.1"],
|
||||
["webpack-cli", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:4.7.0"]
|
||||
],
|
||||
"packagePeers": [
|
||||
"@types/webpack-cli",
|
||||
"@types/webpack",
|
||||
"webpack-cli",
|
||||
"webpack"
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:87e4e29d0a073486e83efd3fb6ccb7905fedbae979a2f77544c4e7ebf5a6bf9cb2ec8ed5b3be875e151cdf6e30b7c3d3d2c148a069709cbac3edecc2314b098d#npm:1.0.3", {
|
||||
"packageLocation": "./.yarn/$$virtual/@webpack-cli-configtest-virtual-c83273e6dc/0/cache/@webpack-cli-configtest-npm-1.0.3-b6e357f778-df71875431.zip/node_modules/@webpack-cli/configtest/",
|
||||
"packageDependencies": [
|
||||
@ -5737,20 +5626,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:84adca422dcfddfebe74232825b4d9a5ef615fdab14f3f17f1a545c4531bd06dc6868a9c1d1e3abce6a67873f8d3e04c605bfaaf12cc7fa1a0495f9f023c0ec3#npm:1.2.4", {
|
||||
"packageLocation": "./.yarn/$$virtual/@webpack-cli-info-virtual-b4f729ae1e/0/cache/@webpack-cli-info-npm-1.2.4-e4a2135f37-7a1b167669.zip/node_modules/@webpack-cli/info/",
|
||||
"packageDependencies": [
|
||||
["@webpack-cli/info", "virtual:84adca422dcfddfebe74232825b4d9a5ef615fdab14f3f17f1a545c4531bd06dc6868a9c1d1e3abce6a67873f8d3e04c605bfaaf12cc7fa1a0495f9f023c0ec3#npm:1.2.4"],
|
||||
["@types/webpack-cli", null],
|
||||
["envinfo", "npm:7.8.1"],
|
||||
["webpack-cli", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:4.7.0"]
|
||||
],
|
||||
"packagePeers": [
|
||||
"@types/webpack-cli",
|
||||
"webpack-cli"
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:87e4e29d0a073486e83efd3fb6ccb7905fedbae979a2f77544c4e7ebf5a6bf9cb2ec8ed5b3be875e151cdf6e30b7c3d3d2c148a069709cbac3edecc2314b098d#npm:1.2.4", {
|
||||
"packageLocation": "./.yarn/$$virtual/@webpack-cli-info-virtual-fe46807215/0/cache/@webpack-cli-info-npm-1.2.4-e4a2135f37-7a1b167669.zip/node_modules/@webpack-cli/info/",
|
||||
"packageDependencies": [
|
||||
@ -5977,21 +5852,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:84adca422dcfddfebe74232825b4d9a5ef615fdab14f3f17f1a545c4531bd06dc6868a9c1d1e3abce6a67873f8d3e04c605bfaaf12cc7fa1a0495f9f023c0ec3#npm:1.4.0", {
|
||||
"packageLocation": "./.yarn/$$virtual/@webpack-cli-serve-virtual-de2ba025af/0/cache/@webpack-cli-serve-npm-1.4.0-1f566be693-0a2495e2f1.zip/node_modules/@webpack-cli/serve/",
|
||||
"packageDependencies": [
|
||||
["@webpack-cli/serve", "virtual:84adca422dcfddfebe74232825b4d9a5ef615fdab14f3f17f1a545c4531bd06dc6868a9c1d1e3abce6a67873f8d3e04c605bfaaf12cc7fa1a0495f9f023c0ec3#npm:1.4.0"],
|
||||
["@types/webpack-cli", null],
|
||||
["webpack-cli", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:4.7.0"],
|
||||
["webpack-dev-server", null]
|
||||
],
|
||||
"packagePeers": [
|
||||
"@types/webpack-cli",
|
||||
"webpack-cli",
|
||||
"webpack-dev-server"
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:87e4e29d0a073486e83efd3fb6ccb7905fedbae979a2f77544c4e7ebf5a6bf9cb2ec8ed5b3be875e151cdf6e30b7c3d3d2c148a069709cbac3edecc2314b098d#npm:1.4.0", {
|
||||
"packageLocation": "./.yarn/$$virtual/@webpack-cli-serve-virtual-960c087e04/0/cache/@webpack-cli-serve-npm-1.4.0-1f566be693-0a2495e2f1.zip/node_modules/@webpack-cli/serve/",
|
||||
"packageDependencies": [
|
||||
@ -7116,7 +6976,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"packageDependencies": [
|
||||
["cosmjs-monorepo-root", "workspace:."],
|
||||
["@cosmjs/amino", "workspace:packages/amino"],
|
||||
["@cosmjs/cosmwasm-launchpad", "workspace:packages/cosmwasm-launchpad"],
|
||||
["@cosmjs/cosmwasm-stargate", "workspace:packages/cosmwasm-stargate"],
|
||||
["@cosmjs/crypto", "workspace:packages/crypto"],
|
||||
["@cosmjs/encoding", "workspace:packages/encoding"],
|
||||
@ -12014,25 +11873,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:139b584ca8199b231f3129ecc7e443fe9bbe8e7998f9ebec0596357d086f652fd437a0a38af20036f53bd7e9193cf4614624133c9c35ef007cf2ce7c818c0ad6#npm:5.1.2", {
|
||||
"packageLocation": "./.yarn/$$virtual/terser-webpack-plugin-virtual-c7f41a226c/0/cache/terser-webpack-plugin-npm-5.1.2-59f409825a-f65229fc60.zip/node_modules/terser-webpack-plugin/",
|
||||
"packageDependencies": [
|
||||
["terser-webpack-plugin", "virtual:139b584ca8199b231f3129ecc7e443fe9bbe8e7998f9ebec0596357d086f652fd437a0a38af20036f53bd7e9193cf4614624133c9c35ef007cf2ce7c818c0ad6#npm:5.1.2"],
|
||||
["@types/webpack", null],
|
||||
["jest-worker", "npm:26.6.2"],
|
||||
["p-limit", "npm:3.1.0"],
|
||||
["schema-utils", "npm:3.0.0"],
|
||||
["serialize-javascript", "npm:5.0.1"],
|
||||
["source-map", "npm:0.6.1"],
|
||||
["terser", "npm:5.7.0"],
|
||||
["webpack", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:5.37.1"]
|
||||
],
|
||||
"packagePeers": [
|
||||
"@types/webpack",
|
||||
"webpack"
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:2c148355187bb60dc28d99d6e8c71135442f7f75eee654fdcef714a856c04749d4bcd6c6f9a8235fa3df92a4b10c63458ab01bcf229754141703842a78ec781f#npm:5.1.2", {
|
||||
"packageLocation": "./.yarn/$$virtual/terser-webpack-plugin-virtual-bcb1a7a129/0/cache/terser-webpack-plugin-npm-5.1.2-59f409825a-f65229fc60.zip/node_modules/terser-webpack-plugin/",
|
||||
"packageDependencies": [
|
||||
@ -12834,40 +12674,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:5.37.1", {
|
||||
"packageLocation": "./.yarn/$$virtual/webpack-virtual-139b584ca8/0/cache/webpack-npm-5.37.1-1e75a59f6f-5fe030ea3f.zip/node_modules/webpack/",
|
||||
"packageDependencies": [
|
||||
["webpack", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:5.37.1"],
|
||||
["@types/eslint-scope", "npm:3.7.0"],
|
||||
["@types/estree", "npm:0.0.47"],
|
||||
["@webassemblyjs/ast", "npm:1.11.0"],
|
||||
["@webassemblyjs/wasm-edit", "npm:1.11.0"],
|
||||
["@webassemblyjs/wasm-parser", "npm:1.11.0"],
|
||||
["acorn", "npm:8.2.4"],
|
||||
["browserslist", "npm:4.16.6"],
|
||||
["chrome-trace-event", "npm:1.0.3"],
|
||||
["enhanced-resolve", "npm:5.8.2"],
|
||||
["es-module-lexer", "npm:0.4.1"],
|
||||
["eslint-scope", "npm:5.1.1"],
|
||||
["events", "npm:3.3.0"],
|
||||
["glob-to-regexp", "npm:0.4.1"],
|
||||
["graceful-fs", "npm:4.2.6"],
|
||||
["json-parse-better-errors", "npm:1.0.2"],
|
||||
["loader-runner", "npm:4.2.0"],
|
||||
["mime-types", "npm:2.1.30"],
|
||||
["neo-async", "npm:2.6.2"],
|
||||
["schema-utils", "npm:3.0.0"],
|
||||
["tapable", "npm:2.2.0"],
|
||||
["terser-webpack-plugin", "virtual:139b584ca8199b231f3129ecc7e443fe9bbe8e7998f9ebec0596357d086f652fd437a0a38af20036f53bd7e9193cf4614624133c9c35ef007cf2ce7c818c0ad6#npm:5.1.2"],
|
||||
["watchpack", "npm:2.2.0"],
|
||||
["webpack-cli", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:4.7.0"],
|
||||
["webpack-sources", "npm:2.2.0"]
|
||||
],
|
||||
"packagePeers": [
|
||||
"webpack-cli"
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:5.37.1", {
|
||||
"packageLocation": "./.yarn/$$virtual/webpack-virtual-830f2cc1c2/0/cache/webpack-npm-5.37.1-1e75a59f6f-5fe030ea3f.zip/node_modules/webpack/",
|
||||
"packageDependencies": [
|
||||
@ -13387,40 +13193,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:4.7.0", {
|
||||
"packageLocation": "./.yarn/$$virtual/webpack-cli-virtual-84adca422d/0/cache/webpack-cli-npm-4.7.0-cb3d7c34ff-6b935cda02.zip/node_modules/webpack-cli/",
|
||||
"packageDependencies": [
|
||||
["webpack-cli", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:4.7.0"],
|
||||
["@discoveryjs/json-ext", "npm:0.5.3"],
|
||||
["@types/webpack", null],
|
||||
["@webpack-cli/configtest", "virtual:84adca422dcfddfebe74232825b4d9a5ef615fdab14f3f17f1a545c4531bd06dc6868a9c1d1e3abce6a67873f8d3e04c605bfaaf12cc7fa1a0495f9f023c0ec3#npm:1.0.3"],
|
||||
["@webpack-cli/generators", null],
|
||||
["@webpack-cli/info", "virtual:84adca422dcfddfebe74232825b4d9a5ef615fdab14f3f17f1a545c4531bd06dc6868a9c1d1e3abce6a67873f8d3e04c605bfaaf12cc7fa1a0495f9f023c0ec3#npm:1.2.4"],
|
||||
["@webpack-cli/migrate", null],
|
||||
["@webpack-cli/serve", "virtual:84adca422dcfddfebe74232825b4d9a5ef615fdab14f3f17f1a545c4531bd06dc6868a9c1d1e3abce6a67873f8d3e04c605bfaaf12cc7fa1a0495f9f023c0ec3#npm:1.4.0"],
|
||||
["colorette", "npm:1.2.2"],
|
||||
["commander", "npm:7.2.0"],
|
||||
["execa", "npm:5.0.0"],
|
||||
["fastest-levenshtein", "npm:1.0.12"],
|
||||
["import-local", "npm:3.0.2"],
|
||||
["interpret", "npm:2.2.0"],
|
||||
["rechoir", "npm:0.7.0"],
|
||||
["v8-compile-cache", "npm:2.3.0"],
|
||||
["webpack", "virtual:395f1354ea29de80eb111225ff7f72912127acf779ae2110ad0ceee26819d65678b5f21be5a4c99566f19c05b63bb8c464db1c3c0ca922c2f45f91ad734637ee#npm:5.37.1"],
|
||||
["webpack-bundle-analyzer", null],
|
||||
["webpack-dev-server", null],
|
||||
["webpack-merge", "npm:5.7.3"]
|
||||
],
|
||||
"packagePeers": [
|
||||
"@types/webpack",
|
||||
"@webpack-cli/generators",
|
||||
"@webpack-cli/migrate",
|
||||
"webpack-bundle-analyzer",
|
||||
"webpack-dev-server",
|
||||
"webpack"
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.7.0", {
|
||||
"packageLocation": "./.yarn/$$virtual/webpack-cli-virtual-0dffb89908/0/cache/webpack-cli-npm-4.7.0-cb3d7c34ff-6b935cda02.zip/node_modules/webpack-cli/",
|
||||
"packageDependencies": [
|
||||
|
||||
@ -51,6 +51,10 @@ and this project adheres to
|
||||
`defaultGasPrice` and `buildFeeTable`.
|
||||
- @cosmjs/tendermint-rpc: `Client` has been removed. Please use
|
||||
`Tendermint33Client` or `Tendermint34Client`, depending on your needs.
|
||||
- @cosmjs/cosmwasm: Package removed ([#786]).
|
||||
- @cosmjs/cosmwasm-launchpad: Package removed ([#786]).
|
||||
|
||||
[#786]: https://github.com/cosmos/cosmjs/issues/786
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
22
HACKING.md
22
HACKING.md
@ -91,14 +91,14 @@ In the `scripts/` folder, a bunch of blockchains and other backend systems are
|
||||
started for testing purposes. Some ports need to be changed from the default in
|
||||
order to avoid conflicts. Here is an overview of the ports used:
|
||||
|
||||
| Port | Application | Usage |
|
||||
| ----- | --------------------- | ------------------------------------------------------ |
|
||||
| 1317 | wasmd LCD API | @cosmjs/launchpad and @cosmjs/cosmwasm-launchpad tests |
|
||||
| 1318 | simapp LCD API | Manual Stargate debugging |
|
||||
| 1319 | wasmd LCD API | Manual Stargate debugging |
|
||||
| 4444 | socketserver | @cosmjs/sockets tests |
|
||||
| 4445 | socketserver slow | @cosmjs/sockets tests |
|
||||
| 11133 | Tendermint 0.33 RPC | @cosmjs/tendermint-rpc tests |
|
||||
| 11134 | Tendermint 0.34 RPC | @cosmjs/tendermint-rpc tests |
|
||||
| 26658 | simapp Tendermint RPC | Stargate client tests |
|
||||
| 26659 | wasmd Tendermint RPC | @cosmjs/cosmwasm-stargate tests |
|
||||
| Port | Application | Usage |
|
||||
| ----- | --------------------- | ------------------------------- |
|
||||
| 1317 | wasmd LCD API | @cosmjs/launchpad tests |
|
||||
| 1318 | simapp LCD API | Manual Stargate debugging |
|
||||
| 1319 | wasmd LCD API | Manual Stargate debugging |
|
||||
| 4444 | socketserver | @cosmjs/sockets tests |
|
||||
| 4445 | socketserver slow | @cosmjs/sockets tests |
|
||||
| 11133 | Tendermint 0.33 RPC | @cosmjs/tendermint-rpc tests |
|
||||
| 11134 | Tendermint 0.34 RPC | @cosmjs/tendermint-rpc tests |
|
||||
| 26658 | simapp Tendermint RPC | Stargate client tests |
|
||||
| 26659 | wasmd Tendermint RPC | @cosmjs/cosmwasm-stargate tests |
|
||||
|
||||
16
README.md
16
README.md
@ -44,14 +44,14 @@ CosmJS is a library that consists of many smaller npm packages within the
|
||||
[@cosmjs namespace](https://www.npmjs.com/org/cosmjs), a so called monorepo.
|
||||
Here are some of them to get an idea:
|
||||
|
||||
| Package | Description | Latest |
|
||||
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [@cosmjs/launchpad](packages/launchpad) | A client library for the Cosmos SDK 0.37 (cosmoshub-3), 0.38 and 0.39 (Launchpad) | [](https://www.npmjs.com/package/@cosmjs/launchpad) |
|
||||
| [@cosmjs/faucet](packages/faucet) | A faucet application for node.js | [](https://www.npmjs.com/package/@cosmjs/faucet) |
|
||||
| [@cosmjs/cosmwasm-launchpad](packages/cosmwasm) | Client for chains with the CosmWasm module enabled | [](https://www.npmjs.com/package/@cosmjs/cosmwasm-launchpad) |
|
||||
| [@cosmjs/crypto](packages/crypto) | Cryptography for blockchain projects, e.g. hashing (SHA-2, Keccak256, Ripemd160), signing (secp256k1, ed25519), HD key derivation (BIPO39, SLIP-0010), KDFs and symmetric encryption for key storage (PBKDF2, Argon2, XChaCha20Poly1305) | [](https://www.npmjs.com/package/@cosmjs/crypto) |
|
||||
| [@cosmjs/encoding](packages/encoding) | Encoding helpers for blockchain projects | [](https://www.npmjs.com/package/@cosmjs/encoding) |
|
||||
| [@cosmjs/math](packages/math) | Safe integers; decimals for handling financial amounts | [](https://www.npmjs.com/package/@cosmjs/math) |
|
||||
| Package | Description | Latest |
|
||||
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [@cosmjs/launchpad](packages/launchpad) | A client library for the Cosmos SDK 0.37 (cosmoshub-3), 0.38 and 0.39 (Launchpad) | [](https://www.npmjs.com/package/@cosmjs/launchpad) |
|
||||
| [@cosmjs/faucet](packages/faucet) | A faucet application for node.js | [](https://www.npmjs.com/package/@cosmjs/faucet) |
|
||||
| [@cosmjs/cosmwasm-stargate](packages/cosmwasm-stargate) | Client for Stargate chains with the CosmWasm module enabled | [](https://www.npmjs.com/package/@cosmjs/cosmwasm-stargate) |
|
||||
| [@cosmjs/crypto](packages/crypto) | Cryptography for blockchain projects, e.g. hashing (SHA-2, Keccak256, Ripemd160), signing (secp256k1, ed25519), HD key derivation (BIPO39, SLIP-0010), KDFs and symmetric encryption for key storage (PBKDF2, Argon2, XChaCha20Poly1305) | [](https://www.npmjs.com/package/@cosmjs/crypto) |
|
||||
| [@cosmjs/encoding](packages/encoding) | Encoding helpers for blockchain projects | [](https://www.npmjs.com/package/@cosmjs/encoding) |
|
||||
| [@cosmjs/math](packages/math) | Safe integers; decimals for handling financial amounts | [](https://www.npmjs.com/package/@cosmjs/math) |
|
||||
|
||||
### Modularity
|
||||
|
||||
|
||||
@ -38,7 +38,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cosmjs/amino": "workspace:packages/amino",
|
||||
"@cosmjs/cosmwasm-launchpad": "workspace:packages/cosmwasm-launchpad",
|
||||
"@cosmjs/cosmwasm-stargate": "workspace:packages/cosmwasm-stargate",
|
||||
"@cosmjs/crypto": "workspace:packages/crypto",
|
||||
"@cosmjs/encoding": "workspace:packages/encoding",
|
||||
|
||||
@ -40,7 +40,6 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@cosmjs/amino": "workspace:packages/amino",
|
||||
"@cosmjs/cosmwasm-launchpad": "workspace:packages/cosmwasm-launchpad",
|
||||
"@cosmjs/cosmwasm-stargate": "workspace:packages/cosmwasm-stargate",
|
||||
"@cosmjs/crypto": "workspace:packages/crypto",
|
||||
"@cosmjs/encoding": "workspace:packages/encoding",
|
||||
|
||||
@ -1 +0,0 @@
|
||||
../../.eslintignore
|
||||
@ -1,92 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
es6: true,
|
||||
jasmine: true,
|
||||
node: true,
|
||||
worker: true,
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
project: "./tsconfig.eslint.json",
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ["@typescript-eslint", "prettier", "simple-import-sort", "import"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:import/typescript",
|
||||
],
|
||||
rules: {
|
||||
curly: ["warn", "multi-line", "consistent"],
|
||||
"no-bitwise": "warn",
|
||||
"no-console": ["warn", { allow: ["error", "info", "table", "warn"] }],
|
||||
"no-param-reassign": "warn",
|
||||
"no-shadow": "off", // disabled in favour of @typescript-eslint/no-shadow, see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-shadow.md
|
||||
"no-unused-vars": "off", // disabled in favour of @typescript-eslint/no-unused-vars, see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md
|
||||
"prefer-const": "warn",
|
||||
radix: ["warn", "always"],
|
||||
"spaced-comment": ["warn", "always", { line: { markers: ["/ <reference"] } }],
|
||||
"import/no-cycle": "warn",
|
||||
"simple-import-sort/imports": "warn",
|
||||
"@typescript-eslint/array-type": ["warn", { default: "array-simple" }],
|
||||
"@typescript-eslint/await-thenable": "warn",
|
||||
"@typescript-eslint/ban-types": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": ["warn", { allowExpressions: true }],
|
||||
"@typescript-eslint/explicit-member-accessibility": "warn",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{
|
||||
selector: "default",
|
||||
format: ["strictCamelCase"],
|
||||
},
|
||||
{
|
||||
selector: "typeLike",
|
||||
format: ["StrictPascalCase"],
|
||||
},
|
||||
{
|
||||
selector: "enumMember",
|
||||
format: ["StrictPascalCase"],
|
||||
},
|
||||
{
|
||||
selector: "variable",
|
||||
format: ["strictCamelCase"],
|
||||
leadingUnderscore: "allow",
|
||||
},
|
||||
{
|
||||
selector: "parameter",
|
||||
format: ["strictCamelCase"],
|
||||
leadingUnderscore: "allow",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-dynamic-delete": "warn",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"@typescript-eslint/no-parameter-properties": "warn",
|
||||
"@typescript-eslint/no-shadow": "warn",
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
|
||||
"@typescript-eslint/no-use-before-define": "warn",
|
||||
"@typescript-eslint/prefer-readonly": "warn",
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: "**/*.js",
|
||||
rules: {
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: "**/*.spec.ts",
|
||||
rules: {
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
3
packages/cosmwasm-launchpad/.gitignore
vendored
3
packages/cosmwasm-launchpad/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
build/
|
||||
dist/
|
||||
docs/
|
||||
@ -1 +0,0 @@
|
||||
../../.nycrc.yml
|
||||
@ -1,30 +0,0 @@
|
||||
# @cosmjs/cosmwasm-launchpad
|
||||
|
||||
[](https://www.npmjs.com/package/@cosmjs/cosmwasm-launchpad)
|
||||
|
||||
An SDK to build CosmWasm clients.
|
||||
|
||||
## Compatibility
|
||||
|
||||
| CosmWasm | x/wasm | @cosmjs/cosmwasm-launchpad |
|
||||
| --------- | --------- | -------------------------- |
|
||||
| 0.10-0.11 | 0.10-0.11 | `^0.23.0` |
|
||||
| 0.10 | 0.10 | `^0.22.0` |
|
||||
| 0.9 | 0.9 | `^0.21.0` |
|
||||
| 0.8 | 0.8 | `^0.20.1` |
|
||||
|
||||
## Development
|
||||
|
||||
Updating Hackatom development contract in `src/testdata/contract.json`:
|
||||
|
||||
```sh
|
||||
cd packages/cosmwasm-launchpad
|
||||
export HACKATOM_URL=https://github.com/CosmWasm/cosmwasm/releases/download/v0.11.0-alpha4/hackatom.wasm
|
||||
echo "{\"// source\": \"$HACKATOM_URL\", \"data\": \"$(curl -sS --location $HACKATOM_URL | base64 | tr -d '[:space:]')\" }" | jq > src/testdata/contract.json
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This package is part of the cosmjs repository, licensed under the Apache License
|
||||
2.0 (see [NOTICE](https://github.com/cosmos/cosmjs/blob/main/NOTICE) and
|
||||
[LICENSE](https://github.com/cosmos/cosmjs/blob/main/LICENSE)).
|
||||
@ -1,38 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
if (process.env.SES_ENABLED) {
|
||||
require("ses/lockdown");
|
||||
// eslint-disable-next-line no-undef
|
||||
lockdown();
|
||||
}
|
||||
|
||||
require("source-map-support").install();
|
||||
const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json");
|
||||
|
||||
// setup Jasmine
|
||||
const Jasmine = require("jasmine");
|
||||
const jasmine = new Jasmine();
|
||||
jasmine.loadConfig({
|
||||
spec_dir: "build",
|
||||
spec_files: ["**/*.spec.js"],
|
||||
helpers: [],
|
||||
random: false,
|
||||
seed: null,
|
||||
stopSpecOnExpectationFailure: false,
|
||||
});
|
||||
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000;
|
||||
|
||||
// setup reporter
|
||||
const { SpecReporter } = require("jasmine-spec-reporter");
|
||||
const reporter = new SpecReporter({
|
||||
...defaultSpecReporterConfig,
|
||||
spec: {
|
||||
...defaultSpecReporterConfig.spec,
|
||||
displaySuccessful: !process.argv.includes("--quiet"),
|
||||
},
|
||||
});
|
||||
|
||||
// initialize and execute
|
||||
jasmine.env.clearReporters();
|
||||
jasmine.addReporter(reporter);
|
||||
void jasmine.execute();
|
||||
@ -1,54 +0,0 @@
|
||||
const chrome = require("karma-chrome-launcher");
|
||||
const firefox = require("karma-firefox-launcher");
|
||||
const jasmine = require("karma-jasmine");
|
||||
const kjhtml = require("karma-jasmine-html-reporter");
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: ".",
|
||||
// registers plugins but does not activate them
|
||||
plugins: [jasmine, kjhtml, chrome, firefox],
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ["jasmine"],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: ["dist/web/tests.js"],
|
||||
|
||||
client: {
|
||||
jasmine: {
|
||||
random: false,
|
||||
timeoutInterval: 15000,
|
||||
},
|
||||
},
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ["progress", "kjhtml"],
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: false,
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ["Firefox"],
|
||||
|
||||
browserNoActivityTimeout: 90000,
|
||||
|
||||
// Keep brower open for debugging. This is overridden by yarn scripts
|
||||
singleRun: false,
|
||||
});
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
Directory used to trigger lerna package updates for all packages
|
||||
@ -1,87 +0,0 @@
|
||||
{
|
||||
"name": "@cosmjs/cosmwasm-launchpad",
|
||||
"version": "0.25.5",
|
||||
"description": "CosmWasm SDK for Launchpad",
|
||||
"contributors": [
|
||||
"Ethan Frey <ethanfrey@users.noreply.github.com>",
|
||||
"Will Clark <willclarktech@users.noreply.github.com>"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"files": [
|
||||
"build/",
|
||||
"*.md",
|
||||
"!*.spec.*",
|
||||
"!**/testdata/"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cosmos/cosmjs/tree/main/packages/cosmwasm-launchpad"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "typedoc --options typedoc.js",
|
||||
"lint": "eslint --max-warnings 0 \"./**/*.ts\" \"./*.js\"",
|
||||
"lint-fix": "eslint --fix --max-warnings 0 \"./**/*.ts\" \"./*.js\"",
|
||||
"format": "prettier --write --loglevel warn \"./src/**/*.ts\"",
|
||||
"format-text": "prettier --write \"./*.md\"",
|
||||
"build": "rm -rf ./build && tsc",
|
||||
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
|
||||
"test-node": "node --require esm jasmine-testrunner.js",
|
||||
"test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox",
|
||||
"test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless",
|
||||
"test": "yarn build-or-skip && yarn test-node",
|
||||
"coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet",
|
||||
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/crypto": "workspace:packages/crypto",
|
||||
"@cosmjs/encoding": "workspace:packages/encoding",
|
||||
"@cosmjs/launchpad": "workspace:packages/launchpad",
|
||||
"@cosmjs/math": "workspace:packages/math",
|
||||
"@cosmjs/utils": "workspace:packages/utils",
|
||||
"pako": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@types/eslint-plugin-prettier": "^3",
|
||||
"@types/jasmine": "^3.6.10",
|
||||
"@types/karma-firefox-launcher": "^2",
|
||||
"@types/karma-jasmine": "^4",
|
||||
"@types/karma-jasmine-html-reporter": "^1",
|
||||
"@types/node": "^15.0.1",
|
||||
"@types/pako": "^1.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28",
|
||||
"@typescript-eslint/parser": "^4.28",
|
||||
"eslint": "^7.5",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-node": "^0.3.4",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"esm": "^3.2.25",
|
||||
"glob": "^7.1.6",
|
||||
"jasmine": "^3.5",
|
||||
"jasmine-core": "^3.7.1",
|
||||
"jasmine-spec-reporter": "^6",
|
||||
"karma": "^6.1.1",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-firefox-launcher": "^2.1.0",
|
||||
"karma-jasmine": "^4.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.5.4",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"readonly-date": "^1.0.0",
|
||||
"ses": "^0.11.0",
|
||||
"source-map-support": "^0.5.19",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"ts-node": "^8",
|
||||
"typedoc": "^0.21",
|
||||
"typescript": "~4.3",
|
||||
"webpack": "^5.32.0",
|
||||
"webpack-cli": "^4.6.0"
|
||||
}
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
import { isValidBuilder } from "./builder";
|
||||
|
||||
describe("builder", () => {
|
||||
describe("isValidBuilder", () => {
|
||||
// Valid cases
|
||||
|
||||
it("returns true for simple examples", () => {
|
||||
expect(isValidBuilder("myorg/super-optimizer:0.1.2")).toEqual(true);
|
||||
expect(isValidBuilder("myorg/super-optimizer:42")).toEqual(true);
|
||||
});
|
||||
|
||||
it("supports images with multi level names", () => {
|
||||
expect(isValidBuilder("myorg/department-x/office-y/technology-z/super-optimizer:0.1.2")).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true for tags with lower and upper chars", () => {
|
||||
expect(isValidBuilder("myorg/super-optimizer:0.1.2-alpha")).toEqual(true);
|
||||
expect(isValidBuilder("myorg/super-optimizer:0.1.2-Alpha")).toEqual(true);
|
||||
});
|
||||
|
||||
// Invalid cases
|
||||
|
||||
it("returns false for missing or empty tag", () => {
|
||||
expect(isValidBuilder("myorg/super-optimizer")).toEqual(false);
|
||||
expect(isValidBuilder("myorg/super-optimizer:")).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns false for name components starting or ending with a separator", () => {
|
||||
expect(isValidBuilder(".myorg/super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("-myorg/super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("_myorg/super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg./super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg-/super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg_/super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg/.super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg/-super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg/_super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg/super-optimizer.:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg/super-optimizer-:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg/super-optimizer_:42")).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns false for upper case character in name component", () => {
|
||||
expect(isValidBuilder("mYorg/super-optimizer:42")).toEqual(false);
|
||||
expect(isValidBuilder("myorg/super-Optimizer:42")).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns false for long images", () => {
|
||||
expect(
|
||||
isValidBuilder(
|
||||
"myorgisnicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenice/super-optimizer:42",
|
||||
),
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns false for images with no organization", () => {
|
||||
// Those are valid dockerhub images from https://hub.docker.com/_/ubuntu and https://hub.docker.com/_/rust
|
||||
// but not valid in the context of CosmWasm Verify
|
||||
expect(isValidBuilder("ubuntu:xenial-20200212")).toEqual(false);
|
||||
expect(isValidBuilder("rust:1.40.0")).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,20 +0,0 @@
|
||||
// A docker image regexp. We remove support for non-standard registries for simplicity.
|
||||
// https://docs.docker.com/engine/reference/commandline/tag/#extended-description
|
||||
//
|
||||
// An image name is made up of slash-separated name components (optionally prefixed by a registry hostname).
|
||||
// Name components may contain lowercase characters, digits and separators.
|
||||
// A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator.
|
||||
//
|
||||
// A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes.
|
||||
// A tag name may not start with a period or a dash and may contain a maximum of 128 characters.
|
||||
const dockerImagePattern = new RegExp(
|
||||
"^[a-z0-9][a-z0-9._-]*[a-z0-9](/[a-z0-9][a-z0-9._-]*[a-z0-9])+:[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$",
|
||||
);
|
||||
|
||||
/** Max length in bytes/characters (regexp enforces all ASCII, even if that is not required by the standard) */
|
||||
const builderMaxLength = 128;
|
||||
|
||||
export function isValidBuilder(builder: string): boolean {
|
||||
if (builder.length > builderMaxLength) return false;
|
||||
return !!builder.match(dockerImagePattern);
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { Coin } from "@cosmjs/launchpad";
|
||||
|
||||
interface BankSendMsg {
|
||||
readonly send: {
|
||||
readonly from_address: string;
|
||||
readonly to_address: string;
|
||||
readonly amount: readonly Coin[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface BankMsg {
|
||||
readonly bank: BankSendMsg;
|
||||
}
|
||||
|
||||
export interface CustomMsg {
|
||||
readonly custom: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface StakingDelegateMsg {
|
||||
readonly delegate: {
|
||||
readonly validator: string;
|
||||
readonly amount: Coin;
|
||||
};
|
||||
}
|
||||
|
||||
interface StakingRedelegateMsg {
|
||||
readonly redelgate: {
|
||||
readonly src_validator: string;
|
||||
readonly dst_validator: string;
|
||||
readonly amount: Coin;
|
||||
};
|
||||
}
|
||||
|
||||
interface StakingUndelegateMsg {
|
||||
readonly undelegate: {
|
||||
readonly validator: string;
|
||||
readonly amount: Coin;
|
||||
};
|
||||
}
|
||||
|
||||
interface StakingWithdrawMsg {
|
||||
readonly withdraw: {
|
||||
readonly validator: string;
|
||||
readonly recipient?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StakingMsg {
|
||||
readonly staking: StakingDelegateMsg | StakingRedelegateMsg | StakingUndelegateMsg | StakingWithdrawMsg;
|
||||
}
|
||||
|
||||
interface WasmExecuteMsg {
|
||||
readonly execute: {
|
||||
readonly contract_address: string;
|
||||
readonly msg: any;
|
||||
readonly send: readonly Coin[];
|
||||
};
|
||||
}
|
||||
|
||||
interface WasmInstantiateMsg {
|
||||
readonly instantiate: {
|
||||
readonly code_id: string;
|
||||
readonly msg: any;
|
||||
readonly send: readonly Coin[];
|
||||
readonly label?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WasmMsg {
|
||||
readonly wasm: WasmExecuteMsg | WasmInstantiateMsg;
|
||||
}
|
||||
|
||||
/** These definitions are derived from CosmWasm:
|
||||
* https://github.com/CosmWasm/cosmwasm/blob/v0.12.0/packages/std/src/results/cosmos_msg.rs#L10-L23
|
||||
*/
|
||||
export type CosmosMsg = BankMsg | CustomMsg | StakingMsg | WasmMsg;
|
||||
@ -1,453 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import {
|
||||
coins,
|
||||
isBroadcastTxFailure,
|
||||
isMsgSend,
|
||||
LcdClient,
|
||||
makeSignDoc,
|
||||
makeStdTx,
|
||||
MsgSend,
|
||||
Secp256k1HdWallet,
|
||||
WrappedStdTx,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
|
||||
import { CosmWasmClient } from "./cosmwasmclient";
|
||||
import { isMsgExecuteContract, isMsgInstantiateContract } from "./msgs";
|
||||
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
|
||||
import {
|
||||
alice,
|
||||
deployedErc20,
|
||||
erc20Enabled,
|
||||
fromOneElementArray,
|
||||
launchpad,
|
||||
launchpadEnabled,
|
||||
makeRandomAddress,
|
||||
pendingWithoutErc20,
|
||||
pendingWithoutLaunchpad,
|
||||
} from "./testutils.spec";
|
||||
|
||||
interface TestTxSend {
|
||||
readonly sender: string;
|
||||
readonly recipient: string;
|
||||
readonly hash: string;
|
||||
readonly height: number;
|
||||
readonly tx: WrappedStdTx;
|
||||
}
|
||||
|
||||
interface TestTxExecute {
|
||||
readonly sender: string;
|
||||
readonly contract: string;
|
||||
readonly hash: string;
|
||||
readonly height: number;
|
||||
readonly tx: WrappedStdTx;
|
||||
}
|
||||
|
||||
describe("CosmWasmClient.getTx and .searchTx", () => {
|
||||
let sendSuccessful: TestTxSend | undefined;
|
||||
let sendSelfSuccessful: TestTxSend | undefined;
|
||||
let sendUnsuccessful: TestTxSend | undefined;
|
||||
let execute: TestTxExecute | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (launchpadEnabled() && erc20Enabled()) {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
|
||||
{
|
||||
const recipient = makeRandomAddress();
|
||||
const amount = coins(1234567, "ucosm");
|
||||
const result = await client.sendTokens(recipient, amount);
|
||||
await sleep(75); // wait until tx is indexed
|
||||
const txDetails = await new LcdClient(launchpad.endpoint).txById(result.transactionHash);
|
||||
sendSuccessful = {
|
||||
sender: alice.address0,
|
||||
recipient: recipient,
|
||||
hash: result.transactionHash,
|
||||
height: Number.parseInt(txDetails.height, 10),
|
||||
tx: txDetails.tx,
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
const recipient = alice.address0;
|
||||
const amount = coins(2345678, "ucosm");
|
||||
const result = await client.sendTokens(recipient, amount);
|
||||
await sleep(75); // wait until tx is indexed
|
||||
const txDetails = await new LcdClient(launchpad.endpoint).txById(result.transactionHash);
|
||||
sendSelfSuccessful = {
|
||||
sender: alice.address0,
|
||||
recipient: recipient,
|
||||
hash: result.transactionHash,
|
||||
height: Number.parseInt(txDetails.height, 10),
|
||||
tx: txDetails.tx,
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
const memo = "Sending more than I can afford";
|
||||
const recipient = makeRandomAddress();
|
||||
const amount = coins(123456700000000, "ucosm");
|
||||
const sendMsg: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: alice.address0,
|
||||
to_address: recipient,
|
||||
amount: amount,
|
||||
},
|
||||
};
|
||||
const fee = {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "80000", // 80k
|
||||
};
|
||||
const { accountNumber, sequence } = await client.getSequence();
|
||||
const chainId = await client.getChainId();
|
||||
const signDoc = makeSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
|
||||
const { signed, signature } = await wallet.signAmino(alice.address0, signDoc);
|
||||
const tx: WrappedStdTx = {
|
||||
type: "cosmos-sdk/StdTx",
|
||||
value: makeStdTx(signed, signature),
|
||||
};
|
||||
const transactionId = await client.getIdentifier(tx);
|
||||
const result = await client.broadcastTx(tx.value);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
sendUnsuccessful = {
|
||||
sender: alice.address0,
|
||||
recipient: recipient,
|
||||
hash: transactionId,
|
||||
height: result.height,
|
||||
tx: tx,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const hashInstance = deployedErc20.instances[0];
|
||||
const msg = {
|
||||
approve: {
|
||||
spender: makeRandomAddress(),
|
||||
amount: "12",
|
||||
},
|
||||
};
|
||||
const result = await client.execute(hashInstance, msg);
|
||||
await sleep(75); // wait until tx is indexed
|
||||
const txDetails = await new LcdClient(launchpad.endpoint).txById(result.transactionHash);
|
||||
execute = {
|
||||
sender: alice.address0,
|
||||
contract: hashInstance,
|
||||
hash: result.transactionHash,
|
||||
height: Number.parseInt(txDetails.height, 10),
|
||||
tx: txDetails.tx,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe("getTx", () => {
|
||||
it("can get successful tx by ID", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendSuccessful, "value must be set in beforeAll()");
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const result = await client.getTx(sendSuccessful.hash);
|
||||
expect(result).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: sendSuccessful.height,
|
||||
hash: sendSuccessful.hash,
|
||||
code: 0,
|
||||
tx: sendSuccessful.tx,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("can get unsuccessful tx by ID", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendUnsuccessful, "value must be set in beforeAll()");
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const result = await client.getTx(sendUnsuccessful.hash);
|
||||
expect(result).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: sendUnsuccessful.height,
|
||||
hash: sendUnsuccessful.hash,
|
||||
code: 5,
|
||||
tx: sendUnsuccessful.tx,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("can get by ID (non existent)", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
const result = await client.getTx(nonExistentId);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with SearchByHeightQuery", () => {
|
||||
it("can search successful tx by height", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendSuccessful, "value must be set in beforeAll()");
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const result = await client.searchTx({ height: sendSuccessful.height });
|
||||
expect(result.length).toBeGreaterThanOrEqual(1);
|
||||
expect(result).toContain(
|
||||
jasmine.objectContaining({
|
||||
height: sendSuccessful.height,
|
||||
hash: sendSuccessful.hash,
|
||||
code: 0,
|
||||
tx: sendSuccessful.tx,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("can search unsuccessful tx by height", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendUnsuccessful, "value must be set in beforeAll()");
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const result = await client.searchTx({ height: sendUnsuccessful.height });
|
||||
expect(result.length).toBeGreaterThanOrEqual(1);
|
||||
expect(result).toContain(
|
||||
jasmine.objectContaining({
|
||||
height: sendUnsuccessful.height,
|
||||
hash: sendUnsuccessful.hash,
|
||||
code: 5,
|
||||
tx: sendUnsuccessful.tx,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with SearchBySentFromOrToQuery", () => {
|
||||
it("can search by sender", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendSuccessful, "value must be set in beforeAll()");
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const results = await client.searchTx({ sentFromOrTo: sendSuccessful.sender });
|
||||
expect(results.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// Check basic structure of all results
|
||||
for (const result of results) {
|
||||
const containsMsgWithSender = !!result.tx.value.msg.find(
|
||||
(msg) => isMsgSend(msg) && msg.value.from_address == sendSuccessful!.sender,
|
||||
);
|
||||
const containsMsgWithRecipient = !!result.tx.value.msg.find(
|
||||
(msg) => isMsgSend(msg) && msg.value.to_address === sendSuccessful!.sender,
|
||||
);
|
||||
expect(containsMsgWithSender || containsMsgWithRecipient).toEqual(true);
|
||||
}
|
||||
|
||||
// Check details of most recent result (not sent to self)
|
||||
expect(results[results.length - 2]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: sendSuccessful.height,
|
||||
hash: sendSuccessful.hash,
|
||||
tx: sendSuccessful.tx,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("can search by recipient", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendSuccessful, "value must be set in beforeAll()");
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const results = await client.searchTx({ sentFromOrTo: sendSuccessful.recipient });
|
||||
expect(results.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// Check basic structure of all results
|
||||
for (const result of results) {
|
||||
const msg = fromOneElementArray(result.tx.value.msg);
|
||||
assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`);
|
||||
expect(
|
||||
msg.value.to_address === sendSuccessful.recipient ||
|
||||
msg.value.from_address == sendSuccessful.recipient,
|
||||
).toEqual(true);
|
||||
}
|
||||
|
||||
// Check details of most recent result
|
||||
expect(results[results.length - 1]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: sendSuccessful.height,
|
||||
hash: sendSuccessful.hash,
|
||||
tx: sendSuccessful.tx,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("can search by sender or recipient (sorted and deduplicated)", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendSelfSuccessful, "value must be set in beforeAll()");
|
||||
const txhash = sendSelfSuccessful.hash;
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const results = await client.searchTx({ sentFromOrTo: sendSelfSuccessful.recipient });
|
||||
|
||||
expect(Array.from(results).sort((tx1, tx2) => tx1.height - tx2.height)).toEqual(results);
|
||||
expect(results.filter((result) => result.hash === txhash).length).toEqual(1);
|
||||
});
|
||||
|
||||
it("can search by recipient and filter by minHeight", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendSuccessful);
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const query = { sentFromOrTo: sendSuccessful.recipient };
|
||||
|
||||
{
|
||||
const result = await client.searchTx(query, { minHeight: 0 });
|
||||
expect(result.length).toEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const result = await client.searchTx(query, { minHeight: sendSuccessful.height - 1 });
|
||||
expect(result.length).toEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const result = await client.searchTx(query, { minHeight: sendSuccessful.height });
|
||||
expect(result.length).toEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const result = await client.searchTx(query, { minHeight: sendSuccessful.height + 1 });
|
||||
expect(result.length).toEqual(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("can search by recipient and filter by maxHeight", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendSuccessful);
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const query = { sentFromOrTo: sendSuccessful.recipient };
|
||||
|
||||
{
|
||||
const result = await client.searchTx(query, { maxHeight: 9999999999999 });
|
||||
expect(result.length).toEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const result = await client.searchTx(query, { maxHeight: sendSuccessful.height + 1 });
|
||||
expect(result.length).toEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const result = await client.searchTx(query, { maxHeight: sendSuccessful.height });
|
||||
expect(result.length).toEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const result = await client.searchTx(query, { maxHeight: sendSuccessful.height - 1 });
|
||||
expect(result.length).toEqual(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("with SearchByTagsQuery", () => {
|
||||
it("can search by transfer.recipient", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(sendSuccessful, "value must be set in beforeAll()");
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const results = await client.searchTx({
|
||||
tags: [{ key: "transfer.recipient", value: sendSuccessful.recipient }],
|
||||
});
|
||||
expect(results.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// Check basic structure of all results
|
||||
for (const result of results) {
|
||||
const msg = fromOneElementArray(result.tx.value.msg);
|
||||
assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`);
|
||||
expect(msg.value.to_address).toEqual(sendSuccessful.recipient);
|
||||
}
|
||||
|
||||
// Check details of most recent result
|
||||
expect(results[results.length - 1]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: sendSuccessful.height,
|
||||
hash: sendSuccessful.hash,
|
||||
tx: sendSuccessful.tx,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("can search by message.contract_address", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(execute, "value must be set in beforeAll()");
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const results = await client.searchTx({
|
||||
tags: [{ key: "message.contract_address", value: execute.contract }],
|
||||
});
|
||||
expect(results.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// Check basic structure of all results
|
||||
for (const result of results) {
|
||||
const msg = fromOneElementArray(result.tx.value.msg);
|
||||
assert(
|
||||
isMsgExecuteContract(msg) || isMsgInstantiateContract(msg),
|
||||
`${result.hash} (at ${result.height}) not an execute or instantiate msg`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the first result is the instantiation
|
||||
const first = fromOneElementArray(results[0].tx.value.msg);
|
||||
assert(isMsgInstantiateContract(first), "First contract search result must be an instantiation");
|
||||
expect(first).toEqual({
|
||||
type: "wasm/MsgInstantiateContract",
|
||||
value: {
|
||||
sender: alice.address0,
|
||||
code_id: deployedErc20.codeId.toString(),
|
||||
label: "HASH",
|
||||
init_msg: jasmine.objectContaining({ symbol: "HASH" }),
|
||||
init_funds: [],
|
||||
},
|
||||
});
|
||||
|
||||
// Check details of most recent result
|
||||
expect(results[results.length - 1]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: execute.height,
|
||||
hash: execute.hash,
|
||||
tx: execute.tx,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("can search by message.contract_address + message.action", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutErc20();
|
||||
assert(execute, "value must be set in beforeAll()");
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const results = await client.searchTx({
|
||||
tags: [
|
||||
{ key: "message.contract_address", value: execute.contract },
|
||||
{ key: "message.action", value: "execute" },
|
||||
],
|
||||
});
|
||||
expect(results.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// Check basic structure of all results
|
||||
for (const result of results) {
|
||||
const msg = fromOneElementArray(result.tx.value.msg);
|
||||
assert(isMsgExecuteContract(msg), `${result.hash} (at ${result.height}) not an execute msg`);
|
||||
expect(msg.value.contract).toEqual(execute.contract);
|
||||
}
|
||||
|
||||
// Check details of most recent result
|
||||
expect(results[results.length - 1]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: execute.height,
|
||||
hash: execute.hash,
|
||||
tx: execute.tx,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,467 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { sha256 } from "@cosmjs/crypto";
|
||||
import { Bech32, fromHex, fromUtf8, toAscii, toBase64 } from "@cosmjs/encoding";
|
||||
import {
|
||||
assertIsBroadcastTxSuccess,
|
||||
isWrappedStdTx,
|
||||
logs,
|
||||
makeSignDoc,
|
||||
makeStdTx,
|
||||
MsgSend,
|
||||
Secp256k1HdWallet,
|
||||
StdFee,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
import { ReadonlyDate } from "readonly-date";
|
||||
|
||||
import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient";
|
||||
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
|
||||
import cosmoshub from "./testdata/cosmoshub.json";
|
||||
import {
|
||||
alice,
|
||||
deployedHackatom,
|
||||
getHackatom,
|
||||
launchpad,
|
||||
launchpadEnabled,
|
||||
makeRandomAddress,
|
||||
pendingWithoutLaunchpad,
|
||||
tendermintIdMatcher,
|
||||
unused,
|
||||
} from "./testutils.spec";
|
||||
|
||||
const blockTime = 1_000; // ms
|
||||
|
||||
interface HackatomInstance {
|
||||
readonly initMsg: {
|
||||
readonly verifier: string;
|
||||
readonly beneficiary: string;
|
||||
};
|
||||
readonly address: string;
|
||||
}
|
||||
|
||||
describe("CosmWasmClient", () => {
|
||||
describe("makeReadOnly", () => {
|
||||
it("can be constructed", () => {
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getChainId", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
expect(await client.getChainId()).toEqual(launchpad.chainId);
|
||||
});
|
||||
|
||||
it("caches chain ID", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const openedClient = client as unknown as PrivateCosmWasmClient;
|
||||
const getCodeSpy = spyOn(openedClient.lcdClient, "nodeInfo").and.callThrough();
|
||||
|
||||
expect(await client.getChainId()).toEqual(launchpad.chainId); // from network
|
||||
expect(await client.getChainId()).toEqual(launchpad.chainId); // from cache
|
||||
|
||||
expect(getCodeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHeight", () => {
|
||||
it("gets height via last block", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const openedClient = client as unknown as PrivateCosmWasmClient;
|
||||
const blockLatestSpy = spyOn(openedClient.lcdClient, "blocksLatest").and.callThrough();
|
||||
|
||||
const height1 = await client.getHeight();
|
||||
expect(height1).toBeGreaterThan(0);
|
||||
await sleep(blockTime * 1.4); // tolerate chain being 40% slower than expected
|
||||
const height2 = await client.getHeight();
|
||||
expect(height2).toBeGreaterThanOrEqual(height1 + 1);
|
||||
expect(height2).toBeLessThanOrEqual(height1 + 2);
|
||||
|
||||
expect(blockLatestSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("gets height via authAccount once an address is known", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
|
||||
const openedClient = client as unknown as PrivateCosmWasmClient;
|
||||
const blockLatestSpy = spyOn(openedClient.lcdClient, "blocksLatest").and.callThrough();
|
||||
const authAccountsSpy = spyOn(openedClient.lcdClient.auth, "account").and.callThrough();
|
||||
|
||||
const height1 = await client.getHeight();
|
||||
expect(height1).toBeGreaterThan(0);
|
||||
|
||||
await client.getCodes(); // warm up the client
|
||||
|
||||
const height2 = await client.getHeight();
|
||||
expect(height2).toBeGreaterThan(0);
|
||||
await sleep(blockTime * 1.3); // tolerate chain being 30% slower than expected
|
||||
const height3 = await client.getHeight();
|
||||
expect(height3).toBeGreaterThanOrEqual(height2 + 1);
|
||||
expect(height3).toBeLessThanOrEqual(height2 + 2);
|
||||
|
||||
expect(blockLatestSpy).toHaveBeenCalledTimes(1);
|
||||
expect(authAccountsSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSequence", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
expect(await client.getSequence(unused.address)).toEqual({
|
||||
accountNumber: unused.accountNumber,
|
||||
sequence: unused.sequence,
|
||||
});
|
||||
});
|
||||
|
||||
it("throws for missing accounts", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const missing = makeRandomAddress();
|
||||
await client.getSequence(missing).then(
|
||||
() => fail("this must not succeed"),
|
||||
(error) => expect(error).toMatch(/account does not exist on chain/i),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAccount", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
expect(await client.getAccount(unused.address)).toEqual({
|
||||
address: unused.address,
|
||||
accountNumber: unused.accountNumber,
|
||||
sequence: unused.sequence,
|
||||
pubkey: undefined,
|
||||
balance: [
|
||||
{ denom: "ucosm", amount: "1000000000" },
|
||||
{ denom: "ustake", amount: "1000000000" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("returns undefined for missing accounts", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const missing = makeRandomAddress();
|
||||
expect(await client.getAccount(missing)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getBlock", () => {
|
||||
it("works for latest block", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const response = await client.getBlock();
|
||||
|
||||
// id
|
||||
expect(response.id).toMatch(tendermintIdMatcher);
|
||||
|
||||
// header
|
||||
expect(response.header.height).toBeGreaterThanOrEqual(1);
|
||||
expect(response.header.chainId).toEqual(await client.getChainId());
|
||||
expect(new ReadonlyDate(response.header.time).getTime()).toBeLessThan(ReadonlyDate.now());
|
||||
expect(new ReadonlyDate(response.header.time).getTime()).toBeGreaterThanOrEqual(
|
||||
ReadonlyDate.now() - 5_000,
|
||||
);
|
||||
|
||||
// txs
|
||||
expect(Array.isArray(response.txs)).toEqual(true);
|
||||
});
|
||||
|
||||
it("works for block by height", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const height = (await client.getBlock()).header.height;
|
||||
const response = await client.getBlock(height - 1);
|
||||
|
||||
// id
|
||||
expect(response.id).toMatch(tendermintIdMatcher);
|
||||
|
||||
// header
|
||||
expect(response.header.height).toEqual(height - 1);
|
||||
expect(response.header.chainId).toEqual(await client.getChainId());
|
||||
expect(new ReadonlyDate(response.header.time).getTime()).toBeLessThan(ReadonlyDate.now());
|
||||
expect(new ReadonlyDate(response.header.time).getTime()).toBeGreaterThanOrEqual(
|
||||
ReadonlyDate.now() - 5_000,
|
||||
);
|
||||
|
||||
// txs
|
||||
expect(Array.isArray(response.txs)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getIdentifier", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
assert(isWrappedStdTx(cosmoshub.tx));
|
||||
expect(await client.getIdentifier(cosmoshub.tx)).toEqual(cosmoshub.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("broadcastTx", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
const sendMsg: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: alice.address0,
|
||||
to_address: makeRandomAddress(),
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "890000",
|
||||
};
|
||||
|
||||
const chainId = await client.getChainId();
|
||||
const { accountNumber, sequence } = await client.getSequence(alice.address0);
|
||||
const signDoc = makeSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
|
||||
const { signed, signature } = await wallet.signAmino(alice.address0, signDoc);
|
||||
const signedTx = makeStdTx(signed, signature);
|
||||
const result = await client.broadcastTx(signedTx);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const amountAttr = logs.findAttribute(result.logs, "transfer", "amount");
|
||||
expect(amountAttr.value).toEqual("1234567ucosm");
|
||||
expect(result.transactionHash).toMatch(/^[0-9A-F]{64}$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCodes", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const result = await client.getCodes();
|
||||
expect(result.length).toBeGreaterThanOrEqual(1);
|
||||
const [first] = result;
|
||||
expect(first).toEqual({
|
||||
id: deployedHackatom.codeId,
|
||||
source: deployedHackatom.source,
|
||||
builder: deployedHackatom.builder,
|
||||
checksum: deployedHackatom.checksum,
|
||||
creator: alice.address0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCodeDetails", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const result = await client.getCodeDetails(1);
|
||||
|
||||
const expectedInfo: Code = {
|
||||
id: deployedHackatom.codeId,
|
||||
source: deployedHackatom.source,
|
||||
builder: deployedHackatom.builder,
|
||||
checksum: deployedHackatom.checksum,
|
||||
creator: alice.address0,
|
||||
};
|
||||
|
||||
// check info
|
||||
expect(result).toEqual(jasmine.objectContaining(expectedInfo));
|
||||
// check data
|
||||
expect(sha256(result.data)).toEqual(fromHex(expectedInfo.checksum));
|
||||
});
|
||||
|
||||
it("caches downloads", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const openedClient = client as unknown as PrivateCosmWasmClient;
|
||||
const getCodeSpy = spyOn(openedClient.lcdClient.wasm, "getCode").and.callThrough();
|
||||
|
||||
const result1 = await client.getCodeDetails(deployedHackatom.codeId); // from network
|
||||
const result2 = await client.getCodeDetails(deployedHackatom.codeId); // from cache
|
||||
expect(result2).toEqual(result1);
|
||||
|
||||
expect(getCodeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getContracts", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const result = await client.getContracts(1);
|
||||
expect(result.length).toBeGreaterThanOrEqual(3);
|
||||
const [zero, one, two] = result;
|
||||
expect(zero).toEqual({
|
||||
address: deployedHackatom.instances[0].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
admin: undefined,
|
||||
label: deployedHackatom.instances[0].label,
|
||||
});
|
||||
expect(one).toEqual({
|
||||
address: deployedHackatom.instances[1].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
admin: undefined,
|
||||
label: deployedHackatom.instances[1].label,
|
||||
});
|
||||
expect(two).toEqual({
|
||||
address: deployedHackatom.instances[2].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
admin: alice.address1,
|
||||
label: deployedHackatom.instances[2].label,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getContract", () => {
|
||||
it("works for instance without admin", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const zero = await client.getContract(deployedHackatom.instances[0].address);
|
||||
expect(zero).toEqual({
|
||||
address: deployedHackatom.instances[0].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
label: deployedHackatom.instances[0].label,
|
||||
admin: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("works for instance with admin", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const two = await client.getContract(deployedHackatom.instances[2].address);
|
||||
expect(two).toEqual(
|
||||
jasmine.objectContaining({
|
||||
address: deployedHackatom.instances[2].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
label: deployedHackatom.instances[2].label,
|
||||
admin: alice.address1,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryContractRaw", () => {
|
||||
const configKey = toAscii("config");
|
||||
const otherKey = toAscii("this_does_not_exist");
|
||||
let contract: HackatomInstance | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (launchpadEnabled()) {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
|
||||
const { contractAddress } = await client.instantiate(codeId, initMsg, "random hackatom");
|
||||
contract = { initMsg: initMsg, address: contractAddress };
|
||||
}
|
||||
});
|
||||
|
||||
it("can query existing key", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(contract);
|
||||
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const raw = await client.queryContractRaw(contract.address, configKey);
|
||||
assert(raw, "must get result");
|
||||
expect(JSON.parse(fromUtf8(raw))).toEqual({
|
||||
verifier: toBase64(Bech32.decode(contract.initMsg.verifier).data),
|
||||
beneficiary: toBase64(Bech32.decode(contract.initMsg.beneficiary).data),
|
||||
funder: toBase64(Bech32.decode(alice.address0).data),
|
||||
});
|
||||
});
|
||||
|
||||
it("can query non-existent key", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(contract);
|
||||
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const raw = await client.queryContractRaw(contract.address, otherKey);
|
||||
expect(raw).toBeNull();
|
||||
});
|
||||
|
||||
it("errors for non-existent contract", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(contract);
|
||||
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
await client.queryContractRaw(nonExistentAddress, configKey).then(
|
||||
() => fail("must not succeed"),
|
||||
(error) => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryContractSmart", () => {
|
||||
let contract: HackatomInstance | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (launchpadEnabled()) {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
|
||||
const { contractAddress } = await client.instantiate(codeId, initMsg, "a different hackatom");
|
||||
contract = { initMsg: initMsg, address: contractAddress };
|
||||
}
|
||||
});
|
||||
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(contract);
|
||||
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
const resultDocument = await client.queryContractSmart(contract.address, { verifier: {} });
|
||||
expect(resultDocument).toEqual({ verifier: contract.initMsg.verifier });
|
||||
});
|
||||
|
||||
it("errors for malformed query message", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(contract);
|
||||
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
await client.queryContractSmart(contract.address, { broken: {} }).then(
|
||||
() => fail("must not succeed"),
|
||||
(error) =>
|
||||
expect(error).toMatch(
|
||||
/query wasm contract failed: Error parsing into type hackatom::contract::QueryMsg: unknown variant/i,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("errors for non-existent contract", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const client = new CosmWasmClient(launchpad.endpoint);
|
||||
await client.queryContractSmart(nonExistentAddress, { verifier: {} }).then(
|
||||
() => fail("must not succeed"),
|
||||
(error) => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,472 +0,0 @@
|
||||
import { sha256 } from "@cosmjs/crypto";
|
||||
import { fromBase64, fromHex, toHex } from "@cosmjs/encoding";
|
||||
import {
|
||||
AuthExtension,
|
||||
BroadcastMode,
|
||||
BroadcastTxResult,
|
||||
Coin,
|
||||
IndexedTx,
|
||||
LcdClient,
|
||||
logs,
|
||||
normalizePubkey,
|
||||
PubKey,
|
||||
setupAuthExtension,
|
||||
StdTx,
|
||||
uint64ToNumber,
|
||||
WrappedStdTx,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { Uint53 } from "@cosmjs/math";
|
||||
|
||||
import { setupWasmExtension, WasmExtension } from "./lcdapi/wasm";
|
||||
import { JsonObject } from "./types";
|
||||
|
||||
export interface GetSequenceResult {
|
||||
readonly accountNumber: number;
|
||||
readonly sequence: number;
|
||||
}
|
||||
|
||||
export interface Account {
|
||||
/** Bech32 account address */
|
||||
readonly address: string;
|
||||
readonly balance: readonly Coin[];
|
||||
readonly pubkey: PubKey | undefined;
|
||||
readonly accountNumber: number;
|
||||
readonly sequence: number;
|
||||
}
|
||||
|
||||
export interface SearchByIdQuery {
|
||||
readonly id: string;
|
||||
}
|
||||
|
||||
export interface SearchByHeightQuery {
|
||||
readonly height: number;
|
||||
}
|
||||
|
||||
export interface SearchBySentFromOrToQuery {
|
||||
readonly sentFromOrTo: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This query type allows you to pass arbitrary key/value pairs to the backend. It is
|
||||
* more powerful and slightly lower level than the other search options.
|
||||
*/
|
||||
export interface SearchByTagsQuery {
|
||||
readonly tags: ReadonlyArray<{ readonly key: string; readonly value: string }>;
|
||||
}
|
||||
|
||||
export type SearchTxQuery = SearchByHeightQuery | SearchBySentFromOrToQuery | SearchByTagsQuery;
|
||||
|
||||
function isSearchByHeightQuery(query: SearchTxQuery): query is SearchByHeightQuery {
|
||||
return (query as SearchByHeightQuery).height !== undefined;
|
||||
}
|
||||
|
||||
function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySentFromOrToQuery {
|
||||
return (query as SearchBySentFromOrToQuery).sentFromOrTo !== undefined;
|
||||
}
|
||||
|
||||
function isSearchByTagsQuery(query: SearchTxQuery): query is SearchByTagsQuery {
|
||||
return (query as SearchByTagsQuery).tags !== undefined;
|
||||
}
|
||||
|
||||
export interface SearchTxFilter {
|
||||
readonly minHeight?: number;
|
||||
readonly maxHeight?: number;
|
||||
}
|
||||
|
||||
export interface Code {
|
||||
readonly id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
/** Hex-encoded sha256 hash of the code stored here */
|
||||
readonly checksum: string;
|
||||
/**
|
||||
* An URL to a .tar.gz archive of the source code of the contract, which can be used to reproducibly build the Wasm bytecode.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly source?: string;
|
||||
/**
|
||||
* A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
|
||||
*
|
||||
* @example ```cosmwasm/rust-optimizer:0.8.0```
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly builder?: string;
|
||||
}
|
||||
|
||||
export interface CodeDetails extends Code {
|
||||
/** The original Wasm bytes */
|
||||
readonly data: Uint8Array;
|
||||
}
|
||||
|
||||
export interface Contract {
|
||||
readonly address: string;
|
||||
readonly codeId: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
/** Bech32-encoded admin address */
|
||||
readonly admin: string | undefined;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
export interface ContractCodeHistoryEntry {
|
||||
/** The source of this history entry */
|
||||
readonly operation: "Genesis" | "Init" | "Migrate";
|
||||
readonly codeId: number;
|
||||
readonly msg: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface BlockHeader {
|
||||
readonly version: {
|
||||
readonly block: string;
|
||||
readonly app: string;
|
||||
};
|
||||
readonly height: number;
|
||||
readonly chainId: string;
|
||||
/** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
|
||||
readonly time: string;
|
||||
}
|
||||
|
||||
export interface Block {
|
||||
/** The ID is a hash of the block header (uppercase hex) */
|
||||
readonly id: string;
|
||||
readonly header: BlockHeader;
|
||||
/** Array of raw transactions */
|
||||
readonly txs: readonly Uint8Array[];
|
||||
}
|
||||
|
||||
/** Use for testing only */
|
||||
export interface PrivateCosmWasmClient {
|
||||
readonly lcdClient: LcdClient & AuthExtension & WasmExtension;
|
||||
}
|
||||
|
||||
export class CosmWasmClient {
|
||||
protected readonly lcdClient: LcdClient & AuthExtension & WasmExtension;
|
||||
/** Any address the chain considers valid (valid bech32 with proper prefix) */
|
||||
protected anyValidAddress: string | undefined;
|
||||
|
||||
private readonly codesCache = new Map<number, CodeDetails>();
|
||||
private chainId: string | undefined;
|
||||
|
||||
/**
|
||||
* Creates a new client to interact with a CosmWasm blockchain.
|
||||
*
|
||||
* This instance does a lot of caching. In order to benefit from that you should try to use one instance
|
||||
* for the lifetime of your application. When switching backends, a new instance must be created.
|
||||
*
|
||||
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
|
||||
* @param broadcastMode Defines at which point of the transaction processing the broadcastTx method returns
|
||||
*/
|
||||
public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) {
|
||||
this.lcdClient = LcdClient.withExtensions(
|
||||
{ apiUrl: apiUrl, broadcastMode: broadcastMode },
|
||||
setupAuthExtension,
|
||||
setupWasmExtension,
|
||||
);
|
||||
}
|
||||
|
||||
public async getChainId(): Promise<string> {
|
||||
if (!this.chainId) {
|
||||
const response = await this.lcdClient.nodeInfo();
|
||||
const chainId = response.node_info.network;
|
||||
if (!chainId) throw new Error("Chain ID must not be empty");
|
||||
this.chainId = chainId;
|
||||
}
|
||||
|
||||
return this.chainId;
|
||||
}
|
||||
|
||||
public async getHeight(): Promise<number> {
|
||||
if (this.anyValidAddress) {
|
||||
const { height } = await this.lcdClient.auth.account(this.anyValidAddress);
|
||||
return parseInt(height, 10);
|
||||
} else {
|
||||
// Note: this gets inefficient when blocks contain a lot of transactions since it
|
||||
// requires downloading and deserializing all transactions in the block.
|
||||
const latest = await this.lcdClient.blocksLatest();
|
||||
return parseInt(latest.block.header.height, 10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID)
|
||||
*/
|
||||
public async getIdentifier(tx: WrappedStdTx): Promise<string> {
|
||||
// We consult the REST API because we don't have a local amino encoder
|
||||
const response = await this.lcdClient.encodeTx(tx);
|
||||
const hash = sha256(fromBase64(response.tx));
|
||||
return toHex(hash).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns account number and sequence.
|
||||
*
|
||||
* Throws if the account does not exist on chain.
|
||||
*
|
||||
* @param address returns data for this address. When unset, the client's sender adddress is used.
|
||||
*/
|
||||
public async getSequence(address: string): Promise<GetSequenceResult> {
|
||||
const account = await this.getAccount(address);
|
||||
if (!account) {
|
||||
throw new Error(
|
||||
"Account does not exist on chain. Send some tokens there before trying to query sequence.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
accountNumber: account.accountNumber,
|
||||
sequence: account.sequence,
|
||||
};
|
||||
}
|
||||
|
||||
public async getAccount(address: string): Promise<Account | undefined> {
|
||||
const account = await this.lcdClient.auth.account(address);
|
||||
const value = account.result.value;
|
||||
if (value.address === "") {
|
||||
return undefined;
|
||||
} else {
|
||||
this.anyValidAddress = value.address;
|
||||
return {
|
||||
address: value.address,
|
||||
balance: value.coins,
|
||||
pubkey: normalizePubkey(value.public_key) || undefined,
|
||||
accountNumber: uint64ToNumber(value.account_number),
|
||||
sequence: uint64ToNumber(value.sequence),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets block header and meta
|
||||
*
|
||||
* @param height The height of the block. If undefined, the latest height is used.
|
||||
*/
|
||||
public async getBlock(height?: number): Promise<Block> {
|
||||
const response =
|
||||
height !== undefined ? await this.lcdClient.blocks(height) : await this.lcdClient.blocksLatest();
|
||||
|
||||
return {
|
||||
id: response.block_id.hash,
|
||||
header: {
|
||||
version: response.block.header.version,
|
||||
time: response.block.header.time,
|
||||
height: parseInt(response.block.header.height, 10),
|
||||
chainId: response.block.header.chain_id,
|
||||
},
|
||||
txs: (response.block.data.txs || []).map(fromBase64),
|
||||
};
|
||||
}
|
||||
|
||||
public async getTx(id: string): Promise<IndexedTx | null> {
|
||||
const results = await this.txsQuery(`tx.hash=${id}`);
|
||||
return results[0] ?? null;
|
||||
}
|
||||
|
||||
public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise<readonly IndexedTx[]> {
|
||||
const minHeight = filter.minHeight || 0;
|
||||
const maxHeight = filter.maxHeight || Number.MAX_SAFE_INTEGER;
|
||||
|
||||
if (maxHeight < minHeight) return []; // optional optimization
|
||||
|
||||
function withFilters(originalQuery: string): string {
|
||||
return `${originalQuery}&tx.minheight=${minHeight}&tx.maxheight=${maxHeight}`;
|
||||
}
|
||||
|
||||
let txs: readonly IndexedTx[];
|
||||
if (isSearchByHeightQuery(query)) {
|
||||
// optional optimization to avoid network request
|
||||
if (query.height < minHeight || query.height > maxHeight) {
|
||||
txs = [];
|
||||
} else {
|
||||
txs = await this.txsQuery(`tx.height=${query.height}`);
|
||||
}
|
||||
} else if (isSearchBySentFromOrToQuery(query)) {
|
||||
// We cannot get both in one request (see https://github.com/cosmos/gaia/issues/75)
|
||||
const sentQuery = withFilters(`message.module=bank&message.sender=${query.sentFromOrTo}`);
|
||||
const receivedQuery = withFilters(`message.module=bank&transfer.recipient=${query.sentFromOrTo}`);
|
||||
const [sent, received] = (await Promise.all([
|
||||
this.txsQuery(sentQuery),
|
||||
this.txsQuery(receivedQuery),
|
||||
])) as [IndexedTx[], IndexedTx[]];
|
||||
|
||||
let mergedTxs: readonly IndexedTx[] = [];
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
// sent/received are presorted
|
||||
while (sent.length && received.length) {
|
||||
const next =
|
||||
sent[0].hash === received[0].hash
|
||||
? sent.shift()! && received.shift()!
|
||||
: sent[0].height <= received[0].height
|
||||
? sent.shift()!
|
||||
: received.shift()!;
|
||||
mergedTxs = [...mergedTxs, next];
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
||||
// At least one of sent/received is empty by now
|
||||
txs = [...mergedTxs, ...sent, ...received];
|
||||
} else if (isSearchByTagsQuery(query)) {
|
||||
const rawQuery = withFilters(query.tags.map((t) => `${t.key}=${t.value}`).join("&"));
|
||||
txs = await this.txsQuery(rawQuery);
|
||||
} else {
|
||||
throw new Error("Unknown query type");
|
||||
}
|
||||
|
||||
// backend sometimes messes up with min/max height filtering
|
||||
const filtered = txs.filter((tx) => tx.height >= minHeight && tx.height <= maxHeight);
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public async broadcastTx(tx: StdTx): Promise<BroadcastTxResult> {
|
||||
const result = await this.lcdClient.broadcastTx(tx);
|
||||
if (!result.txhash.match(/^([0-9A-F][0-9A-F])+$/)) {
|
||||
throw new Error("Received ill-formatted txhash. Must be non-empty upper-case hex");
|
||||
}
|
||||
|
||||
return result.code !== undefined
|
||||
? {
|
||||
height: Uint53.fromString(result.height).toNumber(),
|
||||
transactionHash: result.txhash,
|
||||
code: result.code,
|
||||
rawLog: result.raw_log || "",
|
||||
}
|
||||
: {
|
||||
logs: result.logs ? logs.parseLogs(result.logs) : [],
|
||||
rawLog: result.raw_log || "",
|
||||
transactionHash: result.txhash,
|
||||
data: result.data ? fromHex(result.data) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
public async getCodes(): Promise<readonly Code[]> {
|
||||
const result = await this.lcdClient.wasm.listCodeInfo();
|
||||
return result.map((entry): Code => {
|
||||
this.anyValidAddress = entry.creator;
|
||||
return {
|
||||
id: entry.id,
|
||||
creator: entry.creator,
|
||||
checksum: toHex(fromHex(entry.data_hash)),
|
||||
source: entry.source || undefined,
|
||||
builder: entry.builder || undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public async getCodeDetails(codeId: number): Promise<CodeDetails> {
|
||||
const cached = this.codesCache.get(codeId);
|
||||
if (cached) return cached;
|
||||
|
||||
const getCodeResult = await this.lcdClient.wasm.getCode(codeId);
|
||||
const codeDetails: CodeDetails = {
|
||||
id: getCodeResult.id,
|
||||
creator: getCodeResult.creator,
|
||||
checksum: toHex(fromHex(getCodeResult.data_hash)),
|
||||
source: getCodeResult.source || undefined,
|
||||
builder: getCodeResult.builder || undefined,
|
||||
data: fromBase64(getCodeResult.data),
|
||||
};
|
||||
this.codesCache.set(codeId, codeDetails);
|
||||
return codeDetails;
|
||||
}
|
||||
|
||||
public async getContracts(codeId: number): Promise<readonly Contract[]> {
|
||||
const result = await this.lcdClient.wasm.listContractsByCodeId(codeId);
|
||||
return result.map(
|
||||
(entry): Contract => ({
|
||||
address: entry.address,
|
||||
codeId: entry.code_id,
|
||||
creator: entry.creator,
|
||||
admin: entry.admin,
|
||||
label: entry.label,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error if no contract was found at the address
|
||||
*/
|
||||
public async getContract(address: string): Promise<Contract> {
|
||||
const result = await this.lcdClient.wasm.getContractInfo(address);
|
||||
if (!result) throw new Error(`No contract found at address "${address}"`);
|
||||
return {
|
||||
address: result.address,
|
||||
codeId: result.code_id,
|
||||
creator: result.creator,
|
||||
admin: result.admin,
|
||||
label: result.label,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error if no contract was found at the address
|
||||
*/
|
||||
public async getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]> {
|
||||
const result = await this.lcdClient.wasm.getContractCodeHistory(address);
|
||||
if (!result) throw new Error(`No contract history found for address "${address}"`);
|
||||
return result.map(
|
||||
(entry): ContractCodeHistoryEntry => ({
|
||||
operation: entry.operation,
|
||||
codeId: entry.code_id,
|
||||
msg: entry.msg,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data at the key if present (raw contract dependent storage data)
|
||||
* or null if no data at this key.
|
||||
*
|
||||
* Promise is rejected when contract does not exist.
|
||||
*/
|
||||
public async queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null> {
|
||||
// just test contract existence
|
||||
const _info = await this.getContract(address);
|
||||
|
||||
return this.lcdClient.wasm.queryContractRaw(address, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a smart query on the contract, returns the parsed JSON document.
|
||||
*
|
||||
* Promise is rejected when contract does not exist.
|
||||
* Promise is rejected for invalid query format.
|
||||
* Promise is rejected for invalid response format.
|
||||
*/
|
||||
public async queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject> {
|
||||
try {
|
||||
return await this.lcdClient.wasm.queryContractSmart(address, queryMsg);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.message.startsWith("not found: contract")) {
|
||||
throw new Error(`No contract found at address "${address}"`);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async txsQuery(query: string): Promise<readonly IndexedTx[]> {
|
||||
// TODO: we need proper pagination support
|
||||
const limit = 100;
|
||||
const result = await this.lcdClient.txsQuery(`${query}&limit=${limit}`);
|
||||
const pages = parseInt(result.page_total, 10);
|
||||
if (pages > 1) {
|
||||
throw new Error(
|
||||
`Found more results on the backend than we can process currently. Results: ${result.total_count}, supported: ${limit}`,
|
||||
);
|
||||
}
|
||||
return result.txs.map(
|
||||
(restItem): IndexedTx => ({
|
||||
height: parseInt(restItem.height, 10),
|
||||
hash: restItem.txhash,
|
||||
code: restItem.code || 0,
|
||||
rawLog: restItem.raw_log,
|
||||
logs: logs.parseLogs(restItem.logs || []),
|
||||
tx: restItem.tx,
|
||||
timestamp: restItem.timestamp,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,118 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { coins, Secp256k1HdWallet } from "@cosmjs/launchpad";
|
||||
|
||||
import { Cw1CosmWasmClient } from "./cw1cosmwasmclient";
|
||||
import {
|
||||
alice,
|
||||
deployedCw1,
|
||||
launchpad,
|
||||
makeRandomAddress,
|
||||
pendingWithoutCw1,
|
||||
pendingWithoutLaunchpad,
|
||||
} from "./testutils.spec";
|
||||
|
||||
describe("Cw1CosmWasmClient", () => {
|
||||
const contractAddress = deployedCw1.instances[0];
|
||||
const defaultToAddress = makeRandomAddress();
|
||||
const defaultMsg = {
|
||||
bank: {
|
||||
send: {
|
||||
from_address: contractAddress,
|
||||
to_address: defaultToAddress,
|
||||
amount: coins(1, "ucosm"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("constructor", () => {
|
||||
it("can be constructed", async () => {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("canSend", () => {
|
||||
it("returns true if client signer can send", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.canSend(defaultMsg);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns false if client signer cannot send", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address1,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.canSend(defaultMsg);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns true if supplied signer can send", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address1,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.canSend(defaultMsg, alice.address0);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns false if supplied signer cannot send", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.canSend(defaultMsg, alice.address1);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("executeCw1", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.executeCw1([defaultMsg]);
|
||||
|
||||
expect(result.transactionHash).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,45 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { Account, BroadcastMode, GasLimits, GasPrice, OfflineSigner } from "@cosmjs/launchpad";
|
||||
|
||||
import { CosmosMsg } from "./cosmosmsg";
|
||||
import { CosmWasmFeeTable, ExecuteResult, SigningCosmWasmClient } from "./signingcosmwasmclient";
|
||||
|
||||
export class Cw1CosmWasmClient extends SigningCosmWasmClient {
|
||||
public readonly cw1ContractAddress: string;
|
||||
|
||||
public constructor(
|
||||
apiUrl: string,
|
||||
signerAddress: string,
|
||||
signer: OfflineSigner,
|
||||
cw1ContractAddress: string,
|
||||
gasPrice?: GasPrice,
|
||||
gasLimits?: Partial<GasLimits<CosmWasmFeeTable>>,
|
||||
broadcastMode?: BroadcastMode,
|
||||
) {
|
||||
super(apiUrl, signerAddress, signer, gasPrice, gasLimits, broadcastMode);
|
||||
this.cw1ContractAddress = cw1ContractAddress;
|
||||
}
|
||||
|
||||
public override async getAccount(address?: string): Promise<Account | undefined> {
|
||||
return super.getAccount(address || this.cw1ContractAddress);
|
||||
}
|
||||
|
||||
public async canSend(msg: CosmosMsg, address = this.signerAddress): Promise<boolean> {
|
||||
const result = await this.queryContractSmart(this.cw1ContractAddress, {
|
||||
can_send: {
|
||||
sender: address,
|
||||
msg: msg,
|
||||
},
|
||||
});
|
||||
return result.can_send;
|
||||
}
|
||||
|
||||
public async executeCw1(msgs: readonly CosmosMsg[], memo = ""): Promise<ExecuteResult> {
|
||||
const handleMsg = {
|
||||
execute: {
|
||||
msgs: msgs,
|
||||
},
|
||||
};
|
||||
return this.execute(this.cw1ContractAddress, handleMsg, memo);
|
||||
}
|
||||
}
|
||||
@ -1,323 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { coin, coins, Secp256k1HdWallet } from "@cosmjs/launchpad";
|
||||
|
||||
import { Cw1SubkeyCosmWasmClient } from "./cw1subkeycosmwasmclient";
|
||||
import {
|
||||
alice,
|
||||
deployedCw1,
|
||||
launchpad,
|
||||
makeRandomAddress,
|
||||
pendingWithoutCw1,
|
||||
pendingWithoutLaunchpad,
|
||||
} from "./testutils.spec";
|
||||
|
||||
describe("Cw1SubkeyCosmWasmClient", () => {
|
||||
describe("getAdmins", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.getAdmins();
|
||||
|
||||
expect(result).toEqual([alice.address0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isAdmin", () => {
|
||||
it("returns true if client signer is admin", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.isAdmin();
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns false if client signer is not admin", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address1,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.isAdmin();
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns true if supplied signer is admin", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address1,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.isAdmin(alice.address0);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns false if supplied signer is not admin", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.isAdmin(alice.address1);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAllAllowances", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.getAllAllowances();
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAllowance", () => {
|
||||
it("works for client signer", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.getAllowance();
|
||||
|
||||
expect(result).toEqual({ balance: [], expires: { never: {} } });
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAllPermissions", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.getAllPermissions();
|
||||
expect(result.length).toEqual(1);
|
||||
// TODO: test content of permissions
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPermissions", () => {
|
||||
it("works for client signer", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.getPermissions();
|
||||
|
||||
expect(result).toEqual({
|
||||
delegate: false,
|
||||
redelegate: false,
|
||||
undelegate: false,
|
||||
withdraw: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("works for supplied signer", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const result = await client.getPermissions(alice.address1);
|
||||
|
||||
expect(result).toEqual({
|
||||
delegate: false,
|
||||
redelegate: false,
|
||||
undelegate: false,
|
||||
withdraw: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("addAdmin and removeAdmin", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const newAdmin = makeRandomAddress();
|
||||
expect(await client.isAdmin(newAdmin)).toBeFalse();
|
||||
|
||||
const addResult = await client.addAdmin(newAdmin);
|
||||
expect(addResult.transactionHash).toBeTruthy();
|
||||
expect(await client.isAdmin(newAdmin)).toBeTrue();
|
||||
|
||||
const removeResult = await client.removeAdmin(newAdmin);
|
||||
expect(removeResult.transactionHash).toBeTruthy();
|
||||
expect(await client.isAdmin(newAdmin)).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe("increaseAllowance and decreaseAllowance", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const spender = makeRandomAddress();
|
||||
expect(await client.getAllowance(spender)).toEqual({ balance: [], expires: { never: {} } });
|
||||
|
||||
const increaseAmount = coin(100, "ucosm");
|
||||
const increaseResult = await client.increaseAllowance(spender, increaseAmount);
|
||||
expect(increaseResult.transactionHash).toBeTruthy();
|
||||
expect(await client.getAllowance(spender)).toEqual({
|
||||
balance: [increaseAmount],
|
||||
expires: { never: {} },
|
||||
});
|
||||
|
||||
const decreaseAmount = coin(20, "ucosm");
|
||||
const decreaseResult = await client.decreaseAllowance(spender, decreaseAmount);
|
||||
expect(decreaseResult.transactionHash).toBeTruthy();
|
||||
expect(await client.getAllowance(spender)).toEqual({
|
||||
balance: coins(80, "ucosm"),
|
||||
expires: { never: {} },
|
||||
});
|
||||
});
|
||||
|
||||
it("works with expiration", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const spender = makeRandomAddress();
|
||||
expect(await client.getAllowance(spender)).toEqual({ balance: [], expires: { never: {} } });
|
||||
|
||||
const increaseAmount = coin(100, "ucosm");
|
||||
const increaseExpiration = { at_height: 88888888888 };
|
||||
const increaseResult = await client.increaseAllowance(spender, increaseAmount, increaseExpiration);
|
||||
expect(increaseResult.transactionHash).toBeTruthy();
|
||||
expect(await client.getAllowance(spender)).toEqual({
|
||||
balance: [increaseAmount],
|
||||
expires: increaseExpiration,
|
||||
});
|
||||
|
||||
const decreaseAmount = coin(20, "ucosm");
|
||||
const decreaseExpiration = { at_height: 99999999999 };
|
||||
const decreaseResult = await client.decreaseAllowance(spender, decreaseAmount, decreaseExpiration);
|
||||
expect(decreaseResult.transactionHash).toBeTruthy();
|
||||
expect(await client.getAllowance(spender)).toEqual({
|
||||
balance: coins(80, "ucosm"),
|
||||
expires: decreaseExpiration,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("setPermissions", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw1();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw1SubkeyCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw1.instances[0],
|
||||
);
|
||||
const spender = makeRandomAddress();
|
||||
const defaultPermissions = {
|
||||
delegate: false,
|
||||
redelegate: false,
|
||||
undelegate: false,
|
||||
withdraw: false,
|
||||
};
|
||||
expect(await client.getPermissions(spender)).toEqual(defaultPermissions);
|
||||
|
||||
const newPermissions = {
|
||||
delegate: true,
|
||||
redelegate: true,
|
||||
undelegate: true,
|
||||
withdraw: false,
|
||||
};
|
||||
const setPermissionsResult = await client.setPermissions(spender, newPermissions);
|
||||
expect(setPermissionsResult.transactionHash).toBeTruthy();
|
||||
expect(await client.getPermissions(spender)).toEqual(newPermissions);
|
||||
|
||||
const resetPermissionsResult = await client.setPermissions(spender, defaultPermissions);
|
||||
expect(resetPermissionsResult.transactionHash).toBeTruthy();
|
||||
expect(await client.getPermissions(spender)).toEqual(defaultPermissions);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,152 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { Coin } from "@cosmjs/launchpad";
|
||||
|
||||
import { Cw1CosmWasmClient } from "./cw1cosmwasmclient";
|
||||
import { Expiration } from "./interfaces";
|
||||
import { ExecuteResult } from "./signingcosmwasmclient";
|
||||
|
||||
/**
|
||||
* @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L88
|
||||
*/
|
||||
export interface Cw1SubkeyAllowanceInfo {
|
||||
readonly balance: readonly Coin[];
|
||||
readonly expires: Expiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L83
|
||||
*/
|
||||
interface AllAllowancesResponse {
|
||||
readonly allowances: readonly Cw1SubkeyAllowanceInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/state.rs#L15
|
||||
*/
|
||||
export interface Cw1SubkeyPermissions {
|
||||
readonly delegate: boolean;
|
||||
readonly redelegate: boolean;
|
||||
readonly undelegate: boolean;
|
||||
readonly withdraw: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L95
|
||||
*/
|
||||
export interface Cw1SubkeyPermissionsInfo {
|
||||
/** Spender address */
|
||||
readonly spender: string;
|
||||
readonly permissions: readonly Cw1SubkeyPermissions[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L101
|
||||
*/
|
||||
interface AllPermissionsResponse {
|
||||
readonly permissions: readonly Cw1SubkeyPermissionsInfo[];
|
||||
}
|
||||
|
||||
export class Cw1SubkeyCosmWasmClient extends Cw1CosmWasmClient {
|
||||
private async setAdmins(admins: readonly string[], memo = ""): Promise<ExecuteResult> {
|
||||
const handleMsg = {
|
||||
update_admins: {
|
||||
admins: admins,
|
||||
},
|
||||
};
|
||||
return this.execute(this.cw1ContractAddress, handleMsg, memo);
|
||||
}
|
||||
|
||||
public async getAdmins(): Promise<readonly string[]> {
|
||||
const { admins } = await this.queryContractSmart(this.cw1ContractAddress, { admin_list: {} });
|
||||
return admins;
|
||||
}
|
||||
|
||||
public async isAdmin(address = this.signerAddress): Promise<boolean> {
|
||||
const admins = await this.getAdmins();
|
||||
return admins.includes(address);
|
||||
}
|
||||
|
||||
public async getAllAllowances(): Promise<readonly Cw1SubkeyAllowanceInfo[]> {
|
||||
const response: AllAllowancesResponse = await this.queryContractSmart(this.cw1ContractAddress, {
|
||||
all_allowances: {},
|
||||
});
|
||||
return response.allowances;
|
||||
}
|
||||
|
||||
public async getAllowance(address = this.signerAddress): Promise<Cw1SubkeyAllowanceInfo> {
|
||||
return this.queryContractSmart(this.cw1ContractAddress, {
|
||||
allowance: { spender: address },
|
||||
});
|
||||
}
|
||||
|
||||
public async getAllPermissions(): Promise<readonly Cw1SubkeyPermissionsInfo[]> {
|
||||
const response: AllPermissionsResponse = await this.queryContractSmart(this.cw1ContractAddress, {
|
||||
all_permissions: {},
|
||||
});
|
||||
return response.permissions;
|
||||
}
|
||||
|
||||
public async getPermissions(address = this.signerAddress): Promise<Cw1SubkeyPermissions> {
|
||||
return this.queryContractSmart(this.cw1ContractAddress, {
|
||||
permissions: { spender: address },
|
||||
});
|
||||
}
|
||||
|
||||
public async addAdmin(address: string, memo = ""): Promise<ExecuteResult> {
|
||||
const admins = await this.getAdmins();
|
||||
const newAdmins = admins.includes(address) ? admins : [...admins, address];
|
||||
return this.setAdmins(newAdmins, memo);
|
||||
}
|
||||
|
||||
public async removeAdmin(address: string, memo = ""): Promise<ExecuteResult> {
|
||||
const admins = await this.getAdmins();
|
||||
const newAdmins = admins.filter((admin) => admin !== address);
|
||||
return this.setAdmins(newAdmins, memo);
|
||||
}
|
||||
|
||||
public async increaseAllowance(
|
||||
address: string,
|
||||
amount: Coin,
|
||||
expires?: Expiration,
|
||||
memo = "",
|
||||
): Promise<ExecuteResult> {
|
||||
const handleMsg = {
|
||||
increase_allowance: {
|
||||
spender: address,
|
||||
amount: amount,
|
||||
expires: expires,
|
||||
},
|
||||
};
|
||||
return this.execute(this.cw1ContractAddress, handleMsg, memo);
|
||||
}
|
||||
|
||||
public async decreaseAllowance(
|
||||
address: string,
|
||||
amount: Coin,
|
||||
expires?: Expiration,
|
||||
memo = "",
|
||||
): Promise<ExecuteResult> {
|
||||
const handleMsg = {
|
||||
decrease_allowance: {
|
||||
spender: address,
|
||||
amount: amount,
|
||||
expires: expires,
|
||||
},
|
||||
};
|
||||
return this.execute(this.cw1ContractAddress, handleMsg, memo);
|
||||
}
|
||||
|
||||
public async setPermissions(
|
||||
address: string,
|
||||
permissions: Cw1SubkeyPermissions,
|
||||
memo = "",
|
||||
): Promise<ExecuteResult> {
|
||||
const handleMsg = {
|
||||
set_permissions: {
|
||||
spender: address,
|
||||
permissions: permissions,
|
||||
},
|
||||
};
|
||||
return this.execute(this.cw1ContractAddress, handleMsg, memo);
|
||||
}
|
||||
}
|
||||
@ -1,348 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { makeCosmoshubPath, Secp256k1HdWallet } from "@cosmjs/launchpad";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
import { Cw3CosmWasmClient, Vote } from "./cw3cosmwasmclient";
|
||||
import {
|
||||
alice,
|
||||
cw3Enabled,
|
||||
deployedCw3,
|
||||
launchpad,
|
||||
launchpadEnabled,
|
||||
makeRandomAddress,
|
||||
pendingWithoutCw3,
|
||||
pendingWithoutLaunchpad,
|
||||
} from "./testutils.spec";
|
||||
|
||||
describe("Cw3CosmWasmClient", () => {
|
||||
describe("constructor", () => {
|
||||
it("can be constructed", async () => {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw3.instances[0],
|
||||
);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("queries", () => {
|
||||
const contractAddress = deployedCw3.instances[0];
|
||||
const toAddress = makeRandomAddress();
|
||||
const msg = {
|
||||
bank: {
|
||||
send: {
|
||||
from_address: contractAddress,
|
||||
to_address: toAddress,
|
||||
amount: [
|
||||
{
|
||||
amount: "1",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
let proposalId: number | undefined;
|
||||
let expirationHeight: number | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (launchpadEnabled() && cw3Enabled()) {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const currentHeight = await client.getHeight();
|
||||
expirationHeight = currentHeight + 1;
|
||||
const { logs } = await client.createMultisigProposal(
|
||||
"My proposal",
|
||||
"A proposal to propose proposing proposals",
|
||||
[msg],
|
||||
undefined,
|
||||
{ at_height: expirationHeight },
|
||||
);
|
||||
const wasmEvents = logs[0].events.find((event) => event.type === "wasm");
|
||||
assert(wasmEvents, "Wasm events not found in logs");
|
||||
const proposalIdAttribute = wasmEvents.attributes.find((log) => log.key === "proposal_id");
|
||||
assert(proposalIdAttribute, "Proposal ID not found in logs");
|
||||
proposalId = parseInt(proposalIdAttribute.value, 10);
|
||||
}
|
||||
});
|
||||
|
||||
it("getThreshold", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw3.instances[0],
|
||||
);
|
||||
const result = await client.getThreshold();
|
||||
|
||||
expect(result).toEqual({ absolute_count: { weight_needed: 1, total_weight: 3 } });
|
||||
});
|
||||
|
||||
it("getProposal", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
assert(proposalId, "value must be set in beforeAll()");
|
||||
assert(expirationHeight, "value must be set in beforeAll()");
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.getProposal(proposalId);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: proposalId,
|
||||
title: "My proposal",
|
||||
description: "A proposal to propose proposing proposals",
|
||||
msgs: [msg],
|
||||
expires: { at_height: expirationHeight },
|
||||
status: "passed",
|
||||
});
|
||||
});
|
||||
|
||||
it("listProposals", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
assert(proposalId, "value must be set in beforeAll()");
|
||||
assert(expirationHeight, "value must be set in beforeAll()");
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.listProposals({ startAfter: proposalId - 1, limit: 1 });
|
||||
|
||||
expect(result).toEqual({
|
||||
proposals: [
|
||||
{
|
||||
id: proposalId,
|
||||
title: "My proposal",
|
||||
description: "A proposal to propose proposing proposals",
|
||||
msgs: [msg],
|
||||
expires: { at_height: expirationHeight },
|
||||
status: "passed",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("reverseProposals", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
assert(proposalId, "value must be set in beforeAll()");
|
||||
assert(expirationHeight, "value must be set in beforeAll()");
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.reverseProposals({ limit: 1 });
|
||||
|
||||
expect(result).toEqual({
|
||||
proposals: [
|
||||
{
|
||||
id: proposalId,
|
||||
title: "My proposal",
|
||||
description: "A proposal to propose proposing proposals",
|
||||
msgs: [msg],
|
||||
expires: { at_height: expirationHeight },
|
||||
status: "passed",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("getVote", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
assert(proposalId, "value must be set in beforeAll()");
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.getVote(proposalId, alice.address0);
|
||||
|
||||
expect(result).toEqual({ vote: Vote.Yes });
|
||||
});
|
||||
|
||||
it("listVotes", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
assert(proposalId, "value must be set in beforeAll()");
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.listVotes(proposalId);
|
||||
|
||||
expect(result).toEqual({ votes: [{ voter: alice.address0, vote: Vote.Yes, weight: 1 }] });
|
||||
});
|
||||
|
||||
it("getVoter", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.getVoter(alice.address0);
|
||||
|
||||
expect(result).toEqual({ addr: alice.address0, weight: 1 });
|
||||
});
|
||||
|
||||
it("listVoters", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.listVoters();
|
||||
|
||||
expect(result.voters.length).toEqual(3);
|
||||
expect(result.voters).toEqual(
|
||||
jasmine.arrayContaining([
|
||||
{ addr: alice.address0, weight: 1 },
|
||||
{ addr: alice.address1, weight: 1 },
|
||||
{ addr: alice.address2, weight: 1 },
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Proposal lifecycle", () => {
|
||||
it("proposal is accepted (proposer has enough weight alone)", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
const contractAddress = deployedCw3.instances[0];
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const toAddress = makeRandomAddress();
|
||||
const msg = {
|
||||
bank: {
|
||||
send: {
|
||||
from_address: contractAddress,
|
||||
to_address: toAddress,
|
||||
amount: [
|
||||
{
|
||||
amount: "1",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
await client.createMultisigProposal("My proposal", "A proposal to propose proposing proposals", [msg]);
|
||||
const { proposals } = await client.reverseProposals({ limit: 1 });
|
||||
const proposalId = proposals[0].id;
|
||||
const executeResult = await client.executeMultisigProposal(proposalId);
|
||||
expect(executeResult).toBeTruthy();
|
||||
});
|
||||
|
||||
it("proposal is accepted (proposer does not have enough weight alone)", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
const contractAddress = deployedCw3.instances[1];
|
||||
const proposerWallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const proposer = new Cw3CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
proposerWallet,
|
||||
contractAddress,
|
||||
);
|
||||
const voterWallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, {
|
||||
hdPaths: [makeCosmoshubPath(1)],
|
||||
});
|
||||
const voter = new Cw3CosmWasmClient(launchpad.endpoint, alice.address1, voterWallet, contractAddress);
|
||||
const toAddress = makeRandomAddress();
|
||||
const msg = {
|
||||
bank: {
|
||||
send: {
|
||||
from_address: contractAddress,
|
||||
to_address: toAddress,
|
||||
amount: [
|
||||
{
|
||||
amount: "1",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
await proposer.createMultisigProposal("My proposal", "A proposal to propose proposing proposals", [
|
||||
msg,
|
||||
]);
|
||||
const { proposals } = await voter.reverseProposals({ limit: 1 });
|
||||
const proposalId = proposals[0].id;
|
||||
|
||||
await expectAsync(proposer.executeMultisigProposal(proposalId)).toBeRejectedWithError(
|
||||
/proposal must have passed and not yet been executed/i,
|
||||
);
|
||||
|
||||
const voteResult = await voter.voteMultisigProposal(proposalId, Vote.Yes);
|
||||
expect(voteResult).toBeTruthy();
|
||||
|
||||
const executeResult = await proposer.executeMultisigProposal(proposalId);
|
||||
expect(executeResult).toBeTruthy();
|
||||
});
|
||||
|
||||
it("proposal is rejected", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
const contractAddress = deployedCw3.instances[1];
|
||||
const proposerWallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const proposer = new Cw3CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
proposerWallet,
|
||||
contractAddress,
|
||||
);
|
||||
const voter1Wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, {
|
||||
hdPaths: [makeCosmoshubPath(1)],
|
||||
});
|
||||
const voter1 = new Cw3CosmWasmClient(launchpad.endpoint, alice.address1, voter1Wallet, contractAddress);
|
||||
const voter2Wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, {
|
||||
hdPaths: [makeCosmoshubPath(2)],
|
||||
});
|
||||
const voter2 = new Cw3CosmWasmClient(launchpad.endpoint, alice.address2, voter2Wallet, contractAddress);
|
||||
const toAddress = makeRandomAddress();
|
||||
const msg = {
|
||||
bank: {
|
||||
send: {
|
||||
from_address: contractAddress,
|
||||
to_address: toAddress,
|
||||
amount: [
|
||||
{
|
||||
amount: "1",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const currentHeight = await proposer.getHeight();
|
||||
await proposer.createMultisigProposal(
|
||||
"My proposal",
|
||||
"A proposal to propose proposing proposals",
|
||||
[msg],
|
||||
{
|
||||
at_height: currentHeight,
|
||||
},
|
||||
{
|
||||
at_height: currentHeight + 5,
|
||||
},
|
||||
);
|
||||
const { proposals } = await voter1.reverseProposals({ limit: 1 });
|
||||
const proposalId = proposals[0].id;
|
||||
|
||||
const vote1Result = await voter1.voteMultisigProposal(proposalId, Vote.Abstain);
|
||||
expect(vote1Result).toBeTruthy();
|
||||
const vote2Result = await voter2.voteMultisigProposal(proposalId, Vote.No);
|
||||
expect(vote2Result).toBeTruthy();
|
||||
|
||||
await expectAsync(proposer.executeMultisigProposal(proposalId)).toBeRejectedWithError(
|
||||
/proposal must have passed and not yet been executed/i,
|
||||
);
|
||||
|
||||
const closeResult = await proposer.closeMultisigProposal(proposalId);
|
||||
expect(closeResult).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,201 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { BroadcastMode, GasLimits, GasPrice, OfflineSigner } from "@cosmjs/launchpad";
|
||||
|
||||
import { CosmosMsg } from "./cosmosmsg";
|
||||
import { Account } from "./cosmwasmclient";
|
||||
import { Expiration } from "./interfaces";
|
||||
import { CosmWasmFeeTable, ExecuteResult, SigningCosmWasmClient } from "./signingcosmwasmclient";
|
||||
|
||||
/**
|
||||
* @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/packages/cw3/src/msg.rs#L35
|
||||
*/
|
||||
export enum Vote {
|
||||
Yes = "yes",
|
||||
No = "no",
|
||||
Abstain = "abstain",
|
||||
Veto = "veto",
|
||||
}
|
||||
|
||||
export interface ThresholdResult {
|
||||
readonly absolute_count: {
|
||||
readonly weight_needed: number;
|
||||
readonly total_weight: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ProposalResult {
|
||||
readonly id: number;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly msgs: readonly CosmosMsg[];
|
||||
readonly expires: Expiration;
|
||||
readonly status: string;
|
||||
}
|
||||
|
||||
export interface ProposalsResult {
|
||||
readonly proposals: readonly ProposalResult[];
|
||||
}
|
||||
|
||||
export interface VoteResult {
|
||||
readonly vote: Vote;
|
||||
}
|
||||
|
||||
export interface VotesResult {
|
||||
readonly votes: ReadonlyArray<{ readonly vote: Vote; readonly voter: string; readonly weight: number }>;
|
||||
}
|
||||
|
||||
export interface VoterResult {
|
||||
readonly addr: string;
|
||||
readonly weight: number;
|
||||
}
|
||||
|
||||
export interface VotersResult {
|
||||
readonly voters: readonly VoterResult[];
|
||||
}
|
||||
|
||||
interface StartBeforeNumberPaginationOptions {
|
||||
readonly startBefore?: number;
|
||||
readonly limit?: number;
|
||||
}
|
||||
|
||||
interface StartAfterNumberPaginationOptions {
|
||||
readonly startAfter?: number;
|
||||
readonly limit?: number;
|
||||
}
|
||||
|
||||
interface StartAfterStringPaginationOptions {
|
||||
readonly startAfter?: string;
|
||||
readonly limit?: number;
|
||||
}
|
||||
|
||||
export class Cw3CosmWasmClient extends SigningCosmWasmClient {
|
||||
public readonly cw3ContractAddress: string;
|
||||
|
||||
public constructor(
|
||||
apiUrl: string,
|
||||
signerAddress: string,
|
||||
signer: OfflineSigner,
|
||||
cw3ContractAddress: string,
|
||||
gasPrice?: GasPrice,
|
||||
gasLimits?: Partial<GasLimits<CosmWasmFeeTable>>,
|
||||
broadcastMode?: BroadcastMode,
|
||||
) {
|
||||
super(apiUrl, signerAddress, signer, gasPrice, gasLimits, broadcastMode);
|
||||
this.cw3ContractAddress = cw3ContractAddress;
|
||||
}
|
||||
|
||||
public override getAccount(address?: string): Promise<Account | undefined> {
|
||||
return super.getAccount(address || this.cw3ContractAddress);
|
||||
}
|
||||
|
||||
public getThreshold(): Promise<ThresholdResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, { threshold: {} });
|
||||
}
|
||||
|
||||
public getProposal(proposalId: number): Promise<ProposalResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, { proposal: { proposal_id: proposalId } });
|
||||
}
|
||||
|
||||
public listProposals({
|
||||
startAfter,
|
||||
limit,
|
||||
}: StartAfterNumberPaginationOptions = {}): Promise<ProposalsResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
list_proposals: {
|
||||
start_after: startAfter,
|
||||
limit: limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public reverseProposals({
|
||||
startBefore,
|
||||
limit,
|
||||
}: StartBeforeNumberPaginationOptions = {}): Promise<ProposalsResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
reverse_proposals: {
|
||||
start_before: startBefore,
|
||||
limit: limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public getVote(proposalId: number, voter: string): Promise<VoteResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
vote: {
|
||||
proposal_id: proposalId,
|
||||
voter: voter,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public listVotes(
|
||||
proposalId: number,
|
||||
{ startAfter, limit }: StartAfterStringPaginationOptions = {},
|
||||
): Promise<VotesResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
list_votes: {
|
||||
proposal_id: proposalId,
|
||||
start_after: startAfter,
|
||||
limit: limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public getVoter(address: string): Promise<VoterResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
voter: {
|
||||
address: address,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public listVoters({ startAfter, limit }: StartAfterStringPaginationOptions = {}): Promise<VotersResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
list_voters: {
|
||||
start_after: startAfter,
|
||||
limit: limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public createMultisigProposal(
|
||||
title: string,
|
||||
description: string,
|
||||
msgs: readonly CosmosMsg[],
|
||||
earliest?: Expiration,
|
||||
latest?: Expiration,
|
||||
memo = "",
|
||||
): Promise<ExecuteResult> {
|
||||
const handleMsg = {
|
||||
propose: {
|
||||
title: title,
|
||||
description: description,
|
||||
msgs: msgs,
|
||||
earliest: earliest,
|
||||
latest: latest,
|
||||
},
|
||||
};
|
||||
return this.execute(this.cw3ContractAddress, handleMsg, memo);
|
||||
}
|
||||
|
||||
public voteMultisigProposal(proposalId: number, vote: Vote, memo = ""): Promise<ExecuteResult> {
|
||||
const handleMsg = {
|
||||
vote: {
|
||||
proposal_id: proposalId,
|
||||
vote: vote,
|
||||
},
|
||||
};
|
||||
return this.execute(this.cw3ContractAddress, handleMsg, memo);
|
||||
}
|
||||
|
||||
public executeMultisigProposal(proposalId: number, memo = ""): Promise<ExecuteResult> {
|
||||
const handleMsg = { execute: { proposal_id: proposalId } };
|
||||
return this.execute(this.cw3ContractAddress, handleMsg, memo);
|
||||
}
|
||||
|
||||
public closeMultisigProposal(proposalId: number, memo = ""): Promise<ExecuteResult> {
|
||||
const handleMsg = { close: { proposal_id: proposalId } };
|
||||
return this.execute(this.cw3ContractAddress, handleMsg, memo);
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
export { isValidBuilder } from "./builder";
|
||||
export { Expiration } from "./interfaces";
|
||||
export { setupWasmExtension, WasmExtension } from "./lcdapi/wasm";
|
||||
|
||||
export { BankMsg, CosmosMsg, CustomMsg, StakingMsg, WasmMsg } from "./cosmosmsg";
|
||||
export {
|
||||
Account,
|
||||
Block,
|
||||
BlockHeader,
|
||||
Code,
|
||||
CodeDetails,
|
||||
Contract,
|
||||
ContractCodeHistoryEntry,
|
||||
CosmWasmClient,
|
||||
GetSequenceResult,
|
||||
SearchByHeightQuery,
|
||||
SearchBySentFromOrToQuery,
|
||||
SearchByTagsQuery,
|
||||
SearchTxQuery,
|
||||
SearchTxFilter,
|
||||
} from "./cosmwasmclient";
|
||||
export { Cw1CosmWasmClient } from "./cw1cosmwasmclient";
|
||||
export {
|
||||
Cw3CosmWasmClient,
|
||||
ProposalResult,
|
||||
ProposalsResult,
|
||||
ThresholdResult,
|
||||
Vote,
|
||||
VoteResult,
|
||||
VotesResult,
|
||||
VoterResult,
|
||||
VotersResult,
|
||||
} from "./cw3cosmwasmclient";
|
||||
export {
|
||||
ChangeAdminResult,
|
||||
ExecuteResult,
|
||||
CosmWasmFeeTable,
|
||||
InstantiateOptions,
|
||||
InstantiateResult,
|
||||
MigrateResult,
|
||||
SigningCosmWasmClient,
|
||||
UploadMeta,
|
||||
UploadResult,
|
||||
} from "./signingcosmwasmclient";
|
||||
export {
|
||||
isMsgClearAdmin,
|
||||
isMsgExecuteContract,
|
||||
isMsgInstantiateContract,
|
||||
isMsgMigrateContract,
|
||||
isMsgUpdateAdmin,
|
||||
isMsgStoreCode,
|
||||
MsgClearAdmin,
|
||||
MsgExecuteContract,
|
||||
MsgInstantiateContract,
|
||||
MsgMigrateContract,
|
||||
MsgUpdateAdmin,
|
||||
MsgStoreCode,
|
||||
} from "./msgs";
|
||||
export { JsonObject, parseWasmData, WasmData } from "./types";
|
||||
@ -1,15 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
/**
|
||||
* @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/packages/cw0/src/expiration.rs#L14
|
||||
*/
|
||||
export type Expiration =
|
||||
| {
|
||||
readonly at_height: number;
|
||||
}
|
||||
| {
|
||||
readonly at_time: number;
|
||||
}
|
||||
| {
|
||||
readonly never: Record<any, never>;
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export { Expiration } from "./cw0";
|
||||
@ -1,547 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { sha256 } from "@cosmjs/crypto";
|
||||
import { Bech32, fromAscii, fromHex, fromUtf8, toAscii, toBase64, toHex } from "@cosmjs/encoding";
|
||||
import {
|
||||
assertIsBroadcastTxSuccess,
|
||||
AuthExtension,
|
||||
BroadcastTxResult,
|
||||
BroadcastTxsResponse,
|
||||
Coin,
|
||||
coin,
|
||||
coins,
|
||||
LcdClient,
|
||||
logs,
|
||||
makeSignDoc,
|
||||
makeStdTx,
|
||||
OfflineSigner,
|
||||
Secp256k1HdWallet,
|
||||
setupAuthExtension,
|
||||
SigningCosmosClient,
|
||||
StdFee,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
import {
|
||||
isMsgInstantiateContract,
|
||||
isMsgStoreCode,
|
||||
MsgExecuteContract,
|
||||
MsgInstantiateContract,
|
||||
MsgStoreCode,
|
||||
} from "../msgs";
|
||||
import {
|
||||
alice,
|
||||
base64Matcher,
|
||||
bech32AddressMatcher,
|
||||
ContractUploadInstructions,
|
||||
deployedHackatom,
|
||||
fromOneElementArray,
|
||||
getHackatom,
|
||||
launchpad,
|
||||
launchpadEnabled,
|
||||
makeRandomAddress,
|
||||
pendingWithoutLaunchpad,
|
||||
} from "../testutils.spec";
|
||||
import { setupWasmExtension, WasmExtension } from "./wasm";
|
||||
|
||||
type WasmClient = LcdClient & AuthExtension & WasmExtension;
|
||||
|
||||
function makeWasmClient(apiUrl: string): WasmClient {
|
||||
return LcdClient.withExtensions({ apiUrl }, setupAuthExtension, setupWasmExtension);
|
||||
}
|
||||
|
||||
async function uploadContract(
|
||||
signer: OfflineSigner,
|
||||
contract: ContractUploadInstructions,
|
||||
): Promise<BroadcastTxResult> {
|
||||
const memo = "My first contract on chain";
|
||||
const theMsg: MsgStoreCode = {
|
||||
type: "wasm/MsgStoreCode",
|
||||
value: {
|
||||
sender: alice.address0,
|
||||
wasm_byte_code: toBase64(contract.data),
|
||||
source: contract.source || "",
|
||||
builder: contract.builder || "",
|
||||
},
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: coins(5000000, "ucosm"),
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
const firstAddress = (await signer.getAccounts())[0].address;
|
||||
const client = new SigningCosmosClient(launchpad.endpoint, firstAddress, signer);
|
||||
return client.signAndBroadcast([theMsg], fee, memo);
|
||||
}
|
||||
|
||||
async function instantiateContract(
|
||||
signer: OfflineSigner,
|
||||
codeId: number,
|
||||
beneficiaryAddress: string,
|
||||
funds?: readonly Coin[],
|
||||
): Promise<BroadcastTxResult> {
|
||||
const memo = "Create an escrow instance";
|
||||
const theMsg: MsgInstantiateContract = {
|
||||
type: "wasm/MsgInstantiateContract",
|
||||
value: {
|
||||
sender: alice.address0,
|
||||
code_id: codeId.toString(),
|
||||
label: "my escrow",
|
||||
init_msg: {
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
init_funds: funds || [],
|
||||
},
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: coins(5000000, "ucosm"),
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
const firstAddress = (await signer.getAccounts())[0].address;
|
||||
const client = new SigningCosmosClient(launchpad.endpoint, firstAddress, signer);
|
||||
return client.signAndBroadcast([theMsg], fee, memo);
|
||||
}
|
||||
|
||||
async function executeContract(
|
||||
client: WasmClient,
|
||||
signer: OfflineSigner,
|
||||
contractAddress: string,
|
||||
msg: Record<string, unknown>,
|
||||
): Promise<BroadcastTxsResponse> {
|
||||
const memo = "Time for action";
|
||||
const theMsg: MsgExecuteContract = {
|
||||
type: "wasm/MsgExecuteContract",
|
||||
value: {
|
||||
sender: alice.address0,
|
||||
contract: contractAddress,
|
||||
msg: msg,
|
||||
sent_funds: [],
|
||||
},
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: coins(5000000, "ucosm"),
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
const { account_number, sequence } = (await client.auth.account(alice.address0)).result.value;
|
||||
const signDoc = makeSignDoc([theMsg], fee, launchpad.chainId, memo, account_number, sequence);
|
||||
const { signed, signature } = await signer.signAmino(alice.address0, signDoc);
|
||||
const signedTx = makeStdTx(signed, signature);
|
||||
return client.broadcastTx(signedTx);
|
||||
}
|
||||
|
||||
describe("WasmExtension", () => {
|
||||
const hackatom = getHackatom();
|
||||
const hackatomConfigKey = toAscii("config");
|
||||
let hackatomCodeId: number | undefined;
|
||||
let hackatomContractAddress: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (launchpadEnabled()) {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const result = await uploadContract(wallet, hackatom);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const parsedLogs = logs.parseLogs(result.logs);
|
||||
const codeIdAttr = logs.findAttribute(parsedLogs, "message", "code_id");
|
||||
hackatomCodeId = Number.parseInt(codeIdAttr.value, 10);
|
||||
|
||||
const instantiateResult = await instantiateContract(wallet, hackatomCodeId, makeRandomAddress());
|
||||
assertIsBroadcastTxSuccess(instantiateResult);
|
||||
const instantiateLogs = logs.parseLogs(instantiateResult.logs);
|
||||
const contractAddressAttr = logs.findAttribute(instantiateLogs, "message", "contract_address");
|
||||
hackatomContractAddress = contractAddressAttr.value;
|
||||
}
|
||||
});
|
||||
|
||||
describe("listCodeInfo", () => {
|
||||
it("has recently uploaded contract as last entry", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomCodeId);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const codesList = await client.wasm.listCodeInfo();
|
||||
const lastCode = codesList[codesList.length - 1];
|
||||
expect(lastCode.id).toEqual(hackatomCodeId);
|
||||
expect(lastCode.creator).toEqual(alice.address0);
|
||||
expect(lastCode.source).toEqual(hackatom.source);
|
||||
expect(lastCode.builder).toEqual(hackatom.builder);
|
||||
expect(lastCode.data_hash.toLowerCase()).toEqual(toHex(sha256(hackatom.data)));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCode", () => {
|
||||
it("contains fill code information", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomCodeId);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const code = await client.wasm.getCode(hackatomCodeId);
|
||||
expect(code.id).toEqual(hackatomCodeId);
|
||||
expect(code.creator).toEqual(alice.address0);
|
||||
expect(code.source).toEqual(hackatom.source);
|
||||
expect(code.builder).toEqual(hackatom.builder);
|
||||
expect(code.data_hash.toLowerCase()).toEqual(toHex(sha256(hackatom.data)));
|
||||
expect(code.data).toEqual(toBase64(hackatom.data));
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: move listContractsByCodeId tests out of here
|
||||
describe("getContractInfo", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomCodeId);
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
|
||||
// create new instance and compare before and after
|
||||
const existingContractsByCode = await client.wasm.listContractsByCodeId(hackatomCodeId);
|
||||
for (const contract of existingContractsByCode) {
|
||||
expect(contract.address).toMatch(bech32AddressMatcher);
|
||||
expect(contract.code_id).toEqual(hackatomCodeId);
|
||||
expect(contract.creator).toMatch(bech32AddressMatcher);
|
||||
expect(contract.label).toMatch(/^.+$/);
|
||||
}
|
||||
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const funds = coins(707707, "ucosm");
|
||||
const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, funds);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const parsedLogs = logs.parseLogs(result.logs);
|
||||
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
|
||||
const myAddress = contractAddressAttr.value;
|
||||
|
||||
const newContractsByCode = await client.wasm.listContractsByCodeId(hackatomCodeId);
|
||||
expect(newContractsByCode.length).toEqual(existingContractsByCode.length + 1);
|
||||
const newContract = newContractsByCode[newContractsByCode.length - 1];
|
||||
expect(newContract).toEqual(
|
||||
jasmine.objectContaining({
|
||||
code_id: hackatomCodeId,
|
||||
creator: alice.address0,
|
||||
label: "my escrow",
|
||||
}),
|
||||
);
|
||||
|
||||
const info = await client.wasm.getContractInfo(myAddress);
|
||||
assert(info);
|
||||
expect(info).toEqual(
|
||||
jasmine.objectContaining({
|
||||
code_id: hackatomCodeId,
|
||||
creator: alice.address0,
|
||||
label: "my escrow",
|
||||
}),
|
||||
);
|
||||
expect(info.admin).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns null for non-existent address", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomCodeId);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const info = await client.wasm.getContractInfo(nonExistentAddress);
|
||||
expect(info).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getContractCodeHistory", () => {
|
||||
it("can list contract history", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomCodeId);
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
|
||||
// create new instance and compare before and after
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const funds = coins(707707, "ucosm");
|
||||
const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, funds);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const parsedLogs = logs.parseLogs(result.logs);
|
||||
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
|
||||
const myAddress = contractAddressAttr.value;
|
||||
|
||||
const history = await client.wasm.getContractCodeHistory(myAddress);
|
||||
assert(history);
|
||||
expect(history).toContain({
|
||||
code_id: hackatomCodeId,
|
||||
operation: "Init",
|
||||
msg: {
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null for non-existent address", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomCodeId);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const history = await client.wasm.getContractCodeHistory(nonExistentAddress);
|
||||
expect(history).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAllContractState", () => {
|
||||
it("can get all state", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomContractAddress);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const state = await client.wasm.getAllContractState(hackatomContractAddress);
|
||||
expect(state.length).toEqual(1);
|
||||
const data = state[0];
|
||||
expect(data.key).toEqual(hackatomConfigKey);
|
||||
const value = JSON.parse(fromUtf8(data.val));
|
||||
expect(value.verifier).toMatch(base64Matcher);
|
||||
expect(value.beneficiary).toMatch(base64Matcher);
|
||||
});
|
||||
|
||||
it("is empty for non-existent address", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const state = await client.wasm.getAllContractState(nonExistentAddress);
|
||||
expect(state).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryContractRaw", () => {
|
||||
it("can query by key", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomContractAddress);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const raw = await client.wasm.queryContractRaw(hackatomContractAddress, hackatomConfigKey);
|
||||
assert(raw, "must get result");
|
||||
const model = JSON.parse(fromAscii(raw));
|
||||
expect(model.verifier).toMatch(base64Matcher);
|
||||
expect(model.beneficiary).toMatch(base64Matcher);
|
||||
});
|
||||
|
||||
it("returns null for missing key", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomContractAddress);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const info = await client.wasm.queryContractRaw(hackatomContractAddress, fromHex("cafe0dad"));
|
||||
expect(info).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for non-existent address", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const info = await client.wasm.queryContractRaw(nonExistentAddress, hackatomConfigKey);
|
||||
expect(info).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryContractSmart", () => {
|
||||
it("can make smart queries", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomContractAddress);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const request = { verifier: {} };
|
||||
const response = await client.wasm.queryContractSmart(hackatomContractAddress, request);
|
||||
expect(response).toEqual({ verifier: alice.address0 });
|
||||
});
|
||||
|
||||
it("throws for invalid query requests", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
assert(hackatomContractAddress);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const request = { nosuchkey: {} };
|
||||
await client.wasm.queryContractSmart(hackatomContractAddress, request).then(
|
||||
() => fail("shouldn't succeed"),
|
||||
(error) =>
|
||||
expect(error).toMatch(
|
||||
/query wasm contract failed: Error parsing into type hackatom::contract::QueryMsg: unknown variant/,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("throws for non-existent address", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const request = { verifier: {} };
|
||||
await client.wasm.queryContractSmart(nonExistentAddress, request).then(
|
||||
() => fail("shouldn't succeed"),
|
||||
(error) => expect(error).toMatch("not found"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("txsQuery", () => {
|
||||
it("can query by tags (module + code_id)", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
const result = await client.txsQuery(`message.module=wasm&message.code_id=${deployedHackatom.codeId}`);
|
||||
expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(4);
|
||||
|
||||
// Check first 4 results
|
||||
const [store, zero, one, two] = result.txs.map((tx) => fromOneElementArray(tx.tx.value.msg));
|
||||
assert(isMsgStoreCode(store));
|
||||
assert(isMsgInstantiateContract(zero));
|
||||
assert(isMsgInstantiateContract(one));
|
||||
assert(isMsgInstantiateContract(two));
|
||||
expect(store.value).toEqual(
|
||||
jasmine.objectContaining({
|
||||
sender: alice.address0,
|
||||
source: deployedHackatom.source,
|
||||
builder: deployedHackatom.builder,
|
||||
}),
|
||||
);
|
||||
expect(zero.value).toEqual({
|
||||
code_id: deployedHackatom.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({
|
||||
beneficiary: deployedHackatom.instances[0].beneficiary,
|
||||
}),
|
||||
label: deployedHackatom.instances[0].label,
|
||||
sender: alice.address0,
|
||||
});
|
||||
expect(one.value).toEqual({
|
||||
code_id: deployedHackatom.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({
|
||||
beneficiary: deployedHackatom.instances[1].beneficiary,
|
||||
}),
|
||||
label: deployedHackatom.instances[1].label,
|
||||
sender: alice.address0,
|
||||
});
|
||||
expect(two.value).toEqual({
|
||||
code_id: deployedHackatom.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({
|
||||
beneficiary: deployedHackatom.instances[2].beneficiary,
|
||||
}),
|
||||
label: deployedHackatom.instances[2].label,
|
||||
sender: alice.address0,
|
||||
admin: alice.address1,
|
||||
});
|
||||
});
|
||||
|
||||
// Like previous test but filtered by message.action=store-code and message.action=instantiate
|
||||
it("can query by tags (module + code_id + action)", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
|
||||
{
|
||||
const uploads = await client.txsQuery(
|
||||
`message.module=wasm&message.code_id=${deployedHackatom.codeId}&message.action=store-code`,
|
||||
);
|
||||
expect(parseInt(uploads.count, 10)).toEqual(1);
|
||||
const store = fromOneElementArray(uploads.txs[0].tx.value.msg);
|
||||
assert(isMsgStoreCode(store));
|
||||
expect(store.value).toEqual(
|
||||
jasmine.objectContaining({
|
||||
sender: alice.address0,
|
||||
source: deployedHackatom.source,
|
||||
builder: deployedHackatom.builder,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const instantiations = await client.txsQuery(
|
||||
`message.module=wasm&message.code_id=${deployedHackatom.codeId}&message.action=instantiate`,
|
||||
);
|
||||
expect(parseInt(instantiations.count, 10)).toBeGreaterThanOrEqual(3);
|
||||
const [zero, one, two] = instantiations.txs.map((tx) => fromOneElementArray(tx.tx.value.msg));
|
||||
assert(isMsgInstantiateContract(zero));
|
||||
assert(isMsgInstantiateContract(one));
|
||||
assert(isMsgInstantiateContract(two));
|
||||
expect(zero.value).toEqual({
|
||||
code_id: deployedHackatom.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({
|
||||
beneficiary: deployedHackatom.instances[0].beneficiary,
|
||||
}),
|
||||
label: deployedHackatom.instances[0].label,
|
||||
sender: alice.address0,
|
||||
});
|
||||
expect(one.value).toEqual({
|
||||
code_id: deployedHackatom.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({
|
||||
beneficiary: deployedHackatom.instances[1].beneficiary,
|
||||
}),
|
||||
label: deployedHackatom.instances[1].label,
|
||||
sender: alice.address0,
|
||||
});
|
||||
expect(two.value).toEqual({
|
||||
code_id: deployedHackatom.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({
|
||||
beneficiary: deployedHackatom.instances[2].beneficiary,
|
||||
}),
|
||||
label: deployedHackatom.instances[2].label,
|
||||
sender: alice.address0,
|
||||
admin: alice.address1,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("broadcastTx", () => {
|
||||
it("can upload, instantiate and execute wasm", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = makeWasmClient(launchpad.endpoint);
|
||||
|
||||
let codeId: number;
|
||||
|
||||
// upload
|
||||
{
|
||||
// console.log("Raw log:", result.raw_log);
|
||||
const result = await uploadContract(wallet, getHackatom());
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const parsedLogs = logs.parseLogs(result.logs);
|
||||
const codeIdAttr = logs.findAttribute(parsedLogs, "message", "code_id");
|
||||
codeId = Number.parseInt(codeIdAttr.value, 10);
|
||||
expect(codeId).toBeGreaterThanOrEqual(1);
|
||||
expect(codeId).toBeLessThanOrEqual(200);
|
||||
expect(result.data).toEqual(toAscii(`${codeId}`));
|
||||
}
|
||||
|
||||
const funds = [coin(1234, "ucosm"), coin(321, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
let contractAddress: string;
|
||||
|
||||
// instantiate
|
||||
{
|
||||
const result = await instantiateContract(wallet, codeId, beneficiaryAddress, funds);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
// console.log("Raw log:", result.raw_log);
|
||||
const parsedLogs = logs.parseLogs(result.logs);
|
||||
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
|
||||
contractAddress = contractAddressAttr.value;
|
||||
const amountAttr = logs.findAttribute(parsedLogs, "transfer", "amount");
|
||||
expect(amountAttr.value).toEqual("1234ucosm,321ustake");
|
||||
expect(result.data).toEqual(Bech32.decode(contractAddress).data);
|
||||
|
||||
const balance = (await client.auth.account(contractAddress)).result.value.coins;
|
||||
expect(balance).toEqual(funds);
|
||||
}
|
||||
|
||||
// execute
|
||||
{
|
||||
const result = await executeContract(client, wallet, contractAddress, { release: {} });
|
||||
assert(!result.code);
|
||||
expect(result.data).toEqual("F00BAA");
|
||||
// console.log("Raw log:", result.logs);
|
||||
const parsedLogs = logs.parseLogs(result.logs);
|
||||
const wasmEvent = parsedLogs.find(() => true)?.events.find((e) => e.type === "wasm");
|
||||
assert(wasmEvent, "Event of type wasm expected");
|
||||
expect(wasmEvent.attributes).toContain({ key: "action", value: "release" });
|
||||
expect(wasmEvent.attributes).toContain({
|
||||
key: "destination",
|
||||
value: beneficiaryAddress,
|
||||
});
|
||||
|
||||
// Verify token transfer from contract to beneficiary
|
||||
const beneficiaryBalance = (await client.auth.account(beneficiaryAddress)).result.value.coins;
|
||||
expect(beneficiaryBalance).toEqual(funds);
|
||||
const contractBalance = (await client.auth.account(contractAddress)).result.value.coins;
|
||||
expect(contractBalance).toEqual([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,181 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { fromBase64, fromUtf8, toHex, toUtf8 } from "@cosmjs/encoding";
|
||||
import { LcdApiArray, LcdClient, normalizeLcdApiArray } from "@cosmjs/launchpad";
|
||||
|
||||
import { JsonObject, Model, parseWasmData, WasmData } from "../types";
|
||||
|
||||
type WasmResponse<T> = WasmSuccess<T> | WasmError;
|
||||
|
||||
interface WasmSuccess<T> {
|
||||
readonly height: string;
|
||||
readonly result: T;
|
||||
}
|
||||
|
||||
interface WasmError {
|
||||
readonly error: string;
|
||||
}
|
||||
|
||||
export interface CodeInfo {
|
||||
readonly id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
/** Hex-encoded sha256 hash of the code stored here */
|
||||
readonly data_hash: string;
|
||||
/**
|
||||
* An URL to a .tar.gz archive of the source code of the contract, which can be used to reproducibly build the Wasm bytecode.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly source?: string;
|
||||
/**
|
||||
* A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
|
||||
*
|
||||
* @example ```cosmwasm/rust-optimizer:0.8.0```
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly builder?: string;
|
||||
}
|
||||
|
||||
export interface CodeDetails extends CodeInfo {
|
||||
/** Base64 encoded raw wasm data */
|
||||
readonly data: string;
|
||||
}
|
||||
|
||||
// This is list view, without contract info
|
||||
export interface ContractInfo {
|
||||
readonly address: string;
|
||||
readonly code_id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
/** Bech32-encoded admin address */
|
||||
readonly admin?: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
// An entry in the contracts code/ migration history
|
||||
export interface ContractCodeHistoryEntry {
|
||||
/** The source of this history entry */
|
||||
readonly operation: "Genesis" | "Init" | "Migrate";
|
||||
readonly code_id: number;
|
||||
readonly msg: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface SmartQueryResponse {
|
||||
// base64 encoded response
|
||||
readonly smart: string;
|
||||
}
|
||||
|
||||
function isWasmError<T>(resp: WasmResponse<T>): resp is WasmError {
|
||||
return (resp as WasmError).error !== undefined;
|
||||
}
|
||||
|
||||
function unwrapWasmResponse<T>(response: WasmResponse<T>): T {
|
||||
if (isWasmError(response)) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27
|
||||
*/
|
||||
export interface WasmExtension {
|
||||
readonly wasm: {
|
||||
readonly listCodeInfo: () => Promise<readonly CodeInfo[]>;
|
||||
|
||||
/**
|
||||
* Downloads the original wasm bytecode by code ID.
|
||||
*
|
||||
* Throws an error if no code with this id
|
||||
*/
|
||||
readonly getCode: (id: number) => Promise<CodeDetails>;
|
||||
|
||||
readonly listContractsByCodeId: (id: number) => Promise<readonly ContractInfo[]>;
|
||||
|
||||
/**
|
||||
* Returns null when contract was not found at this address.
|
||||
*/
|
||||
readonly getContractInfo: (address: string) => Promise<ContractInfo | null>;
|
||||
|
||||
/**
|
||||
* Returns null when contract history was not found for this address.
|
||||
*/
|
||||
readonly getContractCodeHistory: (address: string) => Promise<ContractCodeHistoryEntry[] | null>;
|
||||
|
||||
/**
|
||||
* Returns all contract state.
|
||||
* This is an empty array if no such contract, or contract has no data.
|
||||
*/
|
||||
readonly getAllContractState: (address: string) => Promise<readonly Model[]>;
|
||||
|
||||
/**
|
||||
* Returns the data at the key if present (unknown decoded json),
|
||||
* or null if no data at this (contract address, key) pair
|
||||
*/
|
||||
readonly queryContractRaw: (address: string, key: Uint8Array) => Promise<Uint8Array | null>;
|
||||
|
||||
/**
|
||||
* Makes a smart query on the contract and parses the response as JSON.
|
||||
* Throws error if no such contract exists, the query format is invalid or the response is invalid.
|
||||
*/
|
||||
readonly queryContractSmart: (address: string, query: Record<string, unknown>) => Promise<JsonObject>;
|
||||
};
|
||||
}
|
||||
|
||||
export function setupWasmExtension(base: LcdClient): WasmExtension {
|
||||
return {
|
||||
wasm: {
|
||||
listCodeInfo: async () => {
|
||||
const path = `/wasm/code`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<LcdApiArray<CodeInfo>>;
|
||||
return normalizeLcdApiArray(unwrapWasmResponse(responseData));
|
||||
},
|
||||
getCode: async (id: number) => {
|
||||
const path = `/wasm/code/${id}`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<CodeDetails>;
|
||||
return unwrapWasmResponse(responseData);
|
||||
},
|
||||
listContractsByCodeId: async (id: number) => {
|
||||
const path = `/wasm/code/${id}/contracts`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<LcdApiArray<ContractInfo>>;
|
||||
return normalizeLcdApiArray(unwrapWasmResponse(responseData));
|
||||
},
|
||||
getContractInfo: async (address: string) => {
|
||||
const path = `/wasm/contract/${address}`;
|
||||
const response = (await base.get(path)) as WasmResponse<ContractInfo | null>;
|
||||
return unwrapWasmResponse(response);
|
||||
},
|
||||
getContractCodeHistory: async (address: string) => {
|
||||
const path = `/wasm/contract/${address}/history`;
|
||||
const response = (await base.get(path)) as WasmResponse<ContractCodeHistoryEntry[] | null>;
|
||||
return unwrapWasmResponse(response);
|
||||
},
|
||||
getAllContractState: async (address: string) => {
|
||||
const path = `/wasm/contract/${address}/state`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<LcdApiArray<WasmData>>;
|
||||
return normalizeLcdApiArray(unwrapWasmResponse(responseData)).map(parseWasmData);
|
||||
},
|
||||
queryContractRaw: async (address: string, key: Uint8Array) => {
|
||||
const hexKey = toHex(key);
|
||||
const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<WasmData[] | null | string>;
|
||||
const data = unwrapWasmResponse(responseData);
|
||||
if (Array.isArray(data)) {
|
||||
// The CosmWasm 0.10 interface
|
||||
return data.length === 0 ? null : fromBase64(data[0].val);
|
||||
} else {
|
||||
// The CosmWasm 0.11 interface
|
||||
return !data ? null : fromBase64(data); // Yes, we cannot differentiate empty fields from non-existent fields :(
|
||||
}
|
||||
},
|
||||
queryContractSmart: async (address: string, query: Record<string, unknown>) => {
|
||||
const encoded = toHex(toUtf8(JSON.stringify(query)));
|
||||
const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<SmartQueryResponse>;
|
||||
const result = unwrapWasmResponse(responseData);
|
||||
// By convention, smart queries must return a valid JSON document (see https://github.com/CosmWasm/cosmwasm/issues/144)
|
||||
return JSON.parse(fromUtf8(fromBase64(result.smart)));
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,146 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { Coin, Msg } from "@cosmjs/launchpad";
|
||||
|
||||
// TODO: implement
|
||||
/**
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.10.0-alpha/x/wasm/internal/types/params.go#L68-L71
|
||||
*/
|
||||
export type AccessConfig = never;
|
||||
|
||||
/**
|
||||
* Uploads Wasm code to the chain.
|
||||
* A numeric, auto-incrementing code ID will be generated as a result of the execution of this message.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.10.0-alpha/x/wasm/internal/types/msg.go#L10-L20
|
||||
*/
|
||||
export interface MsgStoreCode extends Msg {
|
||||
readonly type: "wasm/MsgStoreCode";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** Base64 encoded Wasm */
|
||||
readonly wasm_byte_code: string;
|
||||
/** A valid URI reference to the contract's source code. Can be empty. */
|
||||
readonly source: string;
|
||||
/** A docker tag. Can be empty. */
|
||||
readonly builder: string;
|
||||
readonly instantiate_permission?: AccessConfig;
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode {
|
||||
return (msg as MsgStoreCode).type === "wasm/MsgStoreCode";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of contract that was uploaded before.
|
||||
* This will trigger a call to the "init" export.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L104
|
||||
*/
|
||||
export interface MsgInstantiateContract extends Msg {
|
||||
readonly type: "wasm/MsgInstantiateContract";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** ID of the Wasm code that was uploaded before */
|
||||
readonly code_id: string;
|
||||
/** Human-readable label for this contract */
|
||||
readonly label: string;
|
||||
/** Init message as JavaScript object */
|
||||
readonly init_msg: any;
|
||||
readonly init_funds: readonly Coin[];
|
||||
/** Bech32-encoded admin address */
|
||||
readonly admin?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract {
|
||||
return (msg as MsgInstantiateContract).type === "wasm/MsgInstantiateContract";
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the admin of a contract
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-beta/x/wasm/internal/types/msg.go#L231
|
||||
*/
|
||||
export interface MsgUpdateAdmin extends Msg {
|
||||
readonly type: "wasm/MsgUpdateAdmin";
|
||||
readonly value: {
|
||||
/** Bech32-encoded sender address. This must be the old admin. */
|
||||
readonly sender: string;
|
||||
/** Bech32-encoded contract address to be updated */
|
||||
readonly contract: string;
|
||||
/** Bech32-encoded address of the new admin */
|
||||
readonly new_admin: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgUpdateAdmin(msg: Msg): msg is MsgUpdateAdmin {
|
||||
return (msg as MsgUpdateAdmin).type === "wasm/MsgUpdateAdmin";
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the admin of a contract, making it immutable.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-beta/x/wasm/internal/types/msg.go#L269
|
||||
*/
|
||||
export interface MsgClearAdmin extends Msg {
|
||||
readonly type: "wasm/MsgClearAdmin";
|
||||
readonly value: {
|
||||
/** Bech32-encoded sender address. This must be the old admin. */
|
||||
readonly sender: string;
|
||||
/** Bech32-encoded contract address to be updated */
|
||||
readonly contract: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgClearAdmin(msg: Msg): msg is MsgClearAdmin {
|
||||
return (msg as MsgClearAdmin).type === "wasm/MsgClearAdmin";
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a smart contract.
|
||||
* This will trigger a call to the "handle" export.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L158
|
||||
*/
|
||||
export interface MsgExecuteContract extends Msg {
|
||||
readonly type: "wasm/MsgExecuteContract";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** Bech32 account address */
|
||||
readonly contract: string;
|
||||
/** Handle message as JavaScript object */
|
||||
readonly msg: any;
|
||||
readonly sent_funds: readonly Coin[];
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract {
|
||||
return (msg as MsgExecuteContract).type === "wasm/MsgExecuteContract";
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates a contract to a new Wasm code.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L195
|
||||
*/
|
||||
export interface MsgMigrateContract extends Msg {
|
||||
readonly type: "wasm/MsgMigrateContract";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** Bech32 account address */
|
||||
readonly contract: string;
|
||||
/** The new code */
|
||||
readonly code_id: string;
|
||||
/** Migrate message as JavaScript object */
|
||||
readonly msg: any;
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgMigrateContract(msg: Msg): msg is MsgMigrateContract {
|
||||
return (msg as MsgMigrateContract).type === "wasm/MsgMigrateContract";
|
||||
}
|
||||
@ -1,565 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { sha256 } from "@cosmjs/crypto";
|
||||
import { toHex } from "@cosmjs/encoding";
|
||||
import {
|
||||
assertIsBroadcastTxSuccess,
|
||||
AuthExtension,
|
||||
coin,
|
||||
coins,
|
||||
GasPrice,
|
||||
LcdClient,
|
||||
MsgDelegate,
|
||||
Secp256k1HdWallet,
|
||||
setupAuthExtension,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
import { PrivateCosmWasmClient } from "./cosmwasmclient";
|
||||
import { setupWasmExtension, WasmExtension } from "./lcdapi/wasm";
|
||||
import { SigningCosmWasmClient, UploadMeta } from "./signingcosmwasmclient";
|
||||
import {
|
||||
alice,
|
||||
getHackatom,
|
||||
launchpad,
|
||||
makeRandomAddress,
|
||||
pendingWithoutLaunchpad,
|
||||
unused,
|
||||
} from "./testutils.spec";
|
||||
|
||||
function makeWasmClient(apiUrl: string): LcdClient & AuthExtension & WasmExtension {
|
||||
return LcdClient.withExtensions({ apiUrl }, setupAuthExtension, setupWasmExtension);
|
||||
}
|
||||
|
||||
describe("SigningCosmWasmClient", () => {
|
||||
describe("makeReadOnly", () => {
|
||||
it("can be constructed", async () => {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
|
||||
it("can be constructed with custom gas price", async () => {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const gasPrice = GasPrice.fromString("3.14utest");
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet, gasPrice);
|
||||
expect(client.fees).toEqual({
|
||||
upload: {
|
||||
amount: [
|
||||
{
|
||||
amount: "4710000",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "1500000",
|
||||
},
|
||||
init: {
|
||||
amount: [
|
||||
{
|
||||
amount: "1570000",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "500000",
|
||||
},
|
||||
migrate: {
|
||||
amount: [
|
||||
{
|
||||
amount: "628000",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "200000",
|
||||
},
|
||||
exec: {
|
||||
amount: [
|
||||
{
|
||||
amount: "628000",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "200000",
|
||||
},
|
||||
send: {
|
||||
amount: [
|
||||
{
|
||||
amount: "251200",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "80000",
|
||||
},
|
||||
changeAdmin: {
|
||||
amount: [
|
||||
{
|
||||
amount: "251200",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "80000",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("can be constructed with custom gas limits", async () => {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const gasLimits = {
|
||||
send: 160000,
|
||||
};
|
||||
const client = new SigningCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
undefined,
|
||||
gasLimits,
|
||||
);
|
||||
expect(client.fees).toEqual({
|
||||
upload: {
|
||||
amount: [
|
||||
{
|
||||
amount: "37500",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "1500000",
|
||||
},
|
||||
init: {
|
||||
amount: [
|
||||
{
|
||||
amount: "12500",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "500000",
|
||||
},
|
||||
migrate: {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "200000",
|
||||
},
|
||||
exec: {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "200000",
|
||||
},
|
||||
send: {
|
||||
amount: [
|
||||
{
|
||||
amount: "4000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "160000",
|
||||
},
|
||||
changeAdmin: {
|
||||
amount: [
|
||||
{
|
||||
amount: "2000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "80000",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("can be constructed with custom gas price and gas limits", async () => {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const gasPrice = GasPrice.fromString("3.14utest");
|
||||
const gasLimits = {
|
||||
send: 160000,
|
||||
};
|
||||
const client = new SigningCosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
gasPrice,
|
||||
gasLimits,
|
||||
);
|
||||
expect(client.fees).toEqual({
|
||||
upload: {
|
||||
amount: [
|
||||
{
|
||||
amount: "4710000",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "1500000",
|
||||
},
|
||||
init: {
|
||||
amount: [
|
||||
{
|
||||
amount: "1570000",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "500000",
|
||||
},
|
||||
migrate: {
|
||||
amount: [
|
||||
{
|
||||
amount: "628000",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "200000",
|
||||
},
|
||||
exec: {
|
||||
amount: [
|
||||
{
|
||||
amount: "628000",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "200000",
|
||||
},
|
||||
send: {
|
||||
amount: [
|
||||
{
|
||||
amount: "502400",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "160000",
|
||||
},
|
||||
changeAdmin: {
|
||||
amount: [
|
||||
{
|
||||
amount: "251200",
|
||||
denom: "utest",
|
||||
},
|
||||
],
|
||||
gas: "80000",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHeight", () => {
|
||||
it("always uses authAccount implementation", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
|
||||
const openedClient = client as unknown as PrivateCosmWasmClient;
|
||||
const blockLatestSpy = spyOn(openedClient.lcdClient, "blocksLatest").and.callThrough();
|
||||
const authAccountsSpy = spyOn(openedClient.lcdClient.auth, "account").and.callThrough();
|
||||
|
||||
const height = await client.getHeight();
|
||||
expect(height).toBeGreaterThan(0);
|
||||
|
||||
expect(blockLatestSpy).toHaveBeenCalledTimes(0);
|
||||
expect(authAccountsSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("upload", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const wasm = getHackatom().data;
|
||||
const { codeId, originalChecksum, originalSize, compressedChecksum, compressedSize } =
|
||||
await client.upload(wasm);
|
||||
expect(originalChecksum).toEqual(toHex(sha256(wasm)));
|
||||
expect(originalSize).toEqual(wasm.length);
|
||||
expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/);
|
||||
expect(compressedSize).toBeLessThan(wasm.length * 0.5);
|
||||
expect(codeId).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("can set builder and source", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const hackatom = getHackatom();
|
||||
|
||||
const meta: UploadMeta = {
|
||||
source: "https://crates.io/api/v1/crates/cw-nameservice/0.1.0/download",
|
||||
builder: "confio/cosmwasm-opt:0.6.2",
|
||||
};
|
||||
const { codeId } = await client.upload(hackatom.data, meta);
|
||||
|
||||
const codeDetails = await client.getCodeDetails(codeId);
|
||||
expect(codeDetails.source).toEqual(meta.source);
|
||||
expect(codeDetails.builder).toEqual(meta.builder);
|
||||
});
|
||||
});
|
||||
|
||||
describe("instantiate", () => {
|
||||
it("works with transfer amount", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
const funds = [coin(1234, "ucosm"), coin(321, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
memo: "Let's see if the memo is used",
|
||||
funds: funds,
|
||||
},
|
||||
);
|
||||
|
||||
const lcdClient = makeWasmClient(launchpad.endpoint);
|
||||
const balance = (await lcdClient.auth.account(contractAddress)).result.value.coins;
|
||||
expect(balance).toEqual(funds);
|
||||
});
|
||||
|
||||
it("works with admin", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{ admin: unused.address },
|
||||
);
|
||||
|
||||
const lcdClient = makeWasmClient(launchpad.endpoint);
|
||||
const contract = await lcdClient.wasm.getContractInfo(contractAddress);
|
||||
assert(contract);
|
||||
expect(contract.admin).toEqual(unused.address);
|
||||
});
|
||||
|
||||
it("can instantiate one code multiple times", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
const contractAddress1 = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: makeRandomAddress(),
|
||||
},
|
||||
"contract 1",
|
||||
);
|
||||
const contractAddress2 = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: makeRandomAddress(),
|
||||
},
|
||||
"contract 2",
|
||||
);
|
||||
expect(contractAddress1).not.toEqual(contractAddress2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateAdmin", () => {
|
||||
it("can update an admin", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
admin: alice.address0,
|
||||
},
|
||||
);
|
||||
|
||||
const lcdClient = makeWasmClient(launchpad.endpoint);
|
||||
const state1 = await lcdClient.wasm.getContractInfo(contractAddress);
|
||||
assert(state1);
|
||||
expect(state1.admin).toEqual(alice.address0);
|
||||
|
||||
await client.updateAdmin(contractAddress, unused.address);
|
||||
|
||||
const state2 = await lcdClient.wasm.getContractInfo(contractAddress);
|
||||
assert(state2);
|
||||
expect(state2.admin).toEqual(unused.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearAdmin", () => {
|
||||
it("can clear an admin", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
admin: alice.address0,
|
||||
},
|
||||
);
|
||||
|
||||
const lcdClient = makeWasmClient(launchpad.endpoint);
|
||||
const state1 = await lcdClient.wasm.getContractInfo(contractAddress);
|
||||
assert(state1);
|
||||
expect(state1.admin).toEqual(alice.address0);
|
||||
|
||||
await client.clearAdmin(contractAddress);
|
||||
|
||||
const state2 = await lcdClient.wasm.getContractInfo(contractAddress);
|
||||
assert(state2);
|
||||
expect(state2.admin).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("migrate", () => {
|
||||
it("can can migrate from one code ID to another", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const { codeId: codeId1 } = await client.upload(getHackatom().data);
|
||||
const { codeId: codeId2 } = await client.upload(getHackatom().data);
|
||||
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId1,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
admin: alice.address0,
|
||||
},
|
||||
);
|
||||
|
||||
const lcdClient = makeWasmClient(launchpad.endpoint);
|
||||
const state1 = await lcdClient.wasm.getContractInfo(contractAddress);
|
||||
assert(state1);
|
||||
expect(state1.admin).toEqual(alice.address0);
|
||||
|
||||
const newVerifier = makeRandomAddress();
|
||||
await client.migrate(contractAddress, codeId2, { verifier: newVerifier });
|
||||
|
||||
const state2 = await lcdClient.wasm.getContractInfo(contractAddress);
|
||||
assert(state2);
|
||||
expect(state2).toEqual({
|
||||
...state1,
|
||||
code_id: codeId2,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
// instantiate
|
||||
const funds = [coin(233444, "ucosm"), coin(5454, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"amazing random contract",
|
||||
{
|
||||
funds: funds,
|
||||
},
|
||||
);
|
||||
|
||||
// execute
|
||||
const result = await client.execute(contractAddress, { release: {} }, undefined);
|
||||
const wasmEvent = result.logs.find(() => true)?.events.find((e) => e.type === "wasm");
|
||||
assert(wasmEvent, "Event of type wasm expected");
|
||||
expect(wasmEvent.attributes).toContain({ key: "action", value: "release" });
|
||||
expect(wasmEvent.attributes).toContain({
|
||||
key: "destination",
|
||||
value: beneficiaryAddress,
|
||||
});
|
||||
|
||||
// Verify token transfer from contract to beneficiary
|
||||
const lcdClient = makeWasmClient(launchpad.endpoint);
|
||||
const beneficiaryBalance = (await lcdClient.auth.account(beneficiaryAddress)).result.value.coins;
|
||||
expect(beneficiaryBalance).toEqual(funds);
|
||||
const contractBalance = (await lcdClient.auth.account(contractAddress)).result.value.coins;
|
||||
expect(contractBalance).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendTokens", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
|
||||
const amount = coins(7890, "ucosm");
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
|
||||
// no tokens here
|
||||
const before = await client.getAccount(beneficiaryAddress);
|
||||
expect(before).toBeUndefined();
|
||||
|
||||
// send
|
||||
const result = await client.sendTokens(beneficiaryAddress, amount, "for dinner");
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const [firstLog] = result.logs;
|
||||
expect(firstLog).toBeTruthy();
|
||||
|
||||
// got tokens
|
||||
const after = await client.getAccount(beneficiaryAddress);
|
||||
assert(after);
|
||||
expect(after.balance).toEqual(amount);
|
||||
});
|
||||
});
|
||||
|
||||
describe("signAndBroadcast", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(launchpad.endpoint, alice.address0, wallet);
|
||||
|
||||
const msg: MsgDelegate = {
|
||||
type: "cosmos-sdk/MsgDelegate",
|
||||
value: {
|
||||
delegator_address: alice.address0,
|
||||
validator_address: launchpad.validator.address,
|
||||
amount: coin(1234, "ustake"),
|
||||
},
|
||||
};
|
||||
const fee = {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "180000", // 180k
|
||||
};
|
||||
const result = await client.signAndBroadcast([msg], fee, "Use your power wisely");
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,370 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { sha256 } from "@cosmjs/crypto";
|
||||
import { toBase64, toHex } from "@cosmjs/encoding";
|
||||
import {
|
||||
BroadcastMode,
|
||||
BroadcastTxFailure,
|
||||
BroadcastTxResult,
|
||||
buildFeeTable,
|
||||
Coin,
|
||||
CosmosFeeTable,
|
||||
GasLimits,
|
||||
GasPrice,
|
||||
isBroadcastTxFailure,
|
||||
logs,
|
||||
makeSignDoc,
|
||||
makeStdTx,
|
||||
Msg,
|
||||
MsgSend,
|
||||
OfflineSigner,
|
||||
StdFee,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { Uint53 } from "@cosmjs/math";
|
||||
import pako from "pako";
|
||||
|
||||
import { isValidBuilder } from "./builder";
|
||||
import { Account, CosmWasmClient, GetSequenceResult } from "./cosmwasmclient";
|
||||
import {
|
||||
MsgClearAdmin,
|
||||
MsgExecuteContract,
|
||||
MsgInstantiateContract,
|
||||
MsgMigrateContract,
|
||||
MsgStoreCode,
|
||||
MsgUpdateAdmin,
|
||||
} from "./msgs";
|
||||
|
||||
/**
|
||||
* These fees are used by the higher level methods of SigningCosmWasmClient
|
||||
*/
|
||||
export interface CosmWasmFeeTable extends CosmosFeeTable {
|
||||
readonly upload: StdFee;
|
||||
readonly init: StdFee;
|
||||
readonly exec: StdFee;
|
||||
readonly migrate: StdFee;
|
||||
/** Paid when setting the contract admin to a new address or unsetting it */
|
||||
readonly changeAdmin: StdFee;
|
||||
}
|
||||
|
||||
function prepareBuilder(builder: string | undefined): string {
|
||||
if (builder === undefined) {
|
||||
return ""; // normalization needed by backend
|
||||
} else {
|
||||
if (!isValidBuilder(builder)) throw new Error("The builder (Docker Hub image with tag) is not valid");
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultGasPrice = GasPrice.fromString("0.025ucosm");
|
||||
const defaultGasLimits: GasLimits<CosmWasmFeeTable> = {
|
||||
upload: 1_500_000,
|
||||
init: 500_000,
|
||||
migrate: 200_000,
|
||||
exec: 200_000,
|
||||
send: 80_000,
|
||||
changeAdmin: 80_000,
|
||||
};
|
||||
|
||||
export interface UploadMeta {
|
||||
/**
|
||||
* An URL to a .tar.gz archive of the source code of the contract, which can be used to reproducibly build the Wasm bytecode.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly source?: string;
|
||||
/**
|
||||
* A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
|
||||
*
|
||||
* @example ```cosmwasm/rust-optimizer:0.8.0```
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly builder?: string;
|
||||
}
|
||||
|
||||
export interface UploadResult {
|
||||
/** Size of the original wasm code in bytes */
|
||||
readonly originalSize: number;
|
||||
/** A hex encoded sha256 checksum of the original wasm code (that is stored on chain) */
|
||||
readonly originalChecksum: string;
|
||||
/** Size of the compressed wasm code in bytes */
|
||||
readonly compressedSize: number;
|
||||
/** A hex encoded sha256 checksum of the compressed wasm code (that stored in the transaction) */
|
||||
readonly compressedChecksum: string;
|
||||
/** The ID of the code asigned by the chain */
|
||||
readonly codeId: number;
|
||||
readonly logs: readonly logs.Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options of an .instantiate() call.
|
||||
* All properties are optional.
|
||||
*/
|
||||
export interface InstantiateOptions {
|
||||
readonly memo?: string;
|
||||
/**
|
||||
* The funds that are transferred from the sender to the newly created contract.
|
||||
* The funds are transferred as part of the message execution after the contract address is
|
||||
* created and before the instantiation message is executed by the contract.
|
||||
*
|
||||
* Only native tokens are supported.
|
||||
*/
|
||||
readonly funds?: readonly Coin[];
|
||||
/**
|
||||
* A bech32 encoded address of an admin account.
|
||||
* Caution: an admin has the privilege to upgrade a contract. If this is not desired, do not set this value.
|
||||
*/
|
||||
readonly admin?: string;
|
||||
}
|
||||
|
||||
export interface InstantiateResult {
|
||||
/** The address of the newly instantiated contract */
|
||||
readonly contractAddress: string;
|
||||
readonly logs: readonly logs.Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type of updateAdmin and clearAdmin
|
||||
*/
|
||||
export interface ChangeAdminResult {
|
||||
readonly logs: readonly logs.Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
export interface MigrateResult {
|
||||
readonly logs: readonly logs.Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
export interface ExecuteResult {
|
||||
readonly logs: readonly logs.Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
function createBroadcastTxErrorMessage(result: BroadcastTxFailure): string {
|
||||
return `Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`;
|
||||
}
|
||||
|
||||
export class SigningCosmWasmClient extends CosmWasmClient {
|
||||
public readonly fees: CosmWasmFeeTable;
|
||||
public readonly signerAddress: string;
|
||||
|
||||
private readonly signer: OfflineSigner;
|
||||
|
||||
/**
|
||||
* Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient.
|
||||
*
|
||||
* This instance does a lot of caching. In order to benefit from that you should try to use one instance
|
||||
* for the lifetime of your application. When switching backends, a new instance must be created.
|
||||
*
|
||||
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
|
||||
* @param signerAddress The address that will sign transactions using this instance. The `signer` must be able to sign with this address.
|
||||
* @param signer An implementation of OfflineSigner which can provide signatures for transactions, potentially requiring user input.
|
||||
* @param gasPrice The price paid per unit of gas
|
||||
* @param gasLimits Custom overrides for gas limits related to specific transaction types
|
||||
* @param broadcastMode Defines at which point of the transaction processing the broadcastTx method returns
|
||||
*/
|
||||
public constructor(
|
||||
apiUrl: string,
|
||||
signerAddress: string,
|
||||
signer: OfflineSigner,
|
||||
gasPrice: GasPrice = defaultGasPrice,
|
||||
gasLimits: Partial<GasLimits<CosmWasmFeeTable>> = {},
|
||||
broadcastMode = BroadcastMode.Block,
|
||||
) {
|
||||
super(apiUrl, broadcastMode);
|
||||
this.anyValidAddress = signerAddress;
|
||||
this.signerAddress = signerAddress;
|
||||
this.signer = signer;
|
||||
this.fees = buildFeeTable<CosmWasmFeeTable>(gasPrice, defaultGasLimits, gasLimits);
|
||||
}
|
||||
|
||||
public override async getSequence(address?: string): Promise<GetSequenceResult> {
|
||||
return super.getSequence(address || this.signerAddress);
|
||||
}
|
||||
|
||||
public override async getAccount(address?: string): Promise<Account | undefined> {
|
||||
return super.getAccount(address || this.signerAddress);
|
||||
}
|
||||
|
||||
/** Uploads code and returns a receipt, including the code ID */
|
||||
public async upload(wasmCode: Uint8Array, meta: UploadMeta = {}, memo = ""): Promise<UploadResult> {
|
||||
const source = meta.source || "";
|
||||
const builder = prepareBuilder(meta.builder);
|
||||
|
||||
const compressed = pako.gzip(wasmCode, { level: 9 });
|
||||
const storeCodeMsg: MsgStoreCode = {
|
||||
type: "wasm/MsgStoreCode",
|
||||
value: {
|
||||
sender: this.signerAddress,
|
||||
wasm_byte_code: toBase64(compressed),
|
||||
source: source,
|
||||
builder: builder,
|
||||
},
|
||||
};
|
||||
const result = await this.signAndBroadcast([storeCodeMsg], this.fees.upload, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
const codeIdAttr = logs.findAttribute(result.logs, "message", "code_id");
|
||||
return {
|
||||
originalSize: wasmCode.length,
|
||||
originalChecksum: toHex(sha256(wasmCode)),
|
||||
compressedSize: compressed.length,
|
||||
compressedChecksum: toHex(sha256(compressed)),
|
||||
codeId: Number.parseInt(codeIdAttr.value, 10),
|
||||
logs: result.logs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async instantiate(
|
||||
codeId: number,
|
||||
msg: Record<string, unknown>,
|
||||
label: string,
|
||||
options: InstantiateOptions = {},
|
||||
): Promise<InstantiateResult> {
|
||||
const instantiateMsg: MsgInstantiateContract = {
|
||||
type: "wasm/MsgInstantiateContract",
|
||||
value: {
|
||||
sender: this.signerAddress,
|
||||
code_id: new Uint53(codeId).toString(),
|
||||
label: label,
|
||||
init_msg: msg,
|
||||
init_funds: options.funds || [],
|
||||
admin: options.admin,
|
||||
},
|
||||
};
|
||||
const result = await this.signAndBroadcast([instantiateMsg], this.fees.init, options.memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
const contractAddressAttr = logs.findAttribute(result.logs, "message", "contract_address");
|
||||
return {
|
||||
contractAddress: contractAddressAttr.value,
|
||||
logs: result.logs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async updateAdmin(contractAddress: string, newAdmin: string, memo = ""): Promise<ChangeAdminResult> {
|
||||
const updateAdminMsg: MsgUpdateAdmin = {
|
||||
type: "wasm/MsgUpdateAdmin",
|
||||
value: {
|
||||
sender: this.signerAddress,
|
||||
contract: contractAddress,
|
||||
new_admin: newAdmin,
|
||||
},
|
||||
};
|
||||
const result = await this.signAndBroadcast([updateAdminMsg], this.fees.changeAdmin, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: result.logs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async clearAdmin(contractAddress: string, memo = ""): Promise<ChangeAdminResult> {
|
||||
const clearAdminMsg: MsgClearAdmin = {
|
||||
type: "wasm/MsgClearAdmin",
|
||||
value: {
|
||||
sender: this.signerAddress,
|
||||
contract: contractAddress,
|
||||
},
|
||||
};
|
||||
const result = await this.signAndBroadcast([clearAdminMsg], this.fees.changeAdmin, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: result.logs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async migrate(
|
||||
contractAddress: string,
|
||||
codeId: number,
|
||||
migrateMsg: Record<string, unknown>,
|
||||
memo = "",
|
||||
): Promise<MigrateResult> {
|
||||
const msg: MsgMigrateContract = {
|
||||
type: "wasm/MsgMigrateContract",
|
||||
value: {
|
||||
sender: this.signerAddress,
|
||||
contract: contractAddress,
|
||||
code_id: new Uint53(codeId).toString(),
|
||||
msg: migrateMsg,
|
||||
},
|
||||
};
|
||||
const result = await this.signAndBroadcast([msg], this.fees.migrate, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: result.logs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async execute(
|
||||
contractAddress: string,
|
||||
msg: Record<string, unknown>,
|
||||
memo = "",
|
||||
funds?: readonly Coin[],
|
||||
): Promise<ExecuteResult> {
|
||||
const executeMsg: MsgExecuteContract = {
|
||||
type: "wasm/MsgExecuteContract",
|
||||
value: {
|
||||
sender: this.signerAddress,
|
||||
contract: contractAddress,
|
||||
msg: msg,
|
||||
sent_funds: funds || [],
|
||||
},
|
||||
};
|
||||
const result = await this.signAndBroadcast([executeMsg], this.fees.exec, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: result.logs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async sendTokens(
|
||||
recipientAddress: string,
|
||||
amount: readonly Coin[],
|
||||
memo = "",
|
||||
): Promise<BroadcastTxResult> {
|
||||
const sendMsg: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: this.signerAddress,
|
||||
to_address: recipientAddress,
|
||||
amount: amount,
|
||||
},
|
||||
};
|
||||
return this.signAndBroadcast([sendMsg], this.fees.send, memo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets account number and sequence from the API, creates a sign doc,
|
||||
* creates a single signature, assembles the signed transaction and broadcasts it.
|
||||
*/
|
||||
public async signAndBroadcast(msgs: readonly Msg[], fee: StdFee, memo = ""): Promise<BroadcastTxResult> {
|
||||
const { accountNumber, sequence } = await this.getSequence();
|
||||
const chainId = await this.getChainId();
|
||||
const signDoc = makeSignDoc(msgs, fee, chainId, memo, accountNumber, sequence);
|
||||
const { signed, signature } = await this.signer.signAmino(this.signerAddress, signDoc);
|
||||
const signedTx = makeStdTx(signed, signature);
|
||||
return this.broadcastTx(signedTx);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -1,44 +0,0 @@
|
||||
{
|
||||
"//source": "https://hubble.figment.network/cosmos/chains/cosmoshub-3/blocks/415777/transactions/2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4?format=json",
|
||||
"tx": {
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cosmos-sdk/MsgSend",
|
||||
"value": {
|
||||
"from_address": "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq",
|
||||
"to_address": "cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae",
|
||||
"amount": [
|
||||
{
|
||||
"denom": "uatom",
|
||||
"amount": "35997500"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [
|
||||
{
|
||||
"denom": "uatom",
|
||||
"amount": "2500"
|
||||
}
|
||||
],
|
||||
"gas": "100000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "A5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAq"
|
||||
},
|
||||
"signature": "NK1Oy4EUGAsoC03c1wi9GG03JC/39LEdautC5Jk643oIbEPqeXHMwaqbdvO/Jws0X/NAXaN8SAy2KNY5Qml+5Q=="
|
||||
}
|
||||
],
|
||||
"memo": ""
|
||||
}
|
||||
},
|
||||
"tx_data": "ygEoKBapCkOoo2GaChRZgJnSW8Lg8zwesNppHWhJTrk8uhIUmSc4HyYqQahKSZHt4pN2aKsALu8aEQoFdWF0b20SCDM1OTk3NTAwEhMKDQoFdWF0b20SBDI1MDAQoI0GGmoKJuta6YchA5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAqEkA0rU7LgRQYCygLTdzXCL0YbTckL/f0sR1q60LkmTrjeghsQ+p5cczBqpt2878nCzRf80Bdo3xIDLYo1jlCaX7l",
|
||||
"id": "2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4"
|
||||
}
|
||||
@ -1,167 +0,0 @@
|
||||
import { Random } from "@cosmjs/crypto";
|
||||
import { Bech32, fromBase64 } from "@cosmjs/encoding";
|
||||
|
||||
import hackatom from "./testdata/contract.json";
|
||||
|
||||
/** An internal testing type. SigningCosmWasmClient has a similar but different interface */
|
||||
export interface ContractUploadInstructions {
|
||||
/** The wasm bytecode */
|
||||
readonly data: Uint8Array;
|
||||
readonly source?: string;
|
||||
readonly builder?: string;
|
||||
}
|
||||
|
||||
export function getHackatom(): ContractUploadInstructions {
|
||||
return {
|
||||
data: fromBase64(hackatom.data),
|
||||
source: "https://some.registry.nice/project/raw/0.7/lib/vm/testdata/contract_0.6.wasm.blub.tar.gz",
|
||||
builder: "confio/cosmwasm-opt:12.34.56",
|
||||
};
|
||||
}
|
||||
|
||||
export function makeRandomAddress(): string {
|
||||
return Bech32.encode("cosmos", Random.getBytes(20));
|
||||
}
|
||||
|
||||
export const tendermintIdMatcher = /^[0-9A-F]{64}$/;
|
||||
/** @see https://rgxdb.com/r/1NUN74O6 */
|
||||
export const base64Matcher =
|
||||
/^(?:[a-zA-Z0-9+/]{4})*(?:|(?:[a-zA-Z0-9+/]{3}=)|(?:[a-zA-Z0-9+/]{2}==)|(?:[a-zA-Z0-9+/]{1}===))$/;
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32
|
||||
export const bech32AddressMatcher = /^[\x21-\x7e]{1,83}1[02-9ac-hj-np-z]{38}$/;
|
||||
|
||||
export const alice = {
|
||||
mnemonic: "enlist hip relief stomach skate base shallow young switch frequent cry park",
|
||||
pubkey0: {
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
value: "A9cXhWb8ZpqCzkA8dQCPV29KdeRLV3rUYxrkHudLbQtS",
|
||||
},
|
||||
address0: "cosmos14qemq0vw6y3gc3u3e0aty2e764u4gs5le3hada",
|
||||
address1: "cosmos1hhg2rlu9jscacku2wwckws7932qqqu8x3gfgw0",
|
||||
address2: "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5",
|
||||
address3: "cosmos17yg9mssjenmc3jkqth6ulcwj9cxujrxxzezwta",
|
||||
address4: "cosmos1f7j7ryulwjfe9ljplvhtcaxa6wqgula3etktce",
|
||||
};
|
||||
|
||||
/** Unused account */
|
||||
export const unused = {
|
||||
pubkey: {
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
value: "ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ",
|
||||
},
|
||||
address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u",
|
||||
accountNumber: 19,
|
||||
sequence: 0,
|
||||
};
|
||||
|
||||
/** Deployed as part of scripts/launchpad/init.sh */
|
||||
export const deployedHackatom = {
|
||||
codeId: 1,
|
||||
source: "https://crates.io/api/v1/crates/hackatom/not-yet-released/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.9.1",
|
||||
checksum: "3defc33a41f58c71d38b176d521c411d8e74d26403fde7660486930c7579a016",
|
||||
instances: [
|
||||
{
|
||||
beneficiary: alice.address0,
|
||||
address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
label: "From deploy_hackatom.js (0)",
|
||||
},
|
||||
{
|
||||
beneficiary: alice.address1,
|
||||
address: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
label: "From deploy_hackatom.js (1)",
|
||||
},
|
||||
{
|
||||
beneficiary: alice.address2,
|
||||
address: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
|
||||
label: "From deploy_hackatom.js (2)",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/** Deployed as part of scripts/launchpad/init.sh */
|
||||
export const deployedErc20 = {
|
||||
codeId: 2,
|
||||
source: "https://crates.io/api/v1/crates/cw-erc20/0.7.0/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.10.4",
|
||||
checksum: "d04368320ad55089384adb171aaea39e43d710d7608829adba0300ed30aa2988",
|
||||
instances: [
|
||||
"cosmos1vjecguu37pmd577339wrdp208ddzymkudc46zj", // HASH
|
||||
"cosmos1ym5m5dw7pttft5w430nxx6uat8f84ck4algmhg", // ISA
|
||||
"cosmos1gv07846a3867ezn3uqkk082c5ftke7hpllcu8q", // JADE
|
||||
],
|
||||
};
|
||||
|
||||
/** Deployed as part of scripts/launchpad/init.sh */
|
||||
export const deployedCw3 = {
|
||||
codeId: 3,
|
||||
source: "https://crates.io/api/v1/crates/cw3-fixed-multisig/0.3.1/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.10.4",
|
||||
instances: [
|
||||
"cosmos1xqeym28j9xgv0p93pwwt6qcxf9tdvf9zddufdw", // Multisig (1/3)
|
||||
"cosmos1jka38ckju8cpjap00jf9xdvdyttz9caujtd6t5", // Multisig (2/3)
|
||||
"cosmos12dnl585uxzddjw9hw4ca694f054shgpgr4zg80", // Multisig (uneven weights)
|
||||
],
|
||||
};
|
||||
|
||||
/** Deployed as part of scripts/launchpad/init.sh */
|
||||
export const deployedCw1 = {
|
||||
codeId: 4,
|
||||
source: "https://crates.io/api/v1/crates/cw1-subkeys/0.3.1/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.10.4",
|
||||
instances: ["cosmos1vs2vuks65rq7xj78mwtvn7vvnm2gn7ad5me0d2"],
|
||||
};
|
||||
|
||||
export const launchpad = {
|
||||
endpoint: "http://localhost:1317",
|
||||
chainId: "testing",
|
||||
validator: {
|
||||
address: "cosmosvaloper1yfkkk04ve8a0sugj4fe6q6zxuvmvza8r3arurr",
|
||||
},
|
||||
};
|
||||
|
||||
export function launchpadEnabled(): boolean {
|
||||
return !!process.env.LAUNCHPAD_ENABLED;
|
||||
}
|
||||
|
||||
export function pendingWithoutLaunchpad(): void {
|
||||
if (!launchpadEnabled()) {
|
||||
return pending("Set LAUNCHPAD_ENABLED to enable Launchpad-based tests");
|
||||
}
|
||||
}
|
||||
|
||||
export function erc20Enabled(): boolean {
|
||||
return !!process.env.ERC20_ENABLED;
|
||||
}
|
||||
|
||||
export function pendingWithoutErc20(): void {
|
||||
if (!erc20Enabled()) {
|
||||
return pending("Set ERC20_ENABLED to enable ERC20-based tests");
|
||||
}
|
||||
}
|
||||
|
||||
export function cw3Enabled(): boolean {
|
||||
return !!process.env.CW3_ENABLED;
|
||||
}
|
||||
|
||||
export function pendingWithoutCw3(): void {
|
||||
if (!cw3Enabled()) {
|
||||
return pending("Set CW3_ENABLED to enable CW3-based tests");
|
||||
}
|
||||
}
|
||||
|
||||
export function cw1Enabled(): boolean {
|
||||
return !!process.env.CW1_ENABLED;
|
||||
}
|
||||
|
||||
export function pendingWithoutCw1(): void {
|
||||
if (!cw1Enabled()) {
|
||||
return pending("Set CW1_ENABLED to enable CW1-based tests");
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns first element. Throws if array has a different length than 1. */
|
||||
export function fromOneElementArray<T>(elements: ArrayLike<T>): T {
|
||||
if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`);
|
||||
return elements[0];
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
import { fromBase64, fromHex } from "@cosmjs/encoding";
|
||||
|
||||
export interface WasmData {
|
||||
// key is hex-encoded
|
||||
readonly key: string;
|
||||
// value is base64 encoded
|
||||
readonly val: string;
|
||||
}
|
||||
|
||||
// Model is a parsed WasmData object
|
||||
export interface Model {
|
||||
readonly key: Uint8Array;
|
||||
readonly val: Uint8Array;
|
||||
}
|
||||
|
||||
export function parseWasmData({ key, val }: WasmData): Model {
|
||||
return {
|
||||
key: fromHex(key),
|
||||
val: fromBase64(val),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing a parsed JSON document. The result of JSON.parse().
|
||||
* This doesn't provide any type safety over `any` but expresses intent in the code.
|
||||
*/
|
||||
export type JsonObject = any;
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
// extend your base config so you don't have to redefine your compilerOptions
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"*.js",
|
||||
".eslintrc.js"
|
||||
]
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "build",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
const packageJson = require("./package.json");
|
||||
|
||||
module.exports = {
|
||||
entryPoints: ["./src"],
|
||||
out: "docs",
|
||||
exclude: "**/*.spec.ts",
|
||||
name: `${packageJson.name} Documentation`,
|
||||
readme: "README.md",
|
||||
excludeExternals: true,
|
||||
excludePrivate: true,
|
||||
};
|
||||
@ -1,40 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
const glob = require("glob");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const target = "web";
|
||||
const distdir = path.join(__dirname, "dist", "web");
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
// bundle used for Karma tests
|
||||
target: target,
|
||||
entry: glob.sync("./build/**/*.spec.js"),
|
||||
output: {
|
||||
path: distdir,
|
||||
filename: "tests.js",
|
||||
},
|
||||
plugins: [
|
||||
new webpack.EnvironmentPlugin({
|
||||
LAUNCHPAD_ENABLED: "",
|
||||
ERC20_ENABLED: "",
|
||||
CW3_ENABLED: "",
|
||||
CW1_ENABLED: "",
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ["buffer", "Buffer"],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
fallback: {
|
||||
buffer: false,
|
||||
crypto: false,
|
||||
events: false,
|
||||
path: false,
|
||||
stream: require.resolve("stream-browserify"),
|
||||
string_decoder: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -1 +0,0 @@
|
||||
../../.eslintignore
|
||||
@ -1,92 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
es6: true,
|
||||
jasmine: true,
|
||||
node: true,
|
||||
worker: true,
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
project: "./tsconfig.eslint.json",
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ["@typescript-eslint", "prettier", "simple-import-sort", "import"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:import/typescript",
|
||||
],
|
||||
rules: {
|
||||
curly: ["warn", "multi-line", "consistent"],
|
||||
"no-bitwise": "warn",
|
||||
"no-console": ["warn", { allow: ["error", "info", "table", "warn"] }],
|
||||
"no-param-reassign": "warn",
|
||||
"no-shadow": "off", // disabled in favour of @typescript-eslint/no-shadow, see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-shadow.md
|
||||
"no-unused-vars": "off", // disabled in favour of @typescript-eslint/no-unused-vars, see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md
|
||||
"prefer-const": "warn",
|
||||
radix: ["warn", "always"],
|
||||
"spaced-comment": ["warn", "always", { line: { markers: ["/ <reference"] } }],
|
||||
"import/no-cycle": "warn",
|
||||
"simple-import-sort/imports": "warn",
|
||||
"@typescript-eslint/array-type": ["warn", { default: "array-simple" }],
|
||||
"@typescript-eslint/await-thenable": "warn",
|
||||
"@typescript-eslint/ban-types": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": ["warn", { allowExpressions: true }],
|
||||
"@typescript-eslint/explicit-member-accessibility": "warn",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{
|
||||
selector: "default",
|
||||
format: ["strictCamelCase"],
|
||||
},
|
||||
{
|
||||
selector: "typeLike",
|
||||
format: ["StrictPascalCase"],
|
||||
},
|
||||
{
|
||||
selector: "enumMember",
|
||||
format: ["StrictPascalCase"],
|
||||
},
|
||||
{
|
||||
selector: "variable",
|
||||
format: ["strictCamelCase"],
|
||||
leadingUnderscore: "allow",
|
||||
},
|
||||
{
|
||||
selector: "parameter",
|
||||
format: ["strictCamelCase"],
|
||||
leadingUnderscore: "allow",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-dynamic-delete": "warn",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"@typescript-eslint/no-parameter-properties": "warn",
|
||||
"@typescript-eslint/no-shadow": "warn",
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
|
||||
"@typescript-eslint/no-use-before-define": "warn",
|
||||
"@typescript-eslint/prefer-readonly": "warn",
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: "**/*.js",
|
||||
rules: {
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: "**/*.spec.ts",
|
||||
rules: {
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
3
packages/cosmwasm/.gitignore
vendored
3
packages/cosmwasm/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
build/
|
||||
dist/
|
||||
docs/
|
||||
@ -1,5 +0,0 @@
|
||||
# @cosmjs/cosmwasm
|
||||
|
||||
This package is deprecated and simply wraps `@cosmjs/cosmwasm-launchpad`, which
|
||||
should be used for Launchpad support. At some point in the future this package
|
||||
name will be used for a Stargate-compatible CosmWasm package.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user