From 978a6711f57834db41f264ef9a7f028f29f0ac42 Mon Sep 17 00:00:00 2001 From: rrr523 <59029880+rrr523@users.noreply.github.com> Date: Fri, 1 Sep 2023 11:47:01 +0800 Subject: [PATCH] Release 0.2.4 (#300) * feat: Update Types * chore: [ci] release (alpha) * Feat/query status (#42) * fix: Return types * feat: Feegrant * fix: Pubkey typeUrl * feat: Upgrade Cosmos SDK * refactor: Simply broadcast and simulate tx * feat: Support Tx by PrivateKey * feat: Replace local message with types npm * test: Test Framework * chore: Upgrade Example wagmi version, Replace ethersjs with viem * feat: Support EIP712 callback use external provider * feat: Support dynamic provider * test: Support jest test * test: Account Query * feat: Change Tx API * feat: Type Complete * chore: Complate Example in New API * fix: Rollup bundle Support Nodejs * feat: Resolute Module (IoC) * docs: Update README * chore: [ci] release (alpha) (#43) Co-authored-by: github-actions[bot] * Feat/multi msg (#49) * feat: Multi Send * feat: Compatibility multi tx message * Revert "Feat/multi msg (#49)" (#53) This reverts commit fc3dd986646624c81f3cc43bdffb02e70d170b4f. * Update WASM (#55) * feat: Update WASM * feat: Update create object tx * docs: Update doc (#56) * feat: Pre alpha (#57) * chore: [ci] release (alpha) (#58) Co-authored-by: github-actions[bot] * Update README.md * multi msg (#59) * feat: Multi Send * feat: Compatibility multi tx message * chore: [ci] release (alpha) (#62) Co-authored-by: github-actions[bot] * fix: CreateObject Msg (#65) * feat: add offchainauth method (#64) * feat: Add offchainauth method * feat: Compatiable old get approval method * Create ten-suits-crash.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> Co-authored-by: rrr523 * chore: [ci] release (alpha) (#66) Co-authored-by: github-actions[bot] * feat: Add Sp params (#67) * feat: Optimize get approval types and add throw error (#70) * feat: Optimize get approval types and add throw error * Create fluffy-snails-explain.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#68) Co-authored-by: github-actions[bot] * chore: Update example without greenfield-cosmos-types (#71) * fix: Return Promise (#72) * fix: Return Promise * Create strong-snails-count.md * chore: [ci] release (alpha) (#73) Co-authored-by: github-actions[bot] * fix: Create Group message (#74) * fix: Create Group message * Create young-bears-scream.md * feat: Add Storage Params API (#76) * chore: [ci] release (alpha) (#75) Co-authored-by: github-actions[bot] * Chore/group example (#77) * feat: Group API * chore: Group and Mirror example * chore: [ci] release (alpha) (#78) Co-authored-by: github-actions[bot] * chore: Update example (#79) * feat: Add query param to ListObjects API for display folder. (#80) * feat: Add query params to ListObjects API for folder * Create clean-dogs-joke.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * Feat/group (#82) * chore: Update Example * feat: Add some group API * Create sweet-feet-hide.md * chore: [ci] release (alpha) (#81) Co-authored-by: github-actions[bot] * Update objectt.ts (#83) * Update objectt.ts add missing resp params * Create cold-suns-accept.md --------- Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * Feat/policy (#86) * feat: Policy API * fix: Object API name * Create pretty-bulldogs-notice.md * feat: Migrate out zkbas-js-sdk and upate the sign msg format (#85) * feat: Migrate out zkbas-js-sdk and upate the sign msg format * Create fluffy-pumas-yawn.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#84) Co-authored-by: github-actions[bot] * fix: Head Group (#96) * fix: Head Group * Create cyan-ties-cross.md * feat: Multi Send Txs (#97) * feat: Multi Send Txs * Create lovely-knives-smile.md * chore: [ci] release (alpha) (#98) Co-authored-by: github-actions[bot] * feat: Allow to have sp auth fail (#101) * feat: Allow to have sp auth fail * fix: Replace endpoint with address when auth failed * Create tidy-dodos-care.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: TS lib (#104) * feat: Sync cosmos types (#106) * chore: [ci] release (alpha) (#102) Co-authored-by: github-actions[bot] * chore: Use rainbowkit simply wallet of example (#107) * Feat/update group (#108) * feat: Update group extra api * chore: Test group api * Create quiet-planets-shop.md * chore: [ci] release (alpha) (#109) Co-authored-by: github-actions[bot] * Feat/sp listgroup (#110) * feat: Add sp.listGroup API * chore: Update Example * chore: [ci] release (alpha) (#111) Co-authored-by: github-actions[bot] * chore: Update Example (#112) * chore: Update Examples (#114) * chore(dep): Add cross-env * chore(dep): Npm script * chore: Pnpm engines * chore: Codesandbox Example (#115) * chore(docs): Update stackblitz url (#116) * refactor: CreateFoler API params (#119) * chore: [ci] release (alpha) (#120) Co-authored-by: github-actions[bot] * feat: TimeStamp format * chore: [ci] release (alpha) (#121) Co-authored-by: github-actions[bot] * Refactor/policy (#122) * refactor: Bucket policy * refactor: Bucket policy * chore: [ci] release (alpha) (#123) Co-authored-by: github-actions[bot] * chore(example): BucketPolicy (#127) * Chore/update policy example (#128) * chore(example): BucketPolicy * chore(example): BucketPolicy * Feat/gashub (#129) * feat: Add gas hub API * refactor: TypeUrl as constants * chore: [ci] release (alpha) (#130) Co-authored-by: github-actions[bot] * feat: Export queryClient (#131) * feat: Export queryClient * Create calm-birds-sleep.md * Update README.md * fix: Compatiable net error (#133) * fix: Compatiable net error * Create old-roses-play.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#132) Co-authored-by: github-actions[bot] * Test/test case (#134) * feat: Isomorphic fetch * test: Uint test and e2e test * feat: Sync Types (#136) * feat: Sync Types * Create weak-hounds-sell.md * chore: [ci] release (alpha) (#135) Co-authored-by: github-actions[bot] * Fix/sort types (#137) * feat: Sort EIP712 message field * feat: Extra field * Create happy-humans-thank.md * chore: [ci] release (alpha) (#138) Co-authored-by: github-actions[bot] * feat: Bump version (#139) * chore: [ci] release (alpha) (#140) Co-authored-by: github-actions[bot] * feat: Split approval and simulate (#141) * chore: [ci] release (alpha) (#142) Co-authored-by: github-actions[bot] * Revert "feat: Split approval and simulate" (#143) * Revert "feat: Split approval and simulate" This reverts commit 76defa7f31b7f2d4683f8ed04e8dc2786dd1ef6c. * Create dirty-parrots-hear.md * chore: [ci] release (alpha) (#144) Co-authored-by: github-actions[bot] * Chore/test case (#145) * chore(test): Remove private key from config * chore: Test CI * chore: Update rainkit version (#146) * feat: Query Lock Fee API (#147) * feat: Query Lock Fee API * Create green-avocados-jump.md * chore: [ci] release (alpha) (#148) Co-authored-by: github-actions[bot] * Update bucket.ts (#149) * Update bucket.ts throw error * Create hot-countries-begin.md --------- Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#150) Co-authored-by: github-actions[bot] * fix: Return real statuscode when call metaservice (#164) * fix: Return real statuscode when call metaservice * Create kind-eyes-brake.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#166) Co-authored-by: github-actions[bot] * feat: Upgrade types version (#167) * chore: [ci] release (alpha) (#168) Co-authored-by: github-actions[bot] * Feat/sp exit (#169) * feat: Add Virtual Group API * feat: Compatibility createBukcet and createObject API * feat: Add Migrate bucket API * Feat/batch upload (#171) * feat: Feegrant * feat: Feegrant * Create angry-horses-enjoy.md * Feat/batch upload (#174) * feat: Feegrant * feat: Feegrant * feat: Feegrant grantAllowance API * feat: CreateObject example * feat: CreateBucket example * feat: Transfer example * feat: AuthInfoBytes add feePayer and feeGranter * feat: Add Two Apis (#179) * feat: Add Two Apis * Create mean-wombats-remain.md * chore: [ci] release (alpha) (#170) Co-authored-by: github-actions[bot] * feat AuthV1 (#182) * chore: Update example * chore: Update Example * feat: V1Auth * feat: V1Auth generate * feat: V1Auth createObject Approval * Create shaggy-experts-approve.md * Feat/com new (#186) * feat: Remove GetUserBuckets API Auth * feat: Update BucketProps type * feat: Remove listObjects api auth and update response type * feat: Remove listGroup api auth * feat: Update Types * chore: [ci] release (alpha) (#183) Co-authored-by: github-actions[bot] * feat: Offchainauth timeout reduced to 2s (#187) * feat: Offchainauth timeout reduced to 2s * Create warm-planets-exercise.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#188) Co-authored-by: github-actions[bot] * Feat/auth v1 (#189) * feat: Upgrade types * chore: Update Example Account config * feat: Object create update * Create empty-pens-sing.md * chore: [ci] release (alpha) (#190) Co-authored-by: github-actions[bot] * Feat/auth v1 (#191) * feat: Upgrade types * chore: Update Example Account config * feat: Object create update * feat: API getStorageProviderInfo params update * chore: Upgrade types * feat: GetSpURLfrom bucket * feat: Remove Sp param when create object and bucket * chore: [ci] release (alpha) (#192) Co-authored-by: github-actions[bot] * feat: Change Allwance value type (#193) * chore: Test config update * feat: Change Allowance Type * chore: [ci] release (alpha) (#194) Co-authored-by: github-actions[bot] * feat: MultiTx support private key (#195) * fix: Throw sp error code and message (#197) * fix: Throw sp error code and message * Create good-bobcats-hang.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#196) Co-authored-by: github-actions[bot] * fix: No sp available error (#200) * fix: No sp available error * Create khaki-ducks-brake.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * Opt (#198) * chore: Update Example * refactor: Replace @ethereumjs/util with @ethersproject/bytes * feat: Upload Object V1 auth * fix: CreateObjectTx content-type msg * chore: [ci] release (alpha) (#201) Co-authored-by: github-actions[bot] * feat: Extend response time to 3s (#202) * feat: Extend response time to 3s * Create orange-spiders-add.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#203) Co-authored-by: github-actions[bot] * Chore/conflict (#209) * release 0.2.2 (#204) * feat: Update Types * chore: [ci] release (alpha) * Feat/query status (#42) * fix: Return types * feat: Feegrant * fix: Pubkey typeUrl * feat: Upgrade Cosmos SDK * refactor: Simply broadcast and simulate tx * feat: Support Tx by PrivateKey * feat: Replace local message with types npm * test: Test Framework * chore: Upgrade Example wagmi version, Replace ethersjs with viem * feat: Support EIP712 callback use external provider * feat: Support dynamic provider * test: Support jest test * test: Account Query * feat: Change Tx API * feat: Type Complete * chore: Complate Example in New API * fix: Rollup bundle Support Nodejs * feat: Resolute Module (IoC) * docs: Update README * chore: [ci] release (alpha) (#43) Co-authored-by: github-actions[bot] * Feat/multi msg (#49) * feat: Multi Send * feat: Compatibility multi tx message * Revert "Feat/multi msg (#49)" (#53) This reverts commit fc3dd986646624c81f3cc43bdffb02e70d170b4f. * Update WASM (#55) * feat: Update WASM * feat: Update create object tx * docs: Update doc (#56) * feat: Pre alpha (#57) * chore: [ci] release (alpha) (#58) Co-authored-by: github-actions[bot] * Update README.md * multi msg (#59) * feat: Multi Send * feat: Compatibility multi tx message * chore: [ci] release (alpha) (#62) Co-authored-by: github-actions[bot] * fix: CreateObject Msg (#65) * feat: add offchainauth method (#64) * feat: Add offchainauth method * feat: Compatiable old get approval method * Create ten-suits-crash.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> Co-authored-by: rrr523 * chore: [ci] release (alpha) (#66) Co-authored-by: github-actions[bot] * feat: Add Sp params (#67) * feat: Optimize get approval types and add throw error (#70) * feat: Optimize get approval types and add throw error * Create fluffy-snails-explain.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#68) Co-authored-by: github-actions[bot] * chore: Update example without greenfield-cosmos-types (#71) * fix: Return Promise (#72) * fix: Return Promise * Create strong-snails-count.md * chore: [ci] release (alpha) (#73) Co-authored-by: github-actions[bot] * fix: Create Group message (#74) * fix: Create Group message * Create young-bears-scream.md * feat: Add Storage Params API (#76) * chore: [ci] release (alpha) (#75) Co-authored-by: github-actions[bot] * Chore/group example (#77) * feat: Group API * chore: Group and Mirror example * chore: [ci] release (alpha) (#78) Co-authored-by: github-actions[bot] * chore: Update example (#79) * feat: Add query param to ListObjects API for display folder. (#80) * feat: Add query params to ListObjects API for folder * Create clean-dogs-joke.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * Feat/group (#82) * chore: Update Example * feat: Add some group API * Create sweet-feet-hide.md * chore: [ci] release (alpha) (#81) Co-authored-by: github-actions[bot] * Update objectt.ts (#83) * Update objectt.ts add missing resp params * Create cold-suns-accept.md --------- Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * Feat/policy (#86) * feat: Policy API * fix: Object API name * Create pretty-bulldogs-notice.md * feat: Migrate out zkbas-js-sdk and upate the sign msg format (#85) * feat: Migrate out zkbas-js-sdk and upate the sign msg format * Create fluffy-pumas-yawn.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#84) Co-authored-by: github-actions[bot] * fix: Head Group (#96) * fix: Head Group * Create cyan-ties-cross.md * feat: Multi Send Txs (#97) * feat: Multi Send Txs * Create lovely-knives-smile.md * chore: [ci] release (alpha) (#98) Co-authored-by: github-actions[bot] * feat: Allow to have sp auth fail (#101) * feat: Allow to have sp auth fail * fix: Replace endpoint with address when auth failed * Create tidy-dodos-care.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: TS lib (#104) * feat: Sync cosmos types (#106) * chore: [ci] release (alpha) (#102) Co-authored-by: github-actions[bot] * chore: Use rainbowkit simply wallet of example (#107) * Feat/update group (#108) * feat: Update group extra api * chore: Test group api * Create quiet-planets-shop.md * chore: [ci] release (alpha) (#109) Co-authored-by: github-actions[bot] * Feat/sp listgroup (#110) * feat: Add sp.listGroup API * chore: Update Example * chore: [ci] release (alpha) (#111) Co-authored-by: github-actions[bot] * chore: Update Example (#112) * chore: Update Examples (#114) * chore(dep): Add cross-env * chore(dep): Npm script * chore: Pnpm engines * chore: Codesandbox Example (#115) * chore(docs): Update stackblitz url (#116) * refactor: CreateFoler API params (#119) * chore: [ci] release (alpha) (#120) Co-authored-by: github-actions[bot] * feat: TimeStamp format * chore: [ci] release (alpha) (#121) Co-authored-by: github-actions[bot] * Refactor/policy (#122) * refactor: Bucket policy * refactor: Bucket policy * chore: [ci] release (alpha) (#123) Co-authored-by: github-actions[bot] * chore(example): BucketPolicy (#127) * Chore/update policy example (#128) * chore(example): BucketPolicy * chore(example): BucketPolicy * Feat/gashub (#129) * feat: Add gas hub API * refactor: TypeUrl as constants * chore: [ci] release (alpha) (#130) Co-authored-by: github-actions[bot] * feat: Export queryClient (#131) * feat: Export queryClient * Create calm-birds-sleep.md * Update README.md * fix: Compatiable net error (#133) * fix: Compatiable net error * Create old-roses-play.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#132) Co-authored-by: github-actions[bot] * Test/test case (#134) * feat: Isomorphic fetch * test: Uint test and e2e test * feat: Sync Types (#136) * feat: Sync Types * Create weak-hounds-sell.md * chore: [ci] release (alpha) (#135) Co-authored-by: github-actions[bot] * Fix/sort types (#137) * feat: Sort EIP712 message field * feat: Extra field * Create happy-humans-thank.md * chore: [ci] release (alpha) (#138) Co-authored-by: github-actions[bot] * feat: Bump version (#139) * chore: [ci] release (alpha) (#140) Co-authored-by: github-actions[bot] * feat: Split approval and simulate (#141) * chore: [ci] release (alpha) (#142) Co-authored-by: github-actions[bot] * Revert "feat: Split approval and simulate" (#143) * Revert "feat: Split approval and simulate" This reverts commit 76defa7f31b7f2d4683f8ed04e8dc2786dd1ef6c. * Create dirty-parrots-hear.md * chore: [ci] release (alpha) (#144) Co-authored-by: github-actions[bot] * Chore/test case (#145) * chore(test): Remove private key from config * chore: Test CI * chore: Update rainkit version (#146) * feat: Query Lock Fee API (#147) * feat: Query Lock Fee API * Create green-avocados-jump.md * chore: [ci] release (alpha) (#148) Co-authored-by: github-actions[bot] * Update bucket.ts (#149) * Update bucket.ts throw error * Create hot-countries-begin.md --------- Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#150) Co-authored-by: github-actions[bot] * fix: Return real statuscode when call metaservice (#164) * fix: Return real statuscode when call metaservice * Create kind-eyes-brake.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#166) Co-authored-by: github-actions[bot] * feat: Upgrade types version (#167) * chore: [ci] release (alpha) (#168) Co-authored-by: github-actions[bot] * Feat/sp exit (#169) * feat: Add Virtual Group API * feat: Compatibility createBukcet and createObject API * feat: Add Migrate bucket API * Feat/batch upload (#171) * feat: Feegrant * feat: Feegrant * Create angry-horses-enjoy.md * Feat/batch upload (#174) * feat: Feegrant * feat: Feegrant * feat: Feegrant grantAllowance API * feat: CreateObject example * feat: CreateBucket example * feat: Transfer example * feat: AuthInfoBytes add feePayer and feeGranter * feat: Add Two Apis (#179) * feat: Add Two Apis * Create mean-wombats-remain.md * chore: [ci] release (alpha) (#170) Co-authored-by: github-actions[bot] * feat AuthV1 (#182) * chore: Update example * chore: Update Example * feat: V1Auth * feat: V1Auth generate * feat: V1Auth createObject Approval * Create shaggy-experts-approve.md * Feat/com new (#186) * feat: Remove GetUserBuckets API Auth * feat: Update BucketProps type * feat: Remove listObjects api auth and update response type * feat: Remove listGroup api auth * feat: Update Types * chore: [ci] release (alpha) (#183) Co-authored-by: github-actions[bot] * feat: Offchainauth timeout reduced to 2s (#187) * feat: Offchainauth timeout reduced to 2s * Create warm-planets-exercise.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#188) Co-authored-by: github-actions[bot] * Feat/auth v1 (#189) * feat: Upgrade types * chore: Update Example Account config * feat: Object create update * Create empty-pens-sing.md * chore: [ci] release (alpha) (#190) Co-authored-by: github-actions[bot] * Feat/auth v1 (#191) * feat: Upgrade types * chore: Update Example Account config * feat: Object create update * feat: API getStorageProviderInfo params update * chore: Upgrade types * feat: GetSpURLfrom bucket * feat: Remove Sp param when create object and bucket * chore: [ci] release (alpha) (#192) Co-authored-by: github-actions[bot] * feat: Change Allwance value type (#193) * chore: Test config update * feat: Change Allowance Type * chore: [ci] release (alpha) (#194) Co-authored-by: github-actions[bot] * feat: MultiTx support private key (#195) * fix: Throw sp error code and message (#197) * fix: Throw sp error code and message * Create good-bobcats-hang.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#196) Co-authored-by: github-actions[bot] * fix: No sp available error (#200) * fix: No sp available error * Create khaki-ducks-brake.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * Opt (#198) * chore: Update Example * refactor: Replace @ethereumjs/util with @ethersproject/bytes * feat: Upload Object V1 auth * fix: CreateObjectTx content-type msg * chore: [ci] release (alpha) (#201) Co-authored-by: github-actions[bot] * feat: Extend response time to 3s (#202) * feat: Extend response time to 3s * Create orange-spiders-add.md --------- Co-authored-by: devinxl Co-authored-by: rrr523 <59029880+rrr523@users.noreply.github.com> * chore: [ci] release (alpha) (#203) Co-authored-by: github-actions[bot] --------- Co-authored-by: github-actions[bot] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: randomx999 <84435529+randomx999@users.noreply.github.com> Co-authored-by: devinxl Co-authored-by: aiden-cao <104969608+aiden-cao@users.noreply.github.com> * chore: Pre exit (#205) * chore: [ci] release (#206) Co-authored-by: github-actions[bot] * chore: Rename package (#207) * chore: [ci] release (#208) Co-authored-by: github-actions[bot] --------- Co-authored-by: github-actions[bot] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: randomx999 <84435529+randomx999@users.noreply.github.com> Co-authored-by: devinxl Co-authored-by: aiden-cao <104969608+aiden-cao@users.noreply.github.com> * Feat/payment (#210) * feat: Add Payment query API * fix: Create Payment Account * fix: Payment disableRefund * chore: Update Example * chore: [ci] release (alpha) (#211) Co-authored-by: github-actions[bot] * chore: Update README link (#214) * fix: Return Types (#213) * chore: Update withdraw example params (#212) * chore: Update withdraw example params * feat: Zk crypto wasm package * chore: Nodejs example update package * chore: Add comment * chore: Update UMD name * feat: Upgrade file handle package * chore: Update OffchainAuth example * feat: New Zk package * chore: Changeset (#215) * chore: [ci] release (alpha) (#216) Co-authored-by: github-actions[bot] * Feat/group api (#217) * fix: Dynamic add and delete members string array * chore: Release zk crypto * chore: [ci] release (alpha) (#218) Co-authored-by: github-actions[bot] * chore: Release zk crypto (#219) * chore: [ci] release (alpha) (#220) Co-authored-by: github-actions[bot] * fix: Zk version bump (#221) * chore: [ci] release (alpha) (#222) Co-authored-by: github-actions[bot] * fix: DeepClone EIP712 (#223) * chore: [ci] release (alpha) (#224) Co-authored-by: github-actions[bot] * fix: Error try catch (#230) * chore: [ci] release (alpha) (#231) Co-authored-by: github-actions[bot] * Feat/auth (#232) * feat: Compatibility new payment api * feat: Compatibility updateGroupMember api * chore: Update example * feat: Feegrant api add timestamp * feat: EDDSA Sign * feat: Remove log (#234) * chore: [ci] release (alpha) (#233) Co-authored-by: github-actions[bot] * fix: Feegrant add expiration time (#235) * fix: Feegrant add expiration time * chore(example): GetAllowence support pagination * refactor (#236) * refactor: Tx * fix: Type * feat: Add Group API * feat: Update Sp API * feat: Bucket add authType * feat: and add params * fix: Types * feat: Change type * chore: [ci] release (alpha) (#237) Co-authored-by: github-actions[bot] * Fix/read quota (#238) * fix: CreateBucketApproval * feat: ReadQuota * chore(example): Update Example * chore: [ci] release (alpha) (#239) Co-authored-by: github-actions[bot] * XML response (#240) * feat: Return XML type * feat: CreateBucket Add payment address * chore: [ci] release (alpha) (#241) Co-authored-by: github-actions[bot] * fix: XMLParser force array not object (#242) * Feat/export req (#243) * fix: XMLParser force array not object * fix: XMLParser force array not object * chore: [ci] release (alpha) (#244) Co-authored-by: github-actions[bot] * feat: Export types (#245) * Export types (#247) * feat: Export types * fix: Hex number convert to string not big number * chore: [ci] release (alpha) (#246) Co-authored-by: github-actions[bot] * Feat/up and down (#248) * feat: Upload Object add AuthType * feat: Download s3 object * feat: Migrate Bucket add authType * feat: SpClient expose makeHeaders method * chore: Update constant pathj * chore: [ci] release (alpha) (#249) Co-authored-by: github-actions[bot] * Feat/xml null (#250) * feat: Add default XML value * feat: Add default XML value * chore: [ci] release (alpha) (#251) Co-authored-by: github-actions[bot] * Fix/gap time (#252) * fix: Gap time * fix: Gap time * Feat/meta (#254) * feat: GetBucketMeta * feat: Object Meta * Create curly-fishes-bow.md * chore: [ci] release (alpha) (#253) Co-authored-by: github-actions[bot] * Refactor/sp client (#255) * chore: Exact parseError * feat: Remove deps * chore: Structor * feat: Refeactor Sp Client * chore: [ci] release (alpha) (#256) Co-authored-by: github-actions[bot] * feat: Custom HTTP method (#257) * feat: Custom HTTP method * chore(example): Update * chore: [ci] release (alpha) (#258) Co-authored-by: github-actions[bot] * chore(docs): Update README (#259) * chore: Add comment (#260) * fix: Sp types (#261) * Feat/upload progress (#263) * fix: GfSpGetUserBucketsResponse Bucket types * feat: Cross fetch * chore: [ci] release (alpha) (#262) Co-authored-by: github-actions[bot] * fix: EncodePath (#264) * chore: [ci] release (alpha) (#265) Co-authored-by: github-actions[bot] * fix: Download http method (#266) * fix: Download http method (#267) * chore: [ci] release (alpha) (#268) Co-authored-by: github-actions[bot] * feat: New API getObjectPreviewUrl (#270) * chore: [ci] release (alpha) (#271) Co-authored-by: github-actions[bot] * fix: XML type align to go struct (#272) * feat: Optional endpoint param (#273) * chore: [ci] release (alpha) (#274) Co-authored-by: github-actions[bot] * fix: XML parse boolean (#275) * chore: [ci] release (alpha) (#276) Co-authored-by: github-actions[bot] * fix: Common Prefix parse as array (#277) * chore: [ci] release (alpha) (#278) Co-authored-by: github-actions[bot] * test: Parse XML test case (#279) * feat: Migrate bucket (#280) * fix: Create bucket quota params (#282) * chore: [ci] release (alpha) (#281) Co-authored-by: github-actions[bot] * feat: Replace xml2js to fast-xml-parse (#283) * Feat/update bucket info (#284) * feat: Update Bucket Info API * chore(example): Update * chore: [ci] release (alpha) (#285) Co-authored-by: github-actions[bot] * fix: EncodePath function (#286) * fix: Convert enum to number (#287) * chore: [ci] release (alpha) (#288) Co-authored-by: github-actions[bot] * feat: Add listReadRecords api * feat: ListGroups API * feat: ListObjectsByIds API * feat: ListBucketsByIds api * feat: Add verifyPermission api * feat: ListGroupsMembers * feat: ListUserGroups api * feat: Add listUserOwnedGroups api * chore: [ci] release (alpha) * feat: Sort query (#292) * feat: Sort query * Create healthy-chicken-beam.md * chore: [ci] release (alpha) (#293) Co-authored-by: github-actions[bot] * chore(exmaple): Group (#294) * docs: Update API (#295) * chore: Pre Exit (#296) * Pre exit (#297) * chore: Pre Exit * chore: Pre Exit * chore: [ci] release (#298) Co-authored-by: github-actions[bot] * chore: Pre Enter (#299) --------- Co-authored-by: github-actions[bot] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: randomx999 <84435529+randomx999@users.noreply.github.com> Co-authored-by: devinxl Co-authored-by: aiden-cao <104969608+aiden-cao@users.noreply.github.com> --- .changeset/README.md | 8 - .changeset/pre.json | 12 + README.md | 1 + examples/nextjs/CHANGELOG.md | 346 ++++++ examples/nextjs/README.md | 10 +- examples/nextjs/package.json | 4 +- examples/nextjs/src/client/index.ts | 20 +- .../src/components/bucket/create/index.tsx | 84 +- .../nextjs/src/components/bucket/index.tsx | 10 + .../src/components/bucket/info/index.tsx | 50 +- .../src/components/bucket/migrate/index.tsx | 30 +- .../src/components/bucket/quota/index.tsx | 47 + .../src/components/bucket/update/index.tsx | 58 + .../nextjs/src/components/deposit/index.tsx | 21 +- .../src/components/feegrant/createObj.tsx | 41 +- .../nextjs/src/components/feegrant/delObj.tsx | 4 + .../nextjs/src/components/feegrant/index.tsx | 8 + .../src/components/group/create/index.tsx | 1 - .../nextjs/src/components/group/index.tsx | 3 + .../src/components/group/info/index.tsx | 15 +- .../src/components/group/list/index.tsx | 4 +- .../src/components/group/update/index.tsx | 122 ++ .../nextjs/src/components/mirror/index.tsx | 2 + .../nextjs/src/components/multimsg/index.tsx | 2 - .../src/components/object/create/index.tsx | 96 +- .../src/components/object/info/index.tsx | 91 +- .../src/components/offchainauth/index.tsx | 43 - .../nextjs/src/components/payment/index.tsx | 174 +++ .../nextjs/src/components/query/index.tsx | 98 ++ .../nextjs/src/components/withdraw/index.tsx | 4 +- examples/nextjs/src/pages/_app.tsx | 3 +- examples/nextjs/src/pages/_document.tsx | 6 + examples/nextjs/src/pages/tx.tsx | 6 +- examples/nextjs/src/utils/offchainAuth.ts | 41 + examples/nodejs/CHANGELOG.md | 330 ++++++ examples/nodejs/index.js | 57 +- examples/nodejs/package.json | 2 +- packages/chain-sdk/CHANGELOG.md | 858 ++++++++++++++ packages/chain-sdk/README.md | 101 +- packages/chain-sdk/package.json | 9 +- packages/chain-sdk/src/api/account.ts | 20 +- packages/chain-sdk/src/api/basic.ts | 232 ++-- packages/chain-sdk/src/api/bucket.ts | 558 +++++---- packages/chain-sdk/src/api/challenge.ts | 2 +- packages/chain-sdk/src/api/crosschain.ts | 34 +- packages/chain-sdk/src/api/distribution.ts | 2 +- packages/chain-sdk/src/api/feegrant.ts | 8 +- packages/chain-sdk/src/api/gashub.ts | 2 +- packages/chain-sdk/src/api/group.ts | 30 +- packages/chain-sdk/src/api/objectt.ts | 458 ++++---- packages/chain-sdk/src/api/offchainauth.ts | 58 +- packages/chain-sdk/src/api/payment.ts | 109 +- packages/chain-sdk/src/api/sp.ts | 445 +++++-- packages/chain-sdk/src/api/storage.ts | 34 +- packages/chain-sdk/src/api/virtualGroup.ts | 2 +- packages/chain-sdk/src/client.ts | 24 +- .../src/{api => clients}/queryclient.ts | 0 .../chain-sdk/src/clients/spclient/auth.ts | 212 ++++ .../spclient}/sign.ts | 19 +- .../clients/spclient/spApis/bucketApproval.ts | 36 + .../clients/spclient/spApis/getBucketMeta.ts | 21 + .../src/clients/spclient/spApis/getNonce.ts | 39 + .../src/clients/spclient/spApis/getObject.ts | 39 + .../clients/spclient/spApis/getObjectMeta.ts | 28 + .../clients/spclient/spApis/getUserBuckets.ts | 36 + .../spclient/spApis/listBucketReadRecords.ts | 69 ++ .../spclient/spApis/listBucketsByIds.ts | 45 + .../src/clients/spclient/spApis/listGroups.ts | 39 + .../spclient/spApis/listGroupsMembers.ts | 33 + .../spclient/spApis/listObjectsByBucket.ts | 46 + .../spclient/spApis/listObjectsByIds.ts | 46 + .../clients/spclient/spApis/listUserGroups.ts | 38 + .../spclient/spApis/listUserOwnedGroups.ts | 38 + .../src/clients/spclient/spApis/metaInfos.ts | 13 + .../spclient/spApis/migrateApproval.ts | 36 + .../clients/spclient/spApis/objectApproval.ts | 36 + .../src/clients/spclient/spApis/parseError.ts | 14 + .../src/clients/spclient/spApis/putObject.ts | 45 + .../spclient/spApis/queryBucketReadQuota.ts | 56 + .../spclient/spApis/updateUserAccountKey.ts} | 40 +- .../spclient/spApis/verifyPermission.ts | 17 + .../src/clients/spclient/spClient.ts | 94 ++ packages/chain-sdk/src/constants/http.ts | 7 + packages/chain-sdk/src/constants/index.ts | 1 + packages/chain-sdk/src/index.ts | 3 +- .../payment/MsgCreatePaymentAccount.ts | 4 +- .../greenfield/storage/MsgCreateGroup.ts | 10 +- .../greenfield/storage/MsgMigrateBucket.ts | 4 + .../greenfield/storage/MsgMirrorGroup.ts | 12 +- .../greenfield/storage/MsgUpdateBucketInfo.ts | 10 + .../storage/MsgUpdateGroupMember.ts | 77 +- packages/chain-sdk/src/messages/utils.ts | 2 +- packages/chain-sdk/src/offchainauth/index.ts | 3 +- packages/chain-sdk/src/offchainauth/utils.ts | 11 +- packages/chain-sdk/src/tests/parsexml.spec.ts | 465 ++++++++ packages/chain-sdk/src/tests/utils.spec.ts | 26 + packages/chain-sdk/src/types/auth.ts | 16 + packages/chain-sdk/src/types/common.ts | 4 +- packages/chain-sdk/src/types/index.ts | 2 + packages/chain-sdk/src/types/sp-xml/Common.ts | 131 +++ .../src/types/sp-xml/GetBucketMetaResponse.ts | 15 + .../src/types/sp-xml/GetObjectMetaResponse.ts | 15 + .../types/sp-xml/GetUserBucketsResponse.ts | 9 + .../sp-xml/ListBucketReadRecordResponse.ts | 10 + .../types/sp-xml/ListBucketsByIDsResponse.ts | 24 + .../types/sp-xml/ListGroupsMembersResponse.ts | 21 + .../src/types/sp-xml/ListGroupsResponse.ts | 20 + .../sp-xml/ListObjectsByBucketNameResponse.ts | 18 + .../types/sp-xml/ListObjectsByIDsResponse.ts | 25 + .../types/sp-xml/ListUserGroupsResponse.ts | 21 + .../sp-xml/ListUserOwnedGroupsResponse.ts | 21 + .../src/types/sp-xml/ReadQuotaResponse.ts | 10 + .../src/types/sp-xml/RequestErrorResponse.ts | 6 + .../src/types/sp-xml/RequestNonceResponse.ts | 8 + .../types/sp-xml/VerifyPermissionResponse.ts | 7 + packages/chain-sdk/src/types/sp-xml/index.ts | 14 + packages/chain-sdk/src/types/storage.ts | 197 ++-- packages/chain-sdk/src/utils/allowance.ts | 15 +- packages/chain-sdk/src/utils/auth.ts | 165 --- packages/chain-sdk/src/utils/encoding.ts | 2 +- packages/chain-sdk/src/utils/helpers.ts | 1 + packages/chain-sdk/src/utils/http.ts | 34 +- packages/chain-sdk/src/utils/index.ts | 1 + packages/file-handle/CHANGELOG.md | 16 + packages/file-handle/config/tsconfig-cjs.json | 10 - packages/file-handle/config/tsconfig-esm.json | 7 - .../file-handle/config/tsconfig-test.json | 10 - packages/file-handle/config/tsconfig.json | 31 - packages/file-handle/package.json | 61 +- packages/file-handle/rollup.config.js | 120 -- packages/file-handle/src/browser/index.js | 28 + packages/file-handle/src/browser/init.js | 47 + packages/file-handle/src/browser/wasm_exec.js | 608 ++++++++++ packages/file-handle/src/constants.js | 3 + .../src/files-handle-wasm/main.wasm | Bin 3079990 -> 0 bytes .../file-handle/src/files-handle-wasm/node.js | 16 - .../src/files-handle-wasm/wasm_exec.js | 626 ---------- .../src/files-handle-wasm/wasm_exec_node.js | 47 - .../src/files-handle-wasm/web.d.ts | 13 - .../file-handle/src/files-handle-wasm/web.js | 31 - packages/file-handle/src/go-wasm/main.go | 3 +- packages/file-handle/src/index.ts | 25 - packages/file-handle/src/node/index.js | 33 + packages/file-handle/src/node/init.js | 32 + packages/file-handle/src/node/wasm_exec.js | 608 ++++++++++ .../file-handle/src/wasm/file-handle.wasm | Bin 0 -> 474223 bytes packages/file-handle/tsconfig.json | 3 - packages/file-handle/types/expose.d.ts | 10 + packages/file-handle/types/index.d.ts | 3 + packages/file-handle/webpack.config.js | 141 +++ packages/zk-crypto/CHANGELOG.md | 69 ++ packages/zk-crypto/README.md | 50 + packages/zk-crypto/package.json | 43 + packages/zk-crypto/src/browser/index.js | 29 + packages/zk-crypto/src/browser/init.js | 47 + packages/zk-crypto/src/browser/wasm_exec.js | 608 ++++++++++ packages/zk-crypto/src/node/index.js | 33 + packages/zk-crypto/src/node/init.js | 32 + packages/zk-crypto/src/node/wasm_exec.js | 608 ++++++++++ packages/zk-crypto/src/wasm/zk-crypto.wasm | Bin 0 -> 471254 bytes packages/zk-crypto/types/expose.d.ts | 3 + packages/zk-crypto/types/index.d.ts | 3 + packages/zk-crypto/webpack.config.js | 141 +++ pnpm-lock.yaml | 1027 +++++++++++++---- 164 files changed, 9937 insertions(+), 2710 deletions(-) delete mode 100644 .changeset/README.md create mode 100644 .changeset/pre.json create mode 100644 examples/nextjs/src/components/bucket/quota/index.tsx create mode 100644 examples/nextjs/src/components/bucket/update/index.tsx create mode 100644 examples/nextjs/src/components/group/update/index.tsx delete mode 100644 examples/nextjs/src/components/offchainauth/index.tsx create mode 100644 examples/nextjs/src/components/payment/index.tsx create mode 100644 examples/nextjs/src/utils/offchainAuth.ts rename packages/chain-sdk/src/{api => clients}/queryclient.ts (100%) create mode 100644 packages/chain-sdk/src/clients/spclient/auth.ts rename packages/chain-sdk/src/{offchainauth => clients/spclient}/sign.ts (68%) create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/bucketApproval.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/getBucketMeta.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/getNonce.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/getObject.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/getObjectMeta.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/getUserBuckets.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/listBucketReadRecords.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/listBucketsByIds.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/listGroups.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/listGroupsMembers.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/listObjectsByBucket.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/listObjectsByIds.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/listUserGroups.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/listUserOwnedGroups.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/metaInfos.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/migrateApproval.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/objectApproval.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/parseError.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/putObject.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/queryBucketReadQuota.ts rename packages/chain-sdk/src/{offchainauth/fetch.ts => clients/spclient/spApis/updateUserAccountKey.ts} (52%) create mode 100644 packages/chain-sdk/src/clients/spclient/spApis/verifyPermission.ts create mode 100644 packages/chain-sdk/src/clients/spclient/spClient.ts create mode 100644 packages/chain-sdk/src/constants/http.ts create mode 100644 packages/chain-sdk/src/tests/parsexml.spec.ts create mode 100644 packages/chain-sdk/src/tests/utils.spec.ts create mode 100644 packages/chain-sdk/src/types/auth.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/Common.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/GetBucketMetaResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/GetObjectMetaResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/GetUserBucketsResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/ListBucketReadRecordResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/ListBucketsByIDsResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/ListGroupsMembersResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/ListGroupsResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/ListObjectsByBucketNameResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/ListObjectsByIDsResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/ListUserGroupsResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/ListUserOwnedGroupsResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/ReadQuotaResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/RequestErrorResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/RequestNonceResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/VerifyPermissionResponse.ts create mode 100644 packages/chain-sdk/src/types/sp-xml/index.ts delete mode 100644 packages/chain-sdk/src/utils/auth.ts create mode 100644 packages/chain-sdk/src/utils/helpers.ts delete mode 100644 packages/file-handle/config/tsconfig-cjs.json delete mode 100644 packages/file-handle/config/tsconfig-esm.json delete mode 100644 packages/file-handle/config/tsconfig-test.json delete mode 100644 packages/file-handle/config/tsconfig.json delete mode 100644 packages/file-handle/rollup.config.js create mode 100644 packages/file-handle/src/browser/index.js create mode 100644 packages/file-handle/src/browser/init.js create mode 100644 packages/file-handle/src/browser/wasm_exec.js create mode 100644 packages/file-handle/src/constants.js delete mode 100755 packages/file-handle/src/files-handle-wasm/main.wasm delete mode 100644 packages/file-handle/src/files-handle-wasm/node.js delete mode 100644 packages/file-handle/src/files-handle-wasm/wasm_exec.js delete mode 100644 packages/file-handle/src/files-handle-wasm/wasm_exec_node.js delete mode 100644 packages/file-handle/src/files-handle-wasm/web.d.ts delete mode 100644 packages/file-handle/src/files-handle-wasm/web.js delete mode 100644 packages/file-handle/src/index.ts create mode 100644 packages/file-handle/src/node/index.js create mode 100644 packages/file-handle/src/node/init.js create mode 100644 packages/file-handle/src/node/wasm_exec.js create mode 100755 packages/file-handle/src/wasm/file-handle.wasm delete mode 100644 packages/file-handle/tsconfig.json create mode 100644 packages/file-handle/types/expose.d.ts create mode 100644 packages/file-handle/types/index.d.ts create mode 100644 packages/file-handle/webpack.config.js create mode 100644 packages/zk-crypto/CHANGELOG.md create mode 100644 packages/zk-crypto/README.md create mode 100644 packages/zk-crypto/package.json create mode 100644 packages/zk-crypto/src/browser/index.js create mode 100644 packages/zk-crypto/src/browser/init.js create mode 100644 packages/zk-crypto/src/browser/wasm_exec.js create mode 100644 packages/zk-crypto/src/node/index.js create mode 100644 packages/zk-crypto/src/node/init.js create mode 100644 packages/zk-crypto/src/node/wasm_exec.js create mode 100755 packages/zk-crypto/src/wasm/zk-crypto.wasm create mode 100644 packages/zk-crypto/types/expose.d.ts create mode 100644 packages/zk-crypto/types/index.d.ts create mode 100644 packages/zk-crypto/webpack.config.js diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index e5b6d8d6..00000000 --- a/.changeset/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Changesets - -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can -find the full documentation for it [in our repository](https://github.com/changesets/changesets) - -We have a quick list of common questions to get you started engaging with this project in -[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000..4b87c69f --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,12 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "@demo/wallet": "0.0.5", + "@demo/nodejs": "0.0.8", + "@bnb-chain/greenfield-js-sdk": "0.2.4", + "@bnb-chain/greenfiled-file-handle": "0.2.1", + "@bnb-chain/greenfield-zk-crypto": "0.0.2" + }, + "changesets": [] +} diff --git a/README.md b/README.md index 96b29ad3..e2a7a1d9 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ You can find some package documentation below: | --- | --- | | [@bnb-chain/greenfield-js-sdk](./packages/chain-sdk/README.md) | A client library for Greenfield Chain | | [@bnb-chain/greenfiled-file-handle](./packages/file-handle/README.md) | WASM module that handle file, such as `checksums` | +| [@bnb-chain/greenfield-zk-crypto](./packages/zk-crypto/README.md) | WASM module about sign crypto | ## Document [Document](./packages/chain-sdk/README.md) diff --git a/examples/nextjs/CHANGELOG.md b/examples/nextjs/CHANGELOG.md index 103a4c7a..979ea941 100644 --- a/examples/nextjs/CHANGELOG.md +++ b/examples/nextjs/CHANGELOG.md @@ -1,5 +1,351 @@ # @demo/wallet +## 0.0.5 + +### Patch Changes + +- [#254](https://github.com/bnb-chain/greenfield-js-sdk/pull/254) + [`1ede2ea`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1ede2ea0ecc9ffc35d262a43a007221f06df3a0e) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add GetBucketMeta and GetObjectMeta + +- [#292](https://github.com/bnb-chain/greenfield-js-sdk/pull/292) + [`7869b75`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7869b75fa16ef8f71daad0a00b0d10ba7fb13fb3) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Sort query + +- Updated dependencies + [[`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`a485367`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a485367f23175c4004a7f030a1a4081c65d0d632), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`2e44e95`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e44e9548712739c33cc98d82bfada1a06ef9472), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`1ede2ea`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1ede2ea0ecc9ffc35d262a43a007221f06df3a0e), + [`8f4e206`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8f4e20671b30befd4b6ede4807b1b1bacb190626), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`53fa253`](https://github.com/bnb-chain/greenfield-js-sdk/commit/53fa253423a47ef190457eec980c895a1d035ab0), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`faf5d47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/faf5d474d626010ef9a4d83bf96491b66d265d35), + [`a3e6b47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e6b472329e32fe86f7dd8a770d39a371a034cc), + [`402da22`](https://github.com/bnb-chain/greenfield-js-sdk/commit/402da22ea101f855a05f098b8970eef13b045ead), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`fcf6283`](https://github.com/bnb-chain/greenfield-js-sdk/commit/fcf62832288fa3a67b552d69e321d73690cbc87e), + [`dba9871`](https://github.com/bnb-chain/greenfield-js-sdk/commit/dba987145172ddff7c7e363c144861a3f33ec999), + [`1badb9c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1badb9c9dd71a7a39025e90adfe9a6feace75936), + [`db61ff6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/db61ff671f7ccc1be9ed6b1a229596916e1ae5ab), + [`7d69f2e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7d69f2e3d044fc5f4c50d5251f99d8e04fb39b86), + [`352419e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/352419e2016e6643fe7223adfe2cc1c3a6ee7ba6), + [`55f8851`](https://github.com/bnb-chain/greenfield-js-sdk/commit/55f8851a659a178c1ab37473f0c76f9a374a0368), + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db), + [`0084053`](https://github.com/bnb-chain/greenfield-js-sdk/commit/00840538a95e830752dd0ea07f52990303dccf95), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`c28ab8b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c28ab8b283f9aca37633c761527706f780bd13f5), + [`7869b75`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7869b75fa16ef8f71daad0a00b0d10ba7fb13fb3), + [`9710b5b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/9710b5bd294484c8a8021193f649b006481e9cd5), + [`ce00ce1`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ce00ce1779c2ef3c627fb7172600093e6b580d23), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`ea31d73`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ea31d738124058d75b251d6a897f924a122b1471), + [`8d08848`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8d08848490d98fb774a8542d226647e8e5d65652), + [`ff112b2`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ff112b2403b3e9826acb120cf42203996bf97872), + [`21c9f6e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/21c9f6e2daa434a3d1385a845a0879750559ffbc), + [`7824130`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7824130027ca00254b94b0a8af61330551c639ef), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`c92e07e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c92e07e725accece3e1b126ea2ef0d4c6e5db431), + [`2e86b92`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e86b92a0762d0701356d24b7ad774364c039e3c), + [`12bd37c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/12bd37ccdefe789660409882c7045e2cf2a60ff6), + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db), + [`8782a21`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8782a21a9dcae1f9b83062877ff92540534fa40c), + [`ad1d7d3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ad1d7d3fa54fae50aed1b4e1d45a152e2e3fb08c), + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d), + [`25ccbb6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/25ccbb6612b86d8037cec4b9ca3a9b141e10cc66), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`569445b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/569445b218a156205172a15ce7aeb2d598b83f76), + [`e586e77`](https://github.com/bnb-chain/greenfield-js-sdk/commit/e586e7738e42581a9103e6157caad558896a8c63), + [`b3683b8`](https://github.com/bnb-chain/greenfield-js-sdk/commit/b3683b8ce7e56a96c970964b2ade500eb95ef298), + [`c7de1fc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c7de1fc86211e879dcdf4c420e3f7363fa1e36a5), + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`8ba4cc7`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8ba4cc735cbd72b07d8d0a9fadae2c4bda1dea53), + [`48521b3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/48521b39f1b1ab5bb806fbe525ca1cd4437b3cbc), + [`cbbb4aa`](https://github.com/bnb-chain/greenfield-js-sdk/commit/cbbb4aa2d0f1e76b9f367b95355590370d35d684), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`d388940`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d388940e821e7fc3cb9671ffae4753d63a3189b9)]: + - @bnb-chain/greenfield-js-sdk@0.2.4 + - @bnb-chain/greenfiled-file-handle@0.2.1 + +## 0.0.5-alpha.28 + +### Patch Changes + +- [#292](https://github.com/bnb-chain/greenfield-js-sdk/pull/292) + [`7869b75`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7869b75fa16ef8f71daad0a00b0d10ba7fb13fb3) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Sort query + +- Updated dependencies + [[`7869b75`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7869b75fa16ef8f71daad0a00b0d10ba7fb13fb3)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.28 + +## 0.0.5-alpha.27 + +### Patch Changes + +- Updated dependencies + [[`faf5d47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/faf5d474d626010ef9a4d83bf96491b66d265d35), + [`fcf6283`](https://github.com/bnb-chain/greenfield-js-sdk/commit/fcf62832288fa3a67b552d69e321d73690cbc87e), + [`7d69f2e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7d69f2e3d044fc5f4c50d5251f99d8e04fb39b86), + [`0084053`](https://github.com/bnb-chain/greenfield-js-sdk/commit/00840538a95e830752dd0ea07f52990303dccf95), + [`9710b5b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/9710b5bd294484c8a8021193f649b006481e9cd5), + [`ea31d73`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ea31d738124058d75b251d6a897f924a122b1471), + [`12bd37c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/12bd37ccdefe789660409882c7045e2cf2a60ff6), + [`ad1d7d3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ad1d7d3fa54fae50aed1b4e1d45a152e2e3fb08c)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.27 + +## 0.0.5-alpha.26 + +### Patch Changes + +- Updated dependencies + [[`402da22`](https://github.com/bnb-chain/greenfield-js-sdk/commit/402da22ea101f855a05f098b8970eef13b045ead), + [`c7de1fc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c7de1fc86211e879dcdf4c420e3f7363fa1e36a5)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.26 + +## 0.0.5-alpha.25 + +### Patch Changes + +- Updated dependencies + [[`1badb9c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1badb9c9dd71a7a39025e90adfe9a6feace75936), + [`c28ab8b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c28ab8b283f9aca37633c761527706f780bd13f5)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.25 + +## 0.0.5-alpha.24 + +### Patch Changes + +- Updated dependencies + [[`8f4e206`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8f4e20671b30befd4b6ede4807b1b1bacb190626), + [`c92e07e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c92e07e725accece3e1b126ea2ef0d4c6e5db431)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.24 + +## 0.0.5-alpha.23 + +### Patch Changes + +- Updated dependencies + [[`ff112b2`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ff112b2403b3e9826acb120cf42203996bf97872)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.23 + +## 0.0.5-alpha.22 + +### Patch Changes + +- Updated dependencies + [[`55f8851`](https://github.com/bnb-chain/greenfield-js-sdk/commit/55f8851a659a178c1ab37473f0c76f9a374a0368)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.22 + +## 0.0.5-alpha.21 + +### Patch Changes + +- Updated dependencies + [[`25ccbb6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/25ccbb6612b86d8037cec4b9ca3a9b141e10cc66), + [`b3683b8`](https://github.com/bnb-chain/greenfield-js-sdk/commit/b3683b8ce7e56a96c970964b2ade500eb95ef298)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.21 + +## 0.0.5-alpha.20 + +### Patch Changes + +- Updated dependencies + [[`a3e6b47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e6b472329e32fe86f7dd8a770d39a371a034cc)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.20 + +## 0.0.5-alpha.19 + +### Patch Changes + +- Updated dependencies + [[`a485367`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a485367f23175c4004a7f030a1a4081c65d0d632)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.19 + +## 0.0.5-alpha.18 + +### Patch Changes + +- Updated dependencies + [[`7824130`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7824130027ca00254b94b0a8af61330551c639ef)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.18 + +## 0.0.5-alpha.17 + +### Patch Changes + +- Updated dependencies + [[`352419e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/352419e2016e6643fe7223adfe2cc1c3a6ee7ba6), + [`21c9f6e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/21c9f6e2daa434a3d1385a845a0879750559ffbc)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.17 + +## 0.0.5-alpha.16 + +### Patch Changes + +- Updated dependencies + [[`569445b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/569445b218a156205172a15ce7aeb2d598b83f76)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.16 + +## 0.0.5-alpha.15 + +### Patch Changes + +- Updated dependencies + [[`ce00ce1`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ce00ce1779c2ef3c627fb7172600093e6b580d23)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.15 + +## 0.0.5-alpha.14 + +### Patch Changes + +- [#254](https://github.com/bnb-chain/greenfield-js-sdk/pull/254) + [`1ede2ea`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1ede2ea0ecc9ffc35d262a43a007221f06df3a0e) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add GetBucketMeta and GetObjectMeta + +- Updated dependencies + [[`1ede2ea`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1ede2ea0ecc9ffc35d262a43a007221f06df3a0e), + [`dba9871`](https://github.com/bnb-chain/greenfield-js-sdk/commit/dba987145172ddff7c7e363c144861a3f33ec999)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.14 + +## 0.0.5-alpha.13 + +### Patch Changes + +- Updated dependencies + [[`2e86b92`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e86b92a0762d0701356d24b7ad774364c039e3c)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.13 + +## 0.0.5-alpha.12 + +### Patch Changes + +- Updated dependencies + [[`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.12 + +## 0.0.5-alpha.11 + +### Patch Changes + +- Updated dependencies + [[`8782a21`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8782a21a9dcae1f9b83062877ff92540534fa40c), + [`d388940`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d388940e821e7fc3cb9671ffae4753d63a3189b9)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.11 + +## 0.0.5-alpha.10 + +### Patch Changes + +- Updated dependencies + [[`cbbb4aa`](https://github.com/bnb-chain/greenfield-js-sdk/commit/cbbb4aa2d0f1e76b9f367b95355590370d35d684)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.10 + +## 0.0.5-alpha.9 + +### Patch Changes + +- Updated dependencies + [[`db61ff6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/db61ff671f7ccc1be9ed6b1a229596916e1ae5ab)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.9 + +## 0.0.5-alpha.8 + +### Patch Changes + +- Updated dependencies + [[`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db), + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.8 + +## 0.0.5-alpha.7 + +### Patch Changes + +- Updated dependencies + [[`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`53fa253`](https://github.com/bnb-chain/greenfield-js-sdk/commit/53fa253423a47ef190457eec980c895a1d035ab0), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.7 + +## 0.0.5-alpha.6 + +### Patch Changes + +- Updated dependencies + [[`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.6 + +## 0.0.5-alpha.5 + +### Patch Changes + +- Updated dependencies + [[`48521b3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/48521b39f1b1ab5bb806fbe525ca1cd4437b3cbc)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.5 + +## 0.0.5-alpha.4 + +### Patch Changes + +- Updated dependencies + [[`2e44e95`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e44e9548712739c33cc98d82bfada1a06ef9472)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.4 + +## 0.0.5-alpha.3 + +### Patch Changes + +- Updated dependencies + [[`8ba4cc7`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8ba4cc735cbd72b07d8d0a9fadae2c4bda1dea53)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.3 + +## 0.0.5-alpha.2 + +### Patch Changes + +- Updated dependencies + [[`8d08848`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8d08848490d98fb774a8542d226647e8e5d65652)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.2 + +## 0.0.5-alpha.1 + +### Patch Changes + +- Updated dependencies + [[`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d), + [`e586e77`](https://github.com/bnb-chain/greenfield-js-sdk/commit/e586e7738e42581a9103e6157caad558896a8c63), + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d)]: + - @bnb-chain/greenfiled-file-handle@0.2.1-alpha.0 + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.1 + +## 0.0.5-alpha.0 + +### Patch Changes + +- Updated dependencies + [[`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.0 + ## 0.0.4 ### Patch Changes diff --git a/examples/nextjs/README.md b/examples/nextjs/README.md index 8eb0840e..7e9681db 100644 --- a/examples/nextjs/README.md +++ b/examples/nextjs/README.md @@ -5,8 +5,8 @@ Use [React](https://react.dev/) and [Next.js](https://nextjs.org/) ## Usage case * tx - * [transfer](./examples/wallet/src/components/transfer/index.tsx) - * [withdraw](./examples/wallet/src/components/withdraw/index.tsx) - * [bucket](./examples/wallet/src/components/bucket/index.tsx) - * [object](./examples/wallet/src/components/object/index.tsx) -* [query](./examples/wallet/src/components/withdraw/query.tsx) + * [transfer](./src/components/transfer/index.tsx) + * [withdraw](./src/components/withdraw/index.tsx) + * [bucket](./src/components/bucket/index.tsx) + * [object](./src/components/object/index.tsx) +* [query](./src/components/withdraw/query.tsx) diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index 92e06222..1b2da818 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@demo/wallet", - "version": "0.0.4", + "version": "0.0.5", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development next dev", @@ -24,7 +24,7 @@ "@types/node": "^18.7.1", "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", - "axios": "^1.3.2", + "axios": "^1.3.4", "cors": "^2.8.5", "eslint": "^8.21.0", "eslint-config-next": "13.1.6", diff --git a/examples/nextjs/src/client/index.ts b/examples/nextjs/src/client/index.ts index 2e152da3..ef65e391 100644 --- a/examples/nextjs/src/client/index.ts +++ b/examples/nextjs/src/client/index.ts @@ -1,15 +1,31 @@ import { GREEN_CHAIN_ID, GRPC_URL } from '@/config'; import { Client } from '@bnb-chain/greenfield-js-sdk'; -export const client = Client.create(GRPC_URL, String(GREEN_CHAIN_ID)); +export const client = Client.create(GRPC_URL, String(GREEN_CHAIN_ID), { + // zkCryptoUrl: 'https://dcellar.io/static/dcellar-web-ui/wasm/zk.wasm', + zkCryptoUrl: + 'https://unpkg.com/@bnb-chain/greenfield-zk-crypto@0.0.2-alpha.4/dist/node/zk-crypto.wasm', +}); export const getSps = async () => { const sps = await client.sp.getStorageProviders(); - const finalSps = (sps ?? []).filter((v: any) => v?.description?.moniker !== 'QATest'); + const finalSps = (sps ?? []).filter((v: any) => v.endpoint.includes('nodereal')); return finalSps; }; +export const getAllSps = async () => { + const sps = await getSps(); + + return sps.map((sp) => { + return { + address: sp.operatorAddress, + endpoint: sp.endpoint, + name: sp.description?.moniker, + }; + }); +}; + export const selectSp = async () => { const finalSps = await getSps(); diff --git a/examples/nextjs/src/components/bucket/create/index.tsx b/examples/nextjs/src/components/bucket/create/index.tsx index 87904a32..62569b20 100644 --- a/examples/nextjs/src/components/bucket/create/index.tsx +++ b/examples/nextjs/src/components/bucket/create/index.tsx @@ -1,11 +1,11 @@ import { client, selectSp } from '@/client'; import { ACCOUNT_PRIVATEKEY } from '@/config/env'; +import { getOffchainAuthKeys } from '@/utils/offchainAuth'; import { useState } from 'react'; -import { useAccount, useNetwork } from 'wagmi'; +import { useAccount } from 'wagmi'; export const CreateBucket = () => { - const { address } = useAccount(); - const { chain } = useNetwork(); + const { address, connector } = useAccount(); const [createBucketInfo, setCreateBucketInfo] = useState<{ bucketName: string; }>({ @@ -31,57 +31,33 @@ export const CreateBucket = () => { const spInfo = await selectSp(); console.log('spInfo', spInfo); - const createBucketTx = await client.bucket.createBucket({ - bucketName: createBucketInfo.bucketName, - creator: address, - visibility: 'VISIBILITY_TYPE_PUBLIC_READ', - chargedReadQuota: '0', - spInfo: { - primarySpAddress: spInfo.primarySpAddress, - }, - signType: 'authTypeV1', - privateKey: ACCOUNT_PRIVATEKEY, - }); - - const simulateInfo = await createBucketTx.simulate({ - denom: 'BNB', - }); - - console.log('simulateInfo', simulateInfo); - - const res = await createBucketTx.broadcast({ - denom: 'BNB', - gasLimit: Number(simulateInfo?.gasLimit), - gasPrice: simulateInfo?.gasPrice || '5000000000', - payer: address, - granter: '', - }); - - if (res.code === 0) { - alert('success'); + const provider = await connector?.getProvider(); + const offChainData = await getOffchainAuthKeys(address, provider); + if (!offChainData) { + alert('No offchain, please create offchain pairs first'); + return; } - }} - > - broadcast with simulate with authTypeV1 - -
- ); diff --git a/examples/nextjs/src/components/bucket/index.tsx b/examples/nextjs/src/components/bucket/index.tsx index 64b9d8b8..eac7284b 100644 --- a/examples/nextjs/src/components/bucket/index.tsx +++ b/examples/nextjs/src/components/bucket/index.tsx @@ -2,6 +2,8 @@ import { CreateBucket } from './create'; import { DeleteBucket } from './delete'; import { BucketInfo } from './info'; import { MigrateBucket } from './migrate'; +import { BucketQuota } from './quota'; +import { UpdateBucket } from './update'; export const Bucket = () => { return ( @@ -12,10 +14,18 @@ export const Bucket = () => {
+ + +
+
+ + +
+
diff --git a/examples/nextjs/src/components/bucket/info/index.tsx b/examples/nextjs/src/components/bucket/info/index.tsx index ae652ec2..5aefc5ab 100644 --- a/examples/nextjs/src/components/bucket/info/index.tsx +++ b/examples/nextjs/src/components/bucket/info/index.tsx @@ -1,9 +1,10 @@ import { client, selectSp } from '@/client'; +import { getOffchainAuthKeys } from '@/utils/offchainAuth'; import { useState } from 'react'; import { useAccount } from 'wagmi'; export const BucketInfo = () => { - const { address } = useAccount(); + const { address, connector } = useAccount(); const [bucketName, setBucketName] = useState(''); const [bucketId, setBucketId] = useState(''); @@ -22,6 +23,53 @@ export const BucketInfo = () => { > get bucket info by name +
+ +
+
diff --git a/examples/nextjs/src/components/bucket/migrate/index.tsx b/examples/nextjs/src/components/bucket/migrate/index.tsx index dd601d16..92dba858 100644 --- a/examples/nextjs/src/components/bucket/migrate/index.tsx +++ b/examples/nextjs/src/components/bucket/migrate/index.tsx @@ -1,9 +1,10 @@ import { client, selectSp } from '@/client'; +import { getOffchainAuthKeys } from '@/utils/offchainAuth'; import { useState } from 'react'; import { useAccount } from 'wagmi'; export const MigrateBucket = () => { - const { address } = useAccount(); + const { address, connector } = useAccount(); const [bucketName, setBucketName] = useState(''); return ( @@ -16,24 +17,35 @@ export const MigrateBucket = () => { setBucketName(e.target.value); }} /> -

+
+
+ ); +}; diff --git a/examples/nextjs/src/components/bucket/update/index.tsx b/examples/nextjs/src/components/bucket/update/index.tsx new file mode 100644 index 00000000..59c2874c --- /dev/null +++ b/examples/nextjs/src/components/bucket/update/index.tsx @@ -0,0 +1,58 @@ +import { client } from '@/client'; +import { useState } from 'react'; +import { useAccount } from 'wagmi'; + +export const UpdateBucket = () => { + const { address } = useAccount(); + const [bucketName, setBucketName] = useState(''); + + return ( + <> +

Update Bucket

+
+ bucket name: + { + setBucketName(e.target.value); + }} + /> +
+
+ + + ); +}; diff --git a/examples/nextjs/src/components/deposit/index.tsx b/examples/nextjs/src/components/deposit/index.tsx index 82e1acb2..2aaee61c 100644 --- a/examples/nextjs/src/components/deposit/index.tsx +++ b/examples/nextjs/src/components/deposit/index.tsx @@ -37,21 +37,22 @@ export const Deposit = () => { const amount_with_relay_fee = relayFee + ackRelayFee + amount; console.log('amount_with_relay_fee', amount_with_relay_fee); - const estimateGas = await publicClient.estimateContractGas({ - address: TOKEN_HUB_CONTRACT_ADDRESS, - abi: TOKENHUB_ABI, - functionName: 'transferOut', - args: [address, amount], - account: address, - value: amount_with_relay_fee, - }); - console.log('estimateGas', estimateGas); + // const estimateGas = await publicClient.estimateContractGas({ + // address: TOKEN_HUB_CONTRACT_ADDRESS, + // abi: TOKENHUB_ABI, + // functionName: 'transferOut', + // args: [address, amount], + // account: address, + // value: amount_with_relay_fee, + // }); + // console.log('estimateGas', estimateGas); const gasPrice = await publicClient.getGasPrice(); console.log('gasPrice', gasPrice); - const gasFee = estimateGas * gasPrice; + // const gasFee = BigInt(5) * gasPrice; + const gasFee = 21000n; console.log('estimate gas fee: gas price * gas = ', formatEther(gasFee), 'ETH'); const txHash = await walletClient.writeContract({ diff --git a/examples/nextjs/src/components/feegrant/createObj.tsx b/examples/nextjs/src/components/feegrant/createObj.tsx index f00c2c06..1411a94c 100644 --- a/examples/nextjs/src/components/feegrant/createObj.tsx +++ b/examples/nextjs/src/components/feegrant/createObj.tsx @@ -1,11 +1,12 @@ import { client } from '@/client'; +import { ACCOUNT_PRIVATEKEY } from '@/config/env'; import { GRNToString, MsgCreateObjectTypeUrl, newBucketGRN, PermissionTypes, + toTimestamp, } from '@bnb-chain/greenfield-js-sdk'; -import { FileHandler } from '@bnb-chain/greenfiled-file-handle'; import { Wallet } from '@ethersproject/wallet'; import { ChangeEvent, useState } from 'react'; import { parseEther } from 'viem'; @@ -62,12 +63,15 @@ export const CreateObj = () => { setWallet(wallet); // 2. allow temporary account to submit specified tx and amount + const date = new Date(); + date.setDate(date.getMinutes() + 10); const grantAllowanceTx = await client.feegrant.grantAllowance({ granter: address, grantee: wallet.address, allowedMessages: [MsgCreateObjectTypeUrl], - amount: parseEther('0.09').toString(), + amount: parseEther('0.01').toString(), denom: 'BNB', + expirationTime: toTimestamp(date), }); // 3. Put bucket policy so that the temporary account can create objects within this bucket @@ -83,6 +87,7 @@ export const CreateObj = () => { type: PermissionTypes.PrincipalType.PRINCIPAL_TYPE_GNFD_ACCOUNT, value: wallet.address, }, + expirationTime: toTimestamp(date), }); // 4. broadcast txs include 2 msg @@ -120,21 +125,27 @@ export const CreateObj = () => { console.log('temp account', granteeAddr, privateKey); const fileBytes = await file.arrayBuffer(); - const hashResult = await FileHandler.getPieceHashRoots(new Uint8Array(fileBytes)); + const hashResult = await (window as any).FileHandle.getCheckSums( + new Uint8Array(fileBytes), + ); const { contentLength, expectCheckSums } = hashResult; - const createObjectTx = await client.object.createObject({ - bucketName: bucketName, - objectName: objectName, - visibility: 'VISIBILITY_TYPE_PUBLIC_READ', - redundancyType: 'REDUNDANCY_EC_TYPE', - contentLength, - expectCheckSums, - fileType: file.type, - signType: 'authTypeV1', - creator: granteeAddr, - privateKey: privateKey, - }); + const createObjectTx = await client.object.createObject( + { + creator: granteeAddr, + bucketName: bucketName, + objectName: objectName, + visibility: 'VISIBILITY_TYPE_PUBLIC_READ', + redundancyType: 'REDUNDANCY_EC_TYPE', + contentLength, + expectCheckSums, + fileType: file.type, + }, + { + type: 'ECDSA', + privateKey: ACCOUNT_PRIVATEKEY, + }, + ); const simulateInfo = await createObjectTx.simulate({ denom: 'BNB', diff --git a/examples/nextjs/src/components/feegrant/delObj.tsx b/examples/nextjs/src/components/feegrant/delObj.tsx index 3db30600..ff7a4c7e 100644 --- a/examples/nextjs/src/components/feegrant/delObj.tsx +++ b/examples/nextjs/src/components/feegrant/delObj.tsx @@ -4,6 +4,7 @@ import { MsgDeleteObjectTypeUrl, newObjectGRN, PermissionTypes, + toTimestamp, } from '@bnb-chain/greenfield-js-sdk'; import { Wallet } from '@ethersproject/wallet'; import { useState } from 'react'; @@ -49,12 +50,15 @@ export const DelObj = () => { setWallet(wallet); // 2. allow temporary account to submit specified tx and amount + const date = new Date(); + date.setDate(date.getDate() + 1); const grantAllowanceTx = await client.feegrant.grantAllowance({ granter: address, grantee: wallet.address, allowedMessages: [MsgDeleteObjectTypeUrl], amount: parseEther('0.09').toString(), denom: 'BNB', + expirationTime: toTimestamp(date), }); // 3. Put bucket policy so that the temporary account can create objects within this bucket diff --git a/examples/nextjs/src/components/feegrant/index.tsx b/examples/nextjs/src/components/feegrant/index.tsx index a3a15031..20d332bc 100644 --- a/examples/nextjs/src/components/feegrant/index.tsx +++ b/examples/nextjs/src/components/feegrant/index.tsx @@ -1,4 +1,5 @@ import { client } from '@/client'; +import { Long } from '@bnb-chain/greenfield-js-sdk'; import { useState } from 'react'; import { CreateObj } from './createObj'; import { DelObj } from './delObj'; @@ -24,6 +25,13 @@ export const FeeGrant = () => { const res = await client.feegrant.getAllowences({ grantee: account, + pagination: { + limit: Long.fromInt(10), + offset: Long.fromInt(0), + countTotal: true, + key: Uint8Array.from([]), + reverse: false, + }, }); console.log('res', res); }} diff --git a/examples/nextjs/src/components/group/create/index.tsx b/examples/nextjs/src/components/group/create/index.tsx index fbacda34..f7b1e5c5 100644 --- a/examples/nextjs/src/components/group/create/index.tsx +++ b/examples/nextjs/src/components/group/create/index.tsx @@ -27,7 +27,6 @@ export const CreateGroup = () => { const createGroupTx = await client.group.createGroup({ creator: address, groupName: createGroupInfo.groupName, - members: [address], extra: 'extra info', }); diff --git a/examples/nextjs/src/components/group/index.tsx b/examples/nextjs/src/components/group/index.tsx index 10e0bcfb..9f3cce44 100644 --- a/examples/nextjs/src/components/group/index.tsx +++ b/examples/nextjs/src/components/group/index.tsx @@ -2,6 +2,7 @@ import { CreateGroup } from './create'; import { DeleteGroup } from './delete'; import { GroupInfo } from './info'; import { ListGroup } from './list'; +import { GroupUpdate } from './update'; export const Group = () => { return ( @@ -14,6 +15,8 @@ export const Group = () => { + +
); diff --git a/examples/nextjs/src/components/group/info/index.tsx b/examples/nextjs/src/components/group/info/index.tsx index ebeeb69b..bbb8562a 100644 --- a/examples/nextjs/src/components/group/info/index.tsx +++ b/examples/nextjs/src/components/group/info/index.tsx @@ -20,12 +20,25 @@ export const GroupInfo = () => { if (!address) return; const groupInfo = await client.group.headGroup(groupName, address); - console.log('groupInfo', groupInfo); }} > get group info +
); }; diff --git a/examples/nextjs/src/components/group/list/index.tsx b/examples/nextjs/src/components/group/list/index.tsx index 9c4569ed..c84156b6 100644 --- a/examples/nextjs/src/components/group/list/index.tsx +++ b/examples/nextjs/src/components/group/list/index.tsx @@ -26,7 +26,9 @@ export const ListGroup = () => { onClick={async () => { if (!address) return; - const groupList = await client.sp.listGroup(groupName, prefix, { + const groupList = await client.sp.listGroups({ + name: groupName, + prefix, sourceType: 'SOURCE_TYPE_ORIGIN', limit: 1000, offset: 0, diff --git a/examples/nextjs/src/components/group/update/index.tsx b/examples/nextjs/src/components/group/update/index.tsx new file mode 100644 index 00000000..ec51a2bc --- /dev/null +++ b/examples/nextjs/src/components/group/update/index.tsx @@ -0,0 +1,122 @@ +import { client } from '@/client'; +import { toTimestamp } from '@bnb-chain/greenfield-js-sdk'; +import { useState } from 'react'; +import { zeroAddress } from 'viem'; +import { useAccount } from 'wagmi'; + +export const GroupUpdate = () => { + const { address } = useAccount(); + const [groupName, setGroupName] = useState(''); + + return ( +
+

group update

+ { + setGroupName(e.target.value); + }} + /> + + + +
+ ); +}; diff --git a/examples/nextjs/src/components/mirror/index.tsx b/examples/nextjs/src/components/mirror/index.tsx index 9e9df125..b91e3cdd 100644 --- a/examples/nextjs/src/components/mirror/index.tsx +++ b/examples/nextjs/src/components/mirror/index.tsx @@ -1,4 +1,5 @@ import { client } from '@/client'; +import { BSC_CHAIN_ID } from '@/config'; import { useState } from 'react'; import { useAccount } from 'wagmi'; @@ -29,6 +30,7 @@ export const Mirror = () => { groupName: '', id: groupInfo.id, operator: address, + destChainId: BSC_CHAIN_ID, }); const simulateInfo = await mirrorGroupTx.simulate({ diff --git a/examples/nextjs/src/components/multimsg/index.tsx b/examples/nextjs/src/components/multimsg/index.tsx index cab665f4..ea232050 100644 --- a/examples/nextjs/src/components/multimsg/index.tsx +++ b/examples/nextjs/src/components/multimsg/index.tsx @@ -1,5 +1,4 @@ import { client } from '@/client'; -import { ACCOUNT_PRIVATEKEY } from '@/config/env'; import { parseEther } from 'viem'; import { useAccount } from 'wagmi'; @@ -51,7 +50,6 @@ export const MultiMsg = () => { gasPrice: '5000000000', payer: address, granter: '', - privateKey: ACCOUNT_PRIVATEKEY, }); if (res.code === 0) { diff --git a/examples/nextjs/src/components/object/create/index.tsx b/examples/nextjs/src/components/object/create/index.tsx index da32183e..861e2b6d 100644 --- a/examples/nextjs/src/components/object/create/index.tsx +++ b/examples/nextjs/src/components/object/create/index.tsx @@ -1,11 +1,11 @@ -import { client, selectSp } from '@/client'; +import { client } from '@/client'; import { ACCOUNT_PRIVATEKEY } from '@/config/env'; -import { FileHandler } from '@bnb-chain/greenfiled-file-handle'; +import { getOffchainAuthKeys } from '@/utils/offchainAuth'; import { ChangeEvent, useState } from 'react'; import { useAccount } from 'wagmi'; export const CreateObject = () => { - const { address } = useAccount(); + const { address, connector } = useAccount(); const [file, setFile] = useState(); const [txHash, setTxHash] = useState(); const [createObjectInfo, setCreateObjectInfo] = useState({ @@ -52,22 +52,42 @@ export const CreateObject = () => { return; } + const provider = await connector?.getProvider(); + const offChainData = await getOffchainAuthKeys(address, provider); + if (!offChainData) { + alert('No offchain, please create offchain pairs first'); + return; + } + const fileBytes = await file.arrayBuffer(); - const hashResult = await FileHandler.getPieceHashRoots(new Uint8Array(fileBytes)); + const hashResult = await (window as any).FileHandle.getCheckSums( + new Uint8Array(fileBytes), + ); const { contentLength, expectCheckSums } = hashResult; - const createObjectTx = await client.object.createObject({ - bucketName: createObjectInfo.bucketName, - objectName: createObjectInfo.objectName, - creator: address, - visibility: 'VISIBILITY_TYPE_PUBLIC_READ', - fileType: file.type, - redundancyType: 'REDUNDANCY_EC_TYPE', - contentLength, - expectCheckSums, - signType: 'authTypeV1', - privateKey: ACCOUNT_PRIVATEKEY, - }); + console.log('offChainData', offChainData); + console.log('hashResult', hashResult); + + const createObjectTx = await client.object.createObject( + { + bucketName: createObjectInfo.bucketName, + objectName: createObjectInfo.objectName, + creator: address, + visibility: 'VISIBILITY_TYPE_PRIVATE', + fileType: file.type, + redundancyType: 'REDUNDANCY_EC_TYPE', + contentLength, + expectCheckSums: JSON.parse(expectCheckSums), + }, + { + type: 'EDDSA', + domain: window.location.origin, + seed: offChainData.seedString, + address, + // type: 'ECDSA', + // privateKey: ACCOUNT_PRIVATEKEY, + }, + ); const simulateInfo = await createObjectTx.simulate({ denom: 'BNB', @@ -97,17 +117,30 @@ export const CreateObject = () => {
+ +
+ + + +
+ + + +
+ +
- get object by bucket name + get objects list by bucket name
- - ); -}; diff --git a/examples/nextjs/src/components/payment/index.tsx b/examples/nextjs/src/components/payment/index.tsx new file mode 100644 index 00000000..28efa015 --- /dev/null +++ b/examples/nextjs/src/components/payment/index.tsx @@ -0,0 +1,174 @@ +import { client } from '@/client'; +import { useState } from 'react'; +import { parseEther } from 'viem'; +import { useAccount } from 'wagmi'; + +export const PaymentComponent = () => { + const { address } = useAccount(); + const [paymentAccount, setPaymentAccount] = useState(''); + return ( + <> +

Payment

+ +
+ +
+ +
+ payment account: + { + setPaymentAccount(e.target.value); + }} + /> +

payment account deposit

+ +
+

payment account withdraw

+ +
+

payment account disableRefund

+ + + ); +}; diff --git a/examples/nextjs/src/components/query/index.tsx b/examples/nextjs/src/components/query/index.tsx index b94fd792..f07536f4 100644 --- a/examples/nextjs/src/components/query/index.tsx +++ b/examples/nextjs/src/components/query/index.tsx @@ -1,4 +1,5 @@ import { client } from '@/client'; +import { Long } from '@bnb-chain/greenfield-js-sdk'; import { useAccount } from 'wagmi'; export const QueryComponent = () => { @@ -31,6 +32,13 @@ export const QueryComponent = () => { onClick={async () => { const gasfeeList = await client.gashub.getMsgGasParams({ msgTypeUrls: [], + pagination: { + countTotal: true, + key: Uint8Array.from([]), + limit: Long.fromInt(10), + offset: Long.fromInt(0), + reverse: false, + }, }); console.log('gasfeeList', gasfeeList); @@ -63,6 +71,96 @@ export const QueryComponent = () => { getSPUrlByBucket +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • ); diff --git a/examples/nextjs/src/components/withdraw/index.tsx b/examples/nextjs/src/components/withdraw/index.tsx index 14a31f68..e0327925 100644 --- a/examples/nextjs/src/components/withdraw/index.tsx +++ b/examples/nextjs/src/components/withdraw/index.tsx @@ -69,8 +69,8 @@ export const Withdraw = () => { const relayFee = relayFeeInfo.params ? getRelayFeeBySimulate( - relayFeeInfo.params.transferOutAckRelayerFee, - relayFeeInfo.params.transferOutRelayerFee, + relayFeeInfo.params.bscTransferOutAckRelayerFee, + relayFeeInfo.params.bscTransferOutRelayerFee, ) : '0'; setTransferOutRelayFee(relayFee.toString()); diff --git a/examples/nextjs/src/pages/_app.tsx b/examples/nextjs/src/pages/_app.tsx index 0bc7deed..5d96ff63 100644 --- a/examples/nextjs/src/pages/_app.tsx +++ b/examples/nextjs/src/pages/_app.tsx @@ -1,4 +1,4 @@ -import { chains, publicClient, RainbowTrustWalletConnector, webSocketPublicClient } from '@/config'; +import { chains, publicClient, webSocketPublicClient } from '@/config'; import '@/styles/globals.css'; import { connectorsForWallets, @@ -6,7 +6,6 @@ import { RainbowKitProvider, } from '@rainbow-me/rainbowkit'; import '@rainbow-me/rainbowkit/styles.css'; -import { trustWallet } from '@rainbow-me/rainbowkit/wallets'; import type { AppProps } from 'next/app'; import { createConfig, WagmiConfig } from 'wagmi'; diff --git a/examples/nextjs/src/pages/_document.tsx b/examples/nextjs/src/pages/_document.tsx index e1e9cbbb..d90c792e 100644 --- a/examples/nextjs/src/pages/_document.tsx +++ b/examples/nextjs/src/pages/_document.tsx @@ -8,6 +8,12 @@ export default function Document() {
    + + ); } diff --git a/examples/nextjs/src/pages/tx.tsx b/examples/nextjs/src/pages/tx.tsx index 9b2029c4..c5e05b00 100644 --- a/examples/nextjs/src/pages/tx.tsx +++ b/examples/nextjs/src/pages/tx.tsx @@ -5,13 +5,13 @@ import { Group } from '@/components/group'; import { Mirror } from '@/components/mirror'; import { MultiMsg } from '@/components/multimsg'; import { ObjectComponent } from '@/components/object'; -import { OffChainAuth } from '@/components/offchainauth'; import { Policy } from '@/components/policy'; import { Transfer } from '@/components/transfer'; import { WalletInfo } from '@/components/walletInfo'; import { Withdraw } from '@/components/withdraw'; import { useIsMounted } from '@/hooks/useIsMounted'; import { useAccount } from 'wagmi'; +import { PaymentComponent } from '@/components/payment'; export default function Tx() { const isMounted = useIsMounted(); @@ -33,12 +33,12 @@ export default function Tx() {

    - -


    + +

    diff --git a/examples/nextjs/src/utils/offchainAuth.ts b/examples/nextjs/src/utils/offchainAuth.ts new file mode 100644 index 00000000..2401bf74 --- /dev/null +++ b/examples/nextjs/src/utils/offchainAuth.ts @@ -0,0 +1,41 @@ +import { client, getAllSps } from '@/client'; +import { GREEN_CHAIN_ID } from '@/config/env'; +import { IReturnOffChainAuthKeyPairAndUpload } from '@bnb-chain/greenfield-js-sdk'; + +/** + * generate off-chain auth key pair and upload public key to sp + */ +export const getOffchainAuthKeys = async (address: string, provider: any) => { + const storageResStr = localStorage.getItem(address); + + if (storageResStr) { + const storageRes = JSON.parse(storageResStr) as IReturnOffChainAuthKeyPairAndUpload; + if (storageRes.expirationTime < Date.now()) { + alert('Your auth key has expired, please generate a new one'); + localStorage.removeItem(address); + return; + } + + return storageRes; + } + + const allSps = await getAllSps(); + const offchainAuthRes = await client.offchainauth.genOffChainAuthKeyPairAndUpload( + { + sps: allSps, + chainId: GREEN_CHAIN_ID, + expirationMs: 5 * 24 * 60 * 60 * 1000, + domain: window.location.origin, + address, + }, + provider, + ); + + const { code, body: offChainData } = offchainAuthRes; + if (code !== 0 || !offChainData) { + throw offchainAuthRes; + } + + localStorage.setItem(address, JSON.stringify(offChainData)); + return offChainData; +}; diff --git a/examples/nodejs/CHANGELOG.md b/examples/nodejs/CHANGELOG.md index 209e51dc..72b005f5 100644 --- a/examples/nodejs/CHANGELOG.md +++ b/examples/nodejs/CHANGELOG.md @@ -1,5 +1,335 @@ # @demo/nodejs +## 0.0.8 + +### Patch Changes + +- Updated dependencies + [[`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`a485367`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a485367f23175c4004a7f030a1a4081c65d0d632), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`2e44e95`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e44e9548712739c33cc98d82bfada1a06ef9472), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`1ede2ea`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1ede2ea0ecc9ffc35d262a43a007221f06df3a0e), + [`8f4e206`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8f4e20671b30befd4b6ede4807b1b1bacb190626), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`53fa253`](https://github.com/bnb-chain/greenfield-js-sdk/commit/53fa253423a47ef190457eec980c895a1d035ab0), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`faf5d47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/faf5d474d626010ef9a4d83bf96491b66d265d35), + [`a3e6b47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e6b472329e32fe86f7dd8a770d39a371a034cc), + [`402da22`](https://github.com/bnb-chain/greenfield-js-sdk/commit/402da22ea101f855a05f098b8970eef13b045ead), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`fcf6283`](https://github.com/bnb-chain/greenfield-js-sdk/commit/fcf62832288fa3a67b552d69e321d73690cbc87e), + [`dba9871`](https://github.com/bnb-chain/greenfield-js-sdk/commit/dba987145172ddff7c7e363c144861a3f33ec999), + [`1badb9c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1badb9c9dd71a7a39025e90adfe9a6feace75936), + [`db61ff6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/db61ff671f7ccc1be9ed6b1a229596916e1ae5ab), + [`7d69f2e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7d69f2e3d044fc5f4c50d5251f99d8e04fb39b86), + [`352419e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/352419e2016e6643fe7223adfe2cc1c3a6ee7ba6), + [`55f8851`](https://github.com/bnb-chain/greenfield-js-sdk/commit/55f8851a659a178c1ab37473f0c76f9a374a0368), + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db), + [`0084053`](https://github.com/bnb-chain/greenfield-js-sdk/commit/00840538a95e830752dd0ea07f52990303dccf95), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`c28ab8b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c28ab8b283f9aca37633c761527706f780bd13f5), + [`7869b75`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7869b75fa16ef8f71daad0a00b0d10ba7fb13fb3), + [`9710b5b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/9710b5bd294484c8a8021193f649b006481e9cd5), + [`ce00ce1`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ce00ce1779c2ef3c627fb7172600093e6b580d23), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`ea31d73`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ea31d738124058d75b251d6a897f924a122b1471), + [`8d08848`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8d08848490d98fb774a8542d226647e8e5d65652), + [`ff112b2`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ff112b2403b3e9826acb120cf42203996bf97872), + [`21c9f6e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/21c9f6e2daa434a3d1385a845a0879750559ffbc), + [`7824130`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7824130027ca00254b94b0a8af61330551c639ef), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`c92e07e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c92e07e725accece3e1b126ea2ef0d4c6e5db431), + [`2e86b92`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e86b92a0762d0701356d24b7ad774364c039e3c), + [`12bd37c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/12bd37ccdefe789660409882c7045e2cf2a60ff6), + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db), + [`8782a21`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8782a21a9dcae1f9b83062877ff92540534fa40c), + [`ad1d7d3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ad1d7d3fa54fae50aed1b4e1d45a152e2e3fb08c), + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d), + [`25ccbb6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/25ccbb6612b86d8037cec4b9ca3a9b141e10cc66), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`569445b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/569445b218a156205172a15ce7aeb2d598b83f76), + [`e586e77`](https://github.com/bnb-chain/greenfield-js-sdk/commit/e586e7738e42581a9103e6157caad558896a8c63), + [`b3683b8`](https://github.com/bnb-chain/greenfield-js-sdk/commit/b3683b8ce7e56a96c970964b2ade500eb95ef298), + [`c7de1fc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c7de1fc86211e879dcdf4c420e3f7363fa1e36a5), + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`8ba4cc7`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8ba4cc735cbd72b07d8d0a9fadae2c4bda1dea53), + [`48521b3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/48521b39f1b1ab5bb806fbe525ca1cd4437b3cbc), + [`cbbb4aa`](https://github.com/bnb-chain/greenfield-js-sdk/commit/cbbb4aa2d0f1e76b9f367b95355590370d35d684), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`d388940`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d388940e821e7fc3cb9671ffae4753d63a3189b9)]: + - @bnb-chain/greenfield-js-sdk@0.2.4 + - @bnb-chain/greenfiled-file-handle@0.2.1 + +## 0.0.8-alpha.28 + +### Patch Changes + +- Updated dependencies + [[`7869b75`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7869b75fa16ef8f71daad0a00b0d10ba7fb13fb3)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.28 + +## 0.0.8-alpha.27 + +### Patch Changes + +- Updated dependencies + [[`faf5d47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/faf5d474d626010ef9a4d83bf96491b66d265d35), + [`fcf6283`](https://github.com/bnb-chain/greenfield-js-sdk/commit/fcf62832288fa3a67b552d69e321d73690cbc87e), + [`7d69f2e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7d69f2e3d044fc5f4c50d5251f99d8e04fb39b86), + [`0084053`](https://github.com/bnb-chain/greenfield-js-sdk/commit/00840538a95e830752dd0ea07f52990303dccf95), + [`9710b5b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/9710b5bd294484c8a8021193f649b006481e9cd5), + [`ea31d73`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ea31d738124058d75b251d6a897f924a122b1471), + [`12bd37c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/12bd37ccdefe789660409882c7045e2cf2a60ff6), + [`ad1d7d3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ad1d7d3fa54fae50aed1b4e1d45a152e2e3fb08c)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.27 + +## 0.0.8-alpha.26 + +### Patch Changes + +- Updated dependencies + [[`402da22`](https://github.com/bnb-chain/greenfield-js-sdk/commit/402da22ea101f855a05f098b8970eef13b045ead), + [`c7de1fc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c7de1fc86211e879dcdf4c420e3f7363fa1e36a5)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.26 + +## 0.0.8-alpha.25 + +### Patch Changes + +- Updated dependencies + [[`1badb9c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1badb9c9dd71a7a39025e90adfe9a6feace75936), + [`c28ab8b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c28ab8b283f9aca37633c761527706f780bd13f5)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.25 + +## 0.0.8-alpha.24 + +### Patch Changes + +- Updated dependencies + [[`8f4e206`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8f4e20671b30befd4b6ede4807b1b1bacb190626), + [`c92e07e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c92e07e725accece3e1b126ea2ef0d4c6e5db431)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.24 + +## 0.0.8-alpha.23 + +### Patch Changes + +- Updated dependencies + [[`ff112b2`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ff112b2403b3e9826acb120cf42203996bf97872)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.23 + +## 0.0.8-alpha.22 + +### Patch Changes + +- Updated dependencies + [[`55f8851`](https://github.com/bnb-chain/greenfield-js-sdk/commit/55f8851a659a178c1ab37473f0c76f9a374a0368)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.22 + +## 0.0.8-alpha.21 + +### Patch Changes + +- Updated dependencies + [[`25ccbb6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/25ccbb6612b86d8037cec4b9ca3a9b141e10cc66), + [`b3683b8`](https://github.com/bnb-chain/greenfield-js-sdk/commit/b3683b8ce7e56a96c970964b2ade500eb95ef298)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.21 + +## 0.0.8-alpha.20 + +### Patch Changes + +- Updated dependencies + [[`a3e6b47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e6b472329e32fe86f7dd8a770d39a371a034cc)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.20 + +## 0.0.8-alpha.19 + +### Patch Changes + +- Updated dependencies + [[`a485367`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a485367f23175c4004a7f030a1a4081c65d0d632)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.19 + +## 0.0.8-alpha.18 + +### Patch Changes + +- Updated dependencies + [[`7824130`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7824130027ca00254b94b0a8af61330551c639ef)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.18 + +## 0.0.8-alpha.17 + +### Patch Changes + +- Updated dependencies + [[`352419e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/352419e2016e6643fe7223adfe2cc1c3a6ee7ba6), + [`21c9f6e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/21c9f6e2daa434a3d1385a845a0879750559ffbc)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.17 + +## 0.0.8-alpha.16 + +### Patch Changes + +- Updated dependencies + [[`569445b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/569445b218a156205172a15ce7aeb2d598b83f76)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.16 + +## 0.0.8-alpha.15 + +### Patch Changes + +- Updated dependencies + [[`ce00ce1`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ce00ce1779c2ef3c627fb7172600093e6b580d23)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.15 + +## 0.0.8-alpha.14 + +### Patch Changes + +- Updated dependencies + [[`1ede2ea`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1ede2ea0ecc9ffc35d262a43a007221f06df3a0e), + [`dba9871`](https://github.com/bnb-chain/greenfield-js-sdk/commit/dba987145172ddff7c7e363c144861a3f33ec999)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.14 + +## 0.0.8-alpha.13 + +### Patch Changes + +- Updated dependencies + [[`2e86b92`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e86b92a0762d0701356d24b7ad774364c039e3c)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.13 + +## 0.0.8-alpha.12 + +### Patch Changes + +- Updated dependencies + [[`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7), + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.12 + +## 0.0.8-alpha.11 + +### Patch Changes + +- Updated dependencies + [[`8782a21`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8782a21a9dcae1f9b83062877ff92540534fa40c), + [`d388940`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d388940e821e7fc3cb9671ffae4753d63a3189b9)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.11 + +## 0.0.8-alpha.10 + +### Patch Changes + +- Updated dependencies + [[`cbbb4aa`](https://github.com/bnb-chain/greenfield-js-sdk/commit/cbbb4aa2d0f1e76b9f367b95355590370d35d684)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.10 + +## 0.0.8-alpha.9 + +### Patch Changes + +- Updated dependencies + [[`db61ff6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/db61ff671f7ccc1be9ed6b1a229596916e1ae5ab)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.9 + +## 0.0.8-alpha.8 + +### Patch Changes + +- Updated dependencies + [[`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db), + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.8 + +## 0.0.8-alpha.7 + +### Patch Changes + +- Updated dependencies + [[`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`53fa253`](https://github.com/bnb-chain/greenfield-js-sdk/commit/53fa253423a47ef190457eec980c895a1d035ab0), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a), + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.7 + +## 0.0.8-alpha.6 + +### Patch Changes + +- Updated dependencies + [[`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.6 + +## 0.0.8-alpha.5 + +### Patch Changes + +- Updated dependencies + [[`48521b3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/48521b39f1b1ab5bb806fbe525ca1cd4437b3cbc)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.5 + +## 0.0.8-alpha.4 + +### Patch Changes + +- Updated dependencies + [[`2e44e95`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e44e9548712739c33cc98d82bfada1a06ef9472)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.4 + +## 0.0.8-alpha.3 + +### Patch Changes + +- Updated dependencies + [[`8ba4cc7`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8ba4cc735cbd72b07d8d0a9fadae2c4bda1dea53)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.3 + +## 0.0.8-alpha.2 + +### Patch Changes + +- Updated dependencies + [[`8d08848`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8d08848490d98fb774a8542d226647e8e5d65652)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.2 + +## 0.0.8-alpha.1 + +### Patch Changes + +- Updated dependencies + [[`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d), + [`e586e77`](https://github.com/bnb-chain/greenfield-js-sdk/commit/e586e7738e42581a9103e6157caad558896a8c63), + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d)]: + - @bnb-chain/greenfiled-file-handle@0.2.1-alpha.0 + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.1 + +## 0.0.8-alpha.0 + +### Patch Changes + +- Updated dependencies + [[`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28), + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28)]: + - @bnb-chain/greenfield-js-sdk@0.2.4-alpha.0 + ## 0.0.7 ### Patch Changes diff --git a/examples/nodejs/index.js b/examples/nodejs/index.js index b57a8a1f..32390f6f 100644 --- a/examples/nodejs/index.js +++ b/examples/nodejs/index.js @@ -1,28 +1,43 @@ -const { Client } = require('@bnb-chain/greenfield-chain-sdk'); -const { getCheckSums } = require('@bnb-chain/greenfiled-file-handle/files'); -const fs = require('fs'); +const { Client } = require('@bnb-chain/greenfield-js-sdk'); +// const { getCheckSums } = require('@bnb-chain/greenfiled-file-handle/files'); +// const fs = require('fs'); + +// const client = Client.create('https://gnfd-dev.qa.bnbchain.world', '8981'); const client = Client.create('https://gnfd.qa.bnbchain.world', '9000'); -// (async () => { -// const filePath = './package.json'; -// const fileBuf = fs.readFileSync(filePath); -// const DEFAULT_SEGMENT_SIZE = 16 * 1024 * 1024; -// const DEFAULT_DATA_BLOCKS = 4; -// const DEFAULT_PARITY_BLOCKS = 2; -// const bytes = new Uint8Array(fileBuf); +(async () => { + const createBucketTx = await client.bucket.createBucket({ + bucketName: 'foo', + creator: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + visibility: 'VISIBILITY_TYPE_PUBLIC_READ', + chargedReadQuota: '0', + spInfo: { + primarySpAddress: '0x66d06FFe266B46C6F0730cC9Ec2fc5B811cdA085', + }, + signType: 'authTypeV1', + privateKey: '0x6547492644d0136f76ef65e3bd04a77d079ed38028f747700c6c6063564d7032', + // signType: 'offChainAuth', + // domain: window.location.origin, + // seedString: offChainData.seedString, + }); + + const simulateInfo = await createBucketTx.simulate({ + denom: 'BNB', + }); -// const hashResult = await getCheckSums( -// Buffer.from(bytes).toString('hex'), -// DEFAULT_SEGMENT_SIZE, -// DEFAULT_DATA_BLOCKS, -// DEFAULT_PARITY_BLOCKS, -// ); -// console.log('hashResult', hashResult); -// })(); + console.log('simulateInfo', simulateInfo); -(async () => { - const account = await client.account.getAccount('0x1C893441AB6c1A75E01887087ea508bE8e07AAae'); + const res = await createBucketTx.broadcast({ + denom: 'BNB', + gasLimit: Number(simulateInfo?.gasLimit), + gasPrice: simulateInfo?.gasPrice || '5000000000', + payer: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + granter: '', + privateKey: '0x6547492644d0136f76ef65e3bd04a77d079ed38028f747700c6c6063564d7032', + }); - console.log(account); + if (res.code === 0) { + console.log('success'); + } })(); diff --git a/examples/nodejs/package.json b/examples/nodejs/package.json index 5be665e3..9ab81a68 100644 --- a/examples/nodejs/package.json +++ b/examples/nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@demo/nodejs", - "version": "0.0.7", + "version": "0.0.8", "type": "commonjs", "private": true, "scripts": { diff --git a/packages/chain-sdk/CHANGELOG.md b/packages/chain-sdk/CHANGELOG.md index c880e8e5..28e3eb58 100644 --- a/packages/chain-sdk/CHANGELOG.md +++ b/packages/chain-sdk/CHANGELOG.md @@ -1,5 +1,624 @@ # @bnb-chain/greenfield-js-sdk +## 0.2.4 + +### Patch Changes + +- [#248](https://github.com/bnb-chain/greenfield-js-sdk/pull/248) + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Upload Object add AuthType + +- [#232](https://github.com/bnb-chain/greenfield-js-sdk/pull/232) + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Compatibility new `updateGroupMember` api + +- [#267](https://github.com/bnb-chain/greenfield-js-sdk/pull/267) + [`a485367`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a485367f23175c4004a7f030a1a4081c65d0d632) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Download HTTP method + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - refactor: Tx + +- [#223](https://github.com/bnb-chain/greenfield-js-sdk/pull/223) + [`2e44e95`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e44e9548712739c33cc98d82bfada1a06ef9472) + Thanks [@rrr523](https://github.com/rrr523)! - fix: DeepClone EIP712 + +- [#248](https://github.com/bnb-chain/greenfield-js-sdk/pull/248) + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7) + Thanks [@rrr523](https://github.com/rrr523)! - feat: download s3 object + +- [#254](https://github.com/bnb-chain/greenfield-js-sdk/pull/254) + [`1ede2ea`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1ede2ea0ecc9ffc35d262a43a007221f06df3a0e) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add GetBucketMeta and GetObjectMeta + +- [#282](https://github.com/bnb-chain/greenfield-js-sdk/pull/282) + [`8f4e206`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8f4e20671b30befd4b6ede4807b1b1bacb190626) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Create bucket quota params + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Changet auth type: `OffChainAuth` -> `EDDSA`, + `V1` -> `ECDSA` + +- [#235](https://github.com/bnb-chain/greenfield-js-sdk/pull/235) + [`53fa253`](https://github.com/bnb-chain/greenfield-js-sdk/commit/53fa253423a47ef190457eec980c895a1d035ab0) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Feegrant add expiration time + +- [#248](https://github.com/bnb-chain/greenfield-js-sdk/pull/248) + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Migrate bucket + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`faf5d47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/faf5d474d626010ef9a4d83bf96491b66d265d35) + Thanks [@rrr523](https://github.com/rrr523)! - feat: `ListBucketsByIds` api + +- [#270](https://github.com/bnb-chain/greenfield-js-sdk/pull/270) + [`a3e6b47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e6b472329e32fe86f7dd8a770d39a371a034cc) + Thanks [@rrr523](https://github.com/rrr523)! - feat: New API getObjectPreviewUrl + +- [#287](https://github.com/bnb-chain/greenfield-js-sdk/pull/287) + [`402da22`](https://github.com/bnb-chain/greenfield-js-sdk/commit/402da22ea101f855a05f098b8970eef13b045ead) + Thanks [@rrr523](https://github.com/rrr523)! - feat: convert XML enum to number + +- [#210](https://github.com/bnb-chain/greenfield-js-sdk/pull/210) + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Payment disableRefund api addr + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`fcf6283`](https://github.com/bnb-chain/greenfield-js-sdk/commit/fcf62832288fa3a67b552d69e321d73690cbc87e) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add listUserGroups + +- [#252](https://github.com/bnb-chain/greenfield-js-sdk/pull/252) + [`dba9871`](https://github.com/bnb-chain/greenfield-js-sdk/commit/dba987145172ddff7c7e363c144861a3f33ec999) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Gap time + +- [#283](https://github.com/bnb-chain/greenfield-js-sdk/pull/283) + [`1badb9c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1badb9c9dd71a7a39025e90adfe9a6feace75936) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Replace xml2js for universal usage browser + and nodejs + +- [#240](https://github.com/bnb-chain/greenfield-js-sdk/pull/240) + [`db61ff6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/db61ff671f7ccc1be9ed6b1a229596916e1ae5ab) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Create Bucket add Payment address + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`7d69f2e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7d69f2e3d044fc5f4c50d5251f99d8e04fb39b86) + Thanks [@rrr523](https://github.com/rrr523)! - feat: ListGroupsMembers + +- [#263](https://github.com/bnb-chain/greenfield-js-sdk/pull/263) + [`352419e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/352419e2016e6643fe7223adfe2cc1c3a6ee7ba6) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Cross Headers + +- [#275](https://github.com/bnb-chain/greenfield-js-sdk/pull/275) + [`55f8851`](https://github.com/bnb-chain/greenfield-js-sdk/commit/55f8851a659a178c1ab37473f0c76f9a374a0368) + Thanks [@rrr523](https://github.com/rrr523)! - fix: XML boolean parse + +- [#238](https://github.com/bnb-chain/greenfield-js-sdk/pull/238) + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db) + Thanks [@rrr523](https://github.com/rrr523)! - feat: ReadQuota + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`0084053`](https://github.com/bnb-chain/greenfield-js-sdk/commit/00840538a95e830752dd0ea07f52990303dccf95) + Thanks [@rrr523](https://github.com/rrr523)! - feat: ListGroup -> ListGroups + +- [#232](https://github.com/bnb-chain/greenfield-js-sdk/pull/232) + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Feegrant api add timestamp + +- [#284](https://github.com/bnb-chain/greenfield-js-sdk/pull/284) + [`c28ab8b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c28ab8b283f9aca37633c761527706f780bd13f5) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Update Bucket Info + +- [#292](https://github.com/bnb-chain/greenfield-js-sdk/pull/292) + [`7869b75`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7869b75fa16ef8f71daad0a00b0d10ba7fb13fb3) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Sort query + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`9710b5b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/9710b5bd294484c8a8021193f649b006481e9cd5) + Thanks [@rrr523](https://github.com/rrr523)! - feat: `ListObjectByIds` api + +- [#255](https://github.com/bnb-chain/greenfield-js-sdk/pull/255) + [`ce00ce1`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ce00ce1779c2ef3c627fb7172600093e6b580d23) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Refactor Sp client and supply custom http + request for meteInfo + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Upgrade types and update SP API: + `getQueryGlobalSpStorePriceByTime` `getQuerySpStoragePrice` + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`ea31d73`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ea31d738124058d75b251d6a897f924a122b1471) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add `ListBucketReadRecords` api + +- [#217](https://github.com/bnb-chain/greenfield-js-sdk/pull/217) + [`8d08848`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8d08848490d98fb774a8542d226647e8e5d65652) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Dynamic add and delete members string array + +- [#277](https://github.com/bnb-chain/greenfield-js-sdk/pull/277) + [`ff112b2`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ff112b2403b3e9826acb120cf42203996bf97872) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Common Prefix XML parse as array + +- [#261](https://github.com/bnb-chain/greenfield-js-sdk/pull/261) + [`21c9f6e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/21c9f6e2daa434a3d1385a845a0879750559ffbc) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Bucket XML type + +- [#264](https://github.com/bnb-chain/greenfield-js-sdk/pull/264) + [`7824130`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7824130027ca00254b94b0a8af61330551c639ef) + Thanks [@rrr523](https://github.com/rrr523)! - fix: EncodePath + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: `getCreateBucketApproval` and `createBucket` + add `authType` params + +- [#210](https://github.com/bnb-chain/greenfield-js-sdk/pull/210) + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add payment API + +- [#232](https://github.com/bnb-chain/greenfield-js-sdk/pull/232) + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Compatibility new payment api + +- [#280](https://github.com/bnb-chain/greenfield-js-sdk/pull/280) + [`c92e07e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c92e07e725accece3e1b126ea2ef0d4c6e5db431) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Migrate bucket + +- [#250](https://github.com/bnb-chain/greenfield-js-sdk/pull/250) + [`2e86b92`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e86b92a0762d0701356d24b7ad774364c039e3c) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add XML default value when null + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`12bd37c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/12bd37ccdefe789660409882c7045e2cf2a60ff6) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add `verifyPermission` api + +- [#238](https://github.com/bnb-chain/greenfield-js-sdk/pull/238) + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db) + Thanks [@rrr523](https://github.com/rrr523)! - fix: CreateBucketApproval + +- [#245](https://github.com/bnb-chain/greenfield-js-sdk/pull/245) + [`8782a21`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8782a21a9dcae1f9b83062877ff92540534fa40c) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Export types + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`ad1d7d3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ad1d7d3fa54fae50aed1b4e1d45a152e2e3fb08c) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add `listUserOwnedGroups` api + +- [#273](https://github.com/bnb-chain/greenfield-js-sdk/pull/273) + [`25ccbb6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/25ccbb6612b86d8037cec4b9ca3a9b141e10cc66) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Sp API add optional endpoint param + +- [#210](https://github.com/bnb-chain/greenfield-js-sdk/pull/210) + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Create Payment Account eip712 struct + +- [#257](https://github.com/bnb-chain/greenfield-js-sdk/pull/257) + [`569445b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/569445b218a156205172a15ce7aeb2d598b83f76) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Custom HTTP method with headers and options + +- [#213](https://github.com/bnb-chain/greenfield-js-sdk/pull/213) + [`e586e77`](https://github.com/bnb-chain/greenfield-js-sdk/commit/e586e7738e42581a9103e6157caad558896a8c63) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Return Types + +- [#272](https://github.com/bnb-chain/greenfield-js-sdk/pull/272) + [`b3683b8`](https://github.com/bnb-chain/greenfield-js-sdk/commit/b3683b8ce7e56a96c970964b2ade500eb95ef298) + Thanks [@rrr523](https://github.com/rrr523)! - fix: XML response from sp align to go struct + +- [#286](https://github.com/bnb-chain/greenfield-js-sdk/pull/286) + [`c7de1fc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c7de1fc86211e879dcdf4c420e3f7363fa1e36a5) + Thanks [@rrr523](https://github.com/rrr523)! - fix: EncodePath function handling non-English chars + +- [#212](https://github.com/bnb-chain/greenfield-js-sdk/pull/212) + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Zk crypto package + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: `getCreateObjectApproval` and `createObject` + add `authType` params + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Group Exist API: `queryGroupMembersExist` + `queryGroupExist` `queryGroupsExistById` + +- [#221](https://github.com/bnb-chain/greenfield-js-sdk/pull/221) + [`8ba4cc7`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8ba4cc735cbd72b07d8d0a9fadae2c4bda1dea53) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Zk crypto version + +- [#230](https://github.com/bnb-chain/greenfield-js-sdk/pull/230) + [`48521b3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/48521b39f1b1ab5bb806fbe525ca1cd4437b3cbc) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Error try catch + +- [#243](https://github.com/bnb-chain/greenfield-js-sdk/pull/243) + [`cbbb4aa`](https://github.com/bnb-chain/greenfield-js-sdk/commit/cbbb4aa2d0f1e76b9f367b95355590370d35d684) + Thanks [@rrr523](https://github.com/rrr523)! - fix: XML force convert array not object + +- [#248](https://github.com/bnb-chain/greenfield-js-sdk/pull/248) + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Export SpClient + +- [#247](https://github.com/bnb-chain/greenfield-js-sdk/pull/247) + [`d388940`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d388940e821e7fc3cb9671ffae4753d63a3189b9) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Hex don't convert number + +- Updated dependencies + [[`2715b17`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2715b171dd8b0984e2515e7c77975ffc15e258f8), + [`d0da019`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d0da0190216e5914e08cc8555be7375e72095b48), + [`8ba4cc7`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8ba4cc735cbd72b07d8d0a9fadae2c4bda1dea53), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee), + [`8d08848`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8d08848490d98fb774a8542d226647e8e5d65652)]: + - @bnb-chain/greenfield-zk-crypto@0.0.2 + +## 0.2.4-alpha.28 + +### Patch Changes + +- [#292](https://github.com/bnb-chain/greenfield-js-sdk/pull/292) + [`7869b75`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7869b75fa16ef8f71daad0a00b0d10ba7fb13fb3) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Sort query + +## 0.2.4-alpha.27 + +### Patch Changes + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`faf5d47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/faf5d474d626010ef9a4d83bf96491b66d265d35) + Thanks [@rrr523](https://github.com/rrr523)! - feat: `ListBucketsByIds` api + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`fcf6283`](https://github.com/bnb-chain/greenfield-js-sdk/commit/fcf62832288fa3a67b552d69e321d73690cbc87e) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add listUserGroups + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`7d69f2e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7d69f2e3d044fc5f4c50d5251f99d8e04fb39b86) + Thanks [@rrr523](https://github.com/rrr523)! - feat: ListGroupsMembers + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`0084053`](https://github.com/bnb-chain/greenfield-js-sdk/commit/00840538a95e830752dd0ea07f52990303dccf95) + Thanks [@rrr523](https://github.com/rrr523)! - feat: ListGroup -> ListGroups + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`9710b5b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/9710b5bd294484c8a8021193f649b006481e9cd5) + Thanks [@rrr523](https://github.com/rrr523)! - feat: `ListObjectByIds` api + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`ea31d73`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ea31d738124058d75b251d6a897f924a122b1471) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add `ListBucketReadRecords` api + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`12bd37c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/12bd37ccdefe789660409882c7045e2cf2a60ff6) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add `verifyPermission` api + +- [#290](https://github.com/bnb-chain/greenfield-js-sdk/pull/290) + [`ad1d7d3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ad1d7d3fa54fae50aed1b4e1d45a152e2e3fb08c) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add `listUserOwnedGroups` api + +## 0.2.4-alpha.26 + +### Patch Changes + +- [#287](https://github.com/bnb-chain/greenfield-js-sdk/pull/287) + [`402da22`](https://github.com/bnb-chain/greenfield-js-sdk/commit/402da22ea101f855a05f098b8970eef13b045ead) + Thanks [@rrr523](https://github.com/rrr523)! - feat: convert XML enum to number + +- [#286](https://github.com/bnb-chain/greenfield-js-sdk/pull/286) + [`c7de1fc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c7de1fc86211e879dcdf4c420e3f7363fa1e36a5) + Thanks [@rrr523](https://github.com/rrr523)! - fix: EncodePath function handling non-English chars + +## 0.2.4-alpha.25 + +### Patch Changes + +- [#283](https://github.com/bnb-chain/greenfield-js-sdk/pull/283) + [`1badb9c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1badb9c9dd71a7a39025e90adfe9a6feace75936) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Replace xml2js for universal usage browser + and nodejs + +- [#284](https://github.com/bnb-chain/greenfield-js-sdk/pull/284) + [`c28ab8b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c28ab8b283f9aca37633c761527706f780bd13f5) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Update Bucket Info + +## 0.2.4-alpha.24 + +### Patch Changes + +- [#282](https://github.com/bnb-chain/greenfield-js-sdk/pull/282) + [`8f4e206`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8f4e20671b30befd4b6ede4807b1b1bacb190626) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Create bucket quota params + +- [#280](https://github.com/bnb-chain/greenfield-js-sdk/pull/280) + [`c92e07e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c92e07e725accece3e1b126ea2ef0d4c6e5db431) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Migrate bucket + +## 0.2.4-alpha.23 + +### Patch Changes + +- [#277](https://github.com/bnb-chain/greenfield-js-sdk/pull/277) + [`ff112b2`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ff112b2403b3e9826acb120cf42203996bf97872) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Common Prefix XML parse as array + +## 0.2.4-alpha.22 + +### Patch Changes + +- [#275](https://github.com/bnb-chain/greenfield-js-sdk/pull/275) + [`55f8851`](https://github.com/bnb-chain/greenfield-js-sdk/commit/55f8851a659a178c1ab37473f0c76f9a374a0368) + Thanks [@rrr523](https://github.com/rrr523)! - fix: XML boolean parse + +## 0.2.4-alpha.21 + +### Patch Changes + +- [#273](https://github.com/bnb-chain/greenfield-js-sdk/pull/273) + [`25ccbb6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/25ccbb6612b86d8037cec4b9ca3a9b141e10cc66) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Sp API add optional endpoint param + +- [#272](https://github.com/bnb-chain/greenfield-js-sdk/pull/272) + [`b3683b8`](https://github.com/bnb-chain/greenfield-js-sdk/commit/b3683b8ce7e56a96c970964b2ade500eb95ef298) + Thanks [@rrr523](https://github.com/rrr523)! - fix: XML response from sp align to go struct + +## 0.2.4-alpha.20 + +### Patch Changes + +- [#270](https://github.com/bnb-chain/greenfield-js-sdk/pull/270) + [`a3e6b47`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e6b472329e32fe86f7dd8a770d39a371a034cc) + Thanks [@rrr523](https://github.com/rrr523)! - feat: New API getObjectPreviewUrl + +## 0.2.4-alpha.19 + +### Patch Changes + +- [#267](https://github.com/bnb-chain/greenfield-js-sdk/pull/267) + [`a485367`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a485367f23175c4004a7f030a1a4081c65d0d632) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Download HTTP method + +## 0.2.4-alpha.18 + +### Patch Changes + +- [#264](https://github.com/bnb-chain/greenfield-js-sdk/pull/264) + [`7824130`](https://github.com/bnb-chain/greenfield-js-sdk/commit/7824130027ca00254b94b0a8af61330551c639ef) + Thanks [@rrr523](https://github.com/rrr523)! - fix: EncodePath + +## 0.2.4-alpha.17 + +### Patch Changes + +- [#263](https://github.com/bnb-chain/greenfield-js-sdk/pull/263) + [`352419e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/352419e2016e6643fe7223adfe2cc1c3a6ee7ba6) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Cross Headers + +- [#261](https://github.com/bnb-chain/greenfield-js-sdk/pull/261) + [`21c9f6e`](https://github.com/bnb-chain/greenfield-js-sdk/commit/21c9f6e2daa434a3d1385a845a0879750559ffbc) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Bucket XML type + +## 0.2.4-alpha.16 + +### Patch Changes + +- [#257](https://github.com/bnb-chain/greenfield-js-sdk/pull/257) + [`569445b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/569445b218a156205172a15ce7aeb2d598b83f76) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Custom HTTP method with headers and options + +## 0.2.4-alpha.15 + +### Patch Changes + +- [#255](https://github.com/bnb-chain/greenfield-js-sdk/pull/255) + [`ce00ce1`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ce00ce1779c2ef3c627fb7172600093e6b580d23) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Refactor Sp client and supply custom http + request for meteInfo + +## 0.2.4-alpha.14 + +### Patch Changes + +- [#254](https://github.com/bnb-chain/greenfield-js-sdk/pull/254) + [`1ede2ea`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1ede2ea0ecc9ffc35d262a43a007221f06df3a0e) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add GetBucketMeta and GetObjectMeta + +- [#252](https://github.com/bnb-chain/greenfield-js-sdk/pull/252) + [`dba9871`](https://github.com/bnb-chain/greenfield-js-sdk/commit/dba987145172ddff7c7e363c144861a3f33ec999) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Gap time + +## 0.2.4-alpha.13 + +### Patch Changes + +- [#250](https://github.com/bnb-chain/greenfield-js-sdk/pull/250) + [`2e86b92`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e86b92a0762d0701356d24b7ad774364c039e3c) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add XML default value when null + +## 0.2.4-alpha.12 + +### Patch Changes + +- [#248](https://github.com/bnb-chain/greenfield-js-sdk/pull/248) + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Upload Object add AuthType + +- [#248](https://github.com/bnb-chain/greenfield-js-sdk/pull/248) + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7) + Thanks [@rrr523](https://github.com/rrr523)! - feat: download s3 object + +- [#248](https://github.com/bnb-chain/greenfield-js-sdk/pull/248) + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Migrate bucket + +- [#248](https://github.com/bnb-chain/greenfield-js-sdk/pull/248) + [`a3e2210`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a3e2210805780a691f796caca32295ed8f8903a7) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Export SpClient + +## 0.2.4-alpha.11 + +### Patch Changes + +- [#245](https://github.com/bnb-chain/greenfield-js-sdk/pull/245) + [`8782a21`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8782a21a9dcae1f9b83062877ff92540534fa40c) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Export types + +- [#247](https://github.com/bnb-chain/greenfield-js-sdk/pull/247) + [`d388940`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d388940e821e7fc3cb9671ffae4753d63a3189b9) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Hex don't convert number + +## 0.2.4-alpha.10 + +### Patch Changes + +- [#243](https://github.com/bnb-chain/greenfield-js-sdk/pull/243) + [`cbbb4aa`](https://github.com/bnb-chain/greenfield-js-sdk/commit/cbbb4aa2d0f1e76b9f367b95355590370d35d684) + Thanks [@rrr523](https://github.com/rrr523)! - fix: XML force convert array not object + +## 0.2.4-alpha.9 + +### Patch Changes + +- [#240](https://github.com/bnb-chain/greenfield-js-sdk/pull/240) + [`db61ff6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/db61ff671f7ccc1be9ed6b1a229596916e1ae5ab) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Create Bucket add Payment address + +## 0.2.4-alpha.8 + +### Patch Changes + +- [#238](https://github.com/bnb-chain/greenfield-js-sdk/pull/238) + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db) + Thanks [@rrr523](https://github.com/rrr523)! - feat: ReadQuota + +- [#238](https://github.com/bnb-chain/greenfield-js-sdk/pull/238) + [`a823e61`](https://github.com/bnb-chain/greenfield-js-sdk/commit/a823e615266973639ff3101e6bec7e3cf88891db) + Thanks [@rrr523](https://github.com/rrr523)! - fix: CreateBucketApproval + +## 0.2.4-alpha.7 + +### Patch Changes + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - refactor: Tx + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Changet auth type: `OffChainAuth` -> `EDDSA`, + `V1` -> `ECDSA` + +- [#235](https://github.com/bnb-chain/greenfield-js-sdk/pull/235) + [`53fa253`](https://github.com/bnb-chain/greenfield-js-sdk/commit/53fa253423a47ef190457eec980c895a1d035ab0) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Feegrant add expiration time + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Upgrade types and update SP API: + `getQueryGlobalSpStorePriceByTime` `getQuerySpStoragePrice` + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: `getCreateBucketApproval` and `createBucket` + add `authType` params + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: `getCreateObjectApproval` and `createObject` + add `authType` params + +- [#236](https://github.com/bnb-chain/greenfield-js-sdk/pull/236) + [`41581f4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/41581f4e684ff7a3e6738e3477e295968af45b4a) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Group Exist API: `queryGroupMembersExist` + `queryGroupExist` `queryGroupsExistById` + +## 0.2.4-alpha.6 + +### Patch Changes + +- [#232](https://github.com/bnb-chain/greenfield-js-sdk/pull/232) + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Compatibility new `updateGroupMember` api + +- [#232](https://github.com/bnb-chain/greenfield-js-sdk/pull/232) + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Feegrant api add timestamp + +- [#232](https://github.com/bnb-chain/greenfield-js-sdk/pull/232) + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Compatibility new payment api + +- Updated dependencies + [[`d0da019`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d0da0190216e5914e08cc8555be7375e72095b48), + [`4b9b4bc`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4b9b4bc44063aee49f91c285f327819283bd6cee)]: + - @bnb-chain/greenfield-zk-crypto@0.0.2-alpha.4 + +## 0.2.4-alpha.5 + +### Patch Changes + +- [#230](https://github.com/bnb-chain/greenfield-js-sdk/pull/230) + [`48521b3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/48521b39f1b1ab5bb806fbe525ca1cd4437b3cbc) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Error try catch + +## 0.2.4-alpha.4 + +### Patch Changes + +- [#223](https://github.com/bnb-chain/greenfield-js-sdk/pull/223) + [`2e44e95`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2e44e9548712739c33cc98d82bfada1a06ef9472) + Thanks [@rrr523](https://github.com/rrr523)! - fix: DeepClone EIP712 + +## 0.2.4-alpha.3 + +### Patch Changes + +- [#221](https://github.com/bnb-chain/greenfield-js-sdk/pull/221) + [`8ba4cc7`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8ba4cc735cbd72b07d8d0a9fadae2c4bda1dea53) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Zk crypto version + +- Updated dependencies + [[`8ba4cc7`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8ba4cc735cbd72b07d8d0a9fadae2c4bda1dea53)]: + - @bnb-chain/greenfield-zk-crypto@0.0.2-alpha.3 + +## 0.2.4-alpha.2 + +### Patch Changes + +- [#217](https://github.com/bnb-chain/greenfield-js-sdk/pull/217) + [`8d08848`](https://github.com/bnb-chain/greenfield-js-sdk/commit/8d08848490d98fb774a8542d226647e8e5d65652) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Dynamic add and delete members string array + +## 0.2.4-alpha.1 + +### Patch Changes + +- [#213](https://github.com/bnb-chain/greenfield-js-sdk/pull/213) + [`e586e77`](https://github.com/bnb-chain/greenfield-js-sdk/commit/e586e7738e42581a9103e6157caad558896a8c63) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Return Types + +- [#212](https://github.com/bnb-chain/greenfield-js-sdk/pull/212) + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Zk crypto package + +- Updated dependencies + [[`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d)]: + - @bnb-chain/zk-crypto@0.0.2-alpha.0 + +## 0.2.4-alpha.0 + +### Patch Changes + +- [#210](https://github.com/bnb-chain/greenfield-js-sdk/pull/210) + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Payment disableRefund api addr + +- [#210](https://github.com/bnb-chain/greenfield-js-sdk/pull/210) + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add payment API + +- [#210](https://github.com/bnb-chain/greenfield-js-sdk/pull/210) + [`3e0abf3`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3e0abf34395a121e941bc3f378b01e7391a64e28) + Thanks [@rrr523](https://github.com/rrr523)! - fix: Create Payment Account eip712 struct + ## 0.2.3 ### Patch Changes @@ -402,6 +1021,245 @@ ### Patch Changes +- [#129](https://github.com/bnb-chain/greenfield-js-sdk/pull/129) + [`16d8860`](https://github.com/bnb-chain/greenfield-js-sdk/commit/16d8860c4fe012ac7e8e58791b0a2e5e06ee2b97) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add Gashub API + +## 0.2.2-alpha.18 + +### Patch Changes + +- [#202](https://github.com/bnb-chain/greenfield-js-sdk/pull/202) + [`f790f26`](https://github.com/bnb-chain/greenfield-js-sdk/commit/f790f261a1b056494a87bd955e7103bcf68c4f54) + Thanks [@randomx999](https://github.com/randomx999)! - feat: Extend response time to 3s + +## 0.2.2-alpha.17 + +### Patch Changes + +- [#200](https://github.com/bnb-chain/greenfield-js-sdk/pull/200) + [`61c7a9c`](https://github.com/bnb-chain/greenfield-js-sdk/commit/61c7a9ca56f03c6a8994b0dc22857777fe7fac46) + Thanks [@randomx999](https://github.com/randomx999)! - fix: No sp available error + +- [#198](https://github.com/bnb-chain/greenfield-js-sdk/pull/198) + [`c3af372`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c3af3729ecc69b65e2af12773028fcdb956bb827) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Upload object V1 auth + +- [#198](https://github.com/bnb-chain/greenfield-js-sdk/pull/198) + [`c3af372`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c3af3729ecc69b65e2af12773028fcdb956bb827) + Thanks [@rrr523](https://github.com/rrr523)! - fix: CreateObject Tx content type field + +- [#198](https://github.com/bnb-chain/greenfield-js-sdk/pull/198) + [`c3af372`](https://github.com/bnb-chain/greenfield-js-sdk/commit/c3af3729ecc69b65e2af12773028fcdb956bb827) + Thanks [@rrr523](https://github.com/rrr523)! - refactor: Replace @ethereumjs/util with + @ethersproject/bytes + +## 0.2.2-alpha.16 + +### Patch Changes + +- [`b483901`](https://github.com/bnb-chain/greenfield-js-sdk/commit/b483901d95048250d4b12e6ea34efd43550d9109) + Thanks [@randomx999](https://github.com/randomx999)! - fix: Throw sp error code and message + +- [#195](https://github.com/bnb-chain/greenfield-js-sdk/pull/195) + [`1880084`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1880084ac21c17c73c553ce4256dc4e8dca24d5b) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Multi tx support private key + +## 0.2.2-alpha.15 + +### Patch Changes + +- [#193](https://github.com/bnb-chain/greenfield-js-sdk/pull/193) + [`776003d`](https://github.com/bnb-chain/greenfield-js-sdk/commit/776003d7830355094c92cb09e266e0d163123d0b) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Change allowance value type as base64 + +## 0.2.2-alpha.14 + +### Patch Changes + +- [#191](https://github.com/bnb-chain/greenfield-js-sdk/pull/191) + [`1d9808f`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1d9808f54b3bb2d934e09f34a2f49b0a644a12f1) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Remove Create object and create bucket + +- [#191](https://github.com/bnb-chain/greenfield-js-sdk/pull/191) + [`1d9808f`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1d9808f54b3bb2d934e09f34a2f49b0a644a12f1) + Thanks [@rrr523](https://github.com/rrr523)! - feat: getStorageProviderInfo params update + +- [#191](https://github.com/bnb-chain/greenfield-js-sdk/pull/191) + [`1d9808f`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1d9808f54b3bb2d934e09f34a2f49b0a644a12f1) + Thanks [@rrr523](https://github.com/rrr523)! - feat: GetSpURLfrom bucket + +## 0.2.2-alpha.13 + +### Patch Changes + +- [#189](https://github.com/bnb-chain/greenfield-js-sdk/pull/189) + [`db5ab13`](https://github.com/bnb-chain/greenfield-js-sdk/commit/db5ab1363fbb5ff50e6c35f195a9b0a30a8375df) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Replace V2 with V1 auth for create object sp + api + +## 0.2.2-alpha.12 + +### Patch Changes + +- [#187](https://github.com/bnb-chain/greenfield-js-sdk/pull/187) + [`1b75dd2`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1b75dd2db01d99b078243619bd8c3d08b8e21f3d) + Thanks [@randomx999](https://github.com/randomx999)! - feat: Offchainauth timeout reduced to 2s + +## 0.2.2-alpha.11 + +### Patch Changes + +- [#186](https://github.com/bnb-chain/greenfield-js-sdk/pull/186) + [`42e323a`](https://github.com/bnb-chain/greenfield-js-sdk/commit/42e323a2c14d759aef675864a6ce11ef73d146ca) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Remove listObjects api auth and update + response type + +- [#186](https://github.com/bnb-chain/greenfield-js-sdk/pull/186) + [`42e323a`](https://github.com/bnb-chain/greenfield-js-sdk/commit/42e323a2c14d759aef675864a6ce11ef73d146ca) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Update BucketProps type + +- [#186](https://github.com/bnb-chain/greenfield-js-sdk/pull/186) + [`42e323a`](https://github.com/bnb-chain/greenfield-js-sdk/commit/42e323a2c14d759aef675864a6ce11ef73d146ca) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Remove listGroup api auth + +- [#186](https://github.com/bnb-chain/greenfield-js-sdk/pull/186) + [`42e323a`](https://github.com/bnb-chain/greenfield-js-sdk/commit/42e323a2c14d759aef675864a6ce11ef73d146ca) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Remove getUserBuckets auth + +- [#182](https://github.com/bnb-chain/greenfield-js-sdk/pull/182) + [`1b803da`](https://github.com/bnb-chain/greenfield-js-sdk/commit/1b803da579469ffab9fa670973fd1414dfa88d2d) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add AuthV1 for SP API (createObject) + +## 0.2.2-alpha.10 + +### Patch Changes + +- [#171](https://github.com/bnb-chain/greenfield-js-sdk/pull/171) + [`e2ebac6`](https://github.com/bnb-chain/greenfield-js-sdk/commit/e2ebac6470c883013ff7dfaf610649b5e38f47bd) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Feegrant `grantAllowance` API + +- [#169](https://github.com/bnb-chain/greenfield-js-sdk/pull/169) + [`d894bad`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d894badd5fcd5ce915dfaec21808a300c15e7783) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add virtual group api + +- [#179](https://github.com/bnb-chain/greenfield-js-sdk/pull/179) + [`dc87783`](https://github.com/bnb-chain/greenfield-js-sdk/commit/dc877833ed400166fd1384b315c9ac068d6a80f5) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Add `getObjectPolicy` and + `isObjectPermissionAllowed` + +- [#169](https://github.com/bnb-chain/greenfield-js-sdk/pull/169) + [`d894bad`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d894badd5fcd5ce915dfaec21808a300c15e7783) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Migrate bucket api + +- [#174](https://github.com/bnb-chain/greenfield-js-sdk/pull/174) + [`4d88665`](https://github.com/bnb-chain/greenfield-js-sdk/commit/4d8866501f1693ebe2593c51fc9c74bd728fba70) + Thanks [@rrr523](https://github.com/rrr523)! - feat: AuthInfoBytes add feePayer and feeGranter + +- [#169](https://github.com/bnb-chain/greenfield-js-sdk/pull/169) + [`d894bad`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d894badd5fcd5ce915dfaec21808a300c15e7783) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Compatibility createBucket and createObject + API for QA net new protobuf + +## 0.2.2-alpha.9 + +### Patch Changes + +- [#167](https://github.com/bnb-chain/greenfield-js-sdk/pull/167) + [`3907fc4`](https://github.com/bnb-chain/greenfield-js-sdk/commit/3907fc4ce5f7f5888583b737c46e65bf828fd779) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Upgrade types version to 0.4.0-alpha.16 + +## 0.2.2-alpha.8 + +### Patch Changes + +- [#164](https://github.com/bnb-chain/greenfield-js-sdk/pull/164) + [`f248937`](https://github.com/bnb-chain/greenfield-js-sdk/commit/f248937d3cfb97b4e2085017436aa25bc7fc6c40) + Thanks [@randomx999](https://github.com/randomx999)! - fix: Return real statuscode when call + metaservice + +## 0.2.2-alpha.7 + +### Patch Changes + +- [#149](https://github.com/bnb-chain/greenfield-js-sdk/pull/149) + [`b050481`](https://github.com/bnb-chain/greenfield-js-sdk/commit/b05048118d68abeff0939e09aad21ed6a22c34d3) + Thanks [@aiden-cao](https://github.com/aiden-cao)! - feat: bucket getApproval replace `return` + with `throw` + +## 0.2.2-alpha.6 + +### Patch Changes + +- [#147](https://github.com/bnb-chain/greenfield-js-sdk/pull/147) + [`d15ef5d`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d15ef5d37ae8d3b340b89645a0fb26c5192d14f3) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Query Lock Fee API + +## 0.2.2-alpha.5 + +### Patch Changes + +- [#143](https://github.com/bnb-chain/greenfield-js-sdk/pull/143) + [`834b71b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/834b71b824b42817194f9fa698102be2e1c94be1) + Thanks [@rrr523](https://github.com/rrr523)! - Revert "feat: Split approval and simulate" + +- [#143](https://github.com/bnb-chain/greenfield-js-sdk/pull/143) + [`834b71b`](https://github.com/bnb-chain/greenfield-js-sdk/commit/834b71b824b42817194f9fa698102be2e1c94be1) + Thanks [@rrr523](https://github.com/rrr523)! - revert: create bucket and object need approval + +## 0.2.2-alpha.4 + +### Patch Changes + +- [#141](https://github.com/bnb-chain/greenfield-js-sdk/pull/141) + [`07c143f`](https://github.com/bnb-chain/greenfield-js-sdk/commit/07c143f245a1c44b821a2bc75ef9877da3df5d2f) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Split approval and simulate + +## 0.2.2-alpha.3 + +### Patch Changes + +- [#139](https://github.com/bnb-chain/greenfield-js-sdk/pull/139) + [`2b00e73`](https://github.com/bnb-chain/greenfield-js-sdk/commit/2b00e7384e62389feffdd224584d0de1db277ee9) + Thanks [@rrr523](https://github.com/rrr523)! - bump version + +## 0.2.1-alpha.10 + +### Patch Changes + +- [#137](https://github.com/bnb-chain/greenfield-js-sdk/pull/137) + [`57a8176`](https://github.com/bnb-chain/greenfield-js-sdk/commit/57a8176be1f8a816003db405e7c07f73170db804) + Thanks [@rrr523](https://github.com/rrr523)! - Sort EIP712 message field if send multi messages + +## 0.2.2-alpha.2 + +### Patch Changes + +- [#134](https://github.com/bnb-chain/greenfield-js-sdk/pull/134) + [`535e9b0`](https://github.com/bnb-chain/greenfield-js-sdk/commit/535e9b06674102ad197532110f018ebe3310eabd) + Thanks [@rrr523](https://github.com/rrr523)! - chore: isomorphic-fetch + +- [#136](https://github.com/bnb-chain/greenfield-js-sdk/pull/136) + [`d471489`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d47148950e7e82fb0c8f952a6f573f2ae1409298) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Sync Types greenfield-cosmos-sdk + v0.2.3-alpha.1 and greendfield v0.2.3-alpha.2 + +## 0.2.2-alpha.1 + +### Patch Changes + +- [#131](https://github.com/bnb-chain/greenfield-js-sdk/pull/131) + [`82082a9`](https://github.com/bnb-chain/greenfield-js-sdk/commit/82082a90352ba60193e8c109f82782fff14c59f9) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Export queryClient + +- [#133](https://github.com/bnb-chain/greenfield-js-sdk/pull/133) + [`d8361be`](https://github.com/bnb-chain/greenfield-js-sdk/commit/d8361be80834c76b1522d312f4b7607a2b09b7fb) + Thanks [@randomx999](https://github.com/randomx999)! - fix: Compatiable net error + +## 0.2.2-alpha.0 + +### Patch Changes + - [#129](https://github.com/bnb-chain/greenfield-js-sdk/pull/129) [`16d8860`](https://github.com/bnb-chain/greenfield-js-sdk/commit/16d8860c4fe012ac7e8e58791b0a2e5e06ee2b97) Thanks [@rrr523](https://github.com/rrr523)! - feat: Add Gashub API diff --git a/packages/chain-sdk/README.md b/packages/chain-sdk/README.md index c5294e54..ff716194 100644 --- a/packages/chain-sdk/README.md +++ b/packages/chain-sdk/README.md @@ -6,21 +6,36 @@ npm install @bnb-chain/greenfield-js-sdk ``` -## Usage - -### create client +## Create Client ```js import {Client} from '@bnb-chain/greenfield-js-sdk' + +// Node.js const client = Client.create(GRPC_URL, GREEN_CHAIN_ID); + +// Browser +Client.create(GRPC_URL, String(GREEN_CHAIN_ID), { + zkCryptoUrl: + 'https://unpkg.com/@bnb-chain/greenfield-zk-crypto@0.0.2-alpha.4/dist/node/zk-crypto.wasm', +}); ``` -Apis include transactions and queries. +> Browser need load wasm manually. + +## Usage + +The SDK consists of two parts: + +* Chain: https://docs.bnbchain.org/greenfield-docs/docs/api/blockchain-rest +* Storage Provider: https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest + +## Chain ### Tx #### 1. Tx construction -take `transfer` for example: +`transfer` tx for example: ```js const transferTx = await client.account.transfer({ @@ -57,14 +72,16 @@ const broadcastRes = await transferTx.broadcast({ }); ``` +#### NOTICE: Signature mode for `Broadcast` + `broadcast` use `window.ethereum` as signature provider by default. If you want to use others, you can set `signTypedDataCallback`: ```js -// trustwallet +// trustwallet: const broadcastRes = await transferTx.broadcast({ - //... + // ... signTypedDataCallback: async (addr: string, message: string) => { return await window.trustwallet.request({ method: 'eth_signTypedData_v4', @@ -74,10 +91,10 @@ const broadcastRes = await transferTx.broadcast({ }); ``` -If you broadcast in Nodejs, you can set `privateKey`: +If you broadcast in Nodejs, you can broadcast a tx by `privateKey`: ```js const broadcastRes = await transferTx.broadcast({ - //... + // ... privateKey: '0x.......' }); ``` @@ -89,25 +106,61 @@ const broadcastRes = await transferTx.broadcast({ await client.account.getAccount(address); ``` +Examples: +* [Next.js](../../examples/nextjs/README.md) +* [Node.js](../../examples/nodejs/README.md) + +### Storage Provider Client -more API: +> https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest -* [account](./src/api/account.ts) -* [basic](./src/api/basic.ts) -* [backet](./src/api/backet.ts) -* [challenge](./src/api/challenge.ts) -* [object](./src/api/object.ts) -* [group](./src/api/group.ts) -* [payment](./src/api/payment.ts) -* [sp](./src/api/sp.ts) +SDK support two [authentication type](https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest#authentication-type): +* ECDSA: It is usually used on Node.js(Because it need to use a private key) +* EDDSA: It is usually used in a browser - diff --git a/packages/chain-sdk/package.json b/packages/chain-sdk/package.json index d8f4dea6..6a59239c 100644 --- a/packages/chain-sdk/package.json +++ b/packages/chain-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@bnb-chain/greenfield-js-sdk", - "version": "0.2.3", + "version": "0.2.4", "description": "greenfield js chain and sp sdk", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", @@ -53,7 +53,8 @@ ] }, "dependencies": { - "@bnb-chain/greenfield-cosmos-types": "0.4.0-alpha.18", + "@bnb-chain/greenfield-cosmos-types": "0.4.0-alpha.22", + "@bnb-chain/greenfield-zk-crypto": "workspace:*", "@cosmjs/proto-signing": "^0.30.1", "@cosmjs/stargate": "^0.30.1", "@cosmjs/tendermint-rpc": "^0.30.1", @@ -66,6 +67,8 @@ "dayjs": "^1.11.7", "dotenv": "^16.0.3", "ethereum-cryptography": "^2.0.0", + "fast-xml-parser": "^4.2.7", + "lodash.clonedeep": "^4.5.0", "lodash.mapvalues": "^4.6.0", "lodash.sortby": "^4.7.0", "long": "^5.2.1", @@ -75,9 +78,11 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@types/jest": "^29.5.1", + "@types/lodash.clonedeep": "^4.5.7", "@types/lodash.mapvalues": "^4.6.7", "@types/lodash.sortby": "^4.7.7", "@types/mime": "^3.0.1", + "@types/xml2js": "^0.4.11", "jest": "^29.5.0", "mime": "^3.0.0", "rollup": "^2.79.1", diff --git a/packages/chain-sdk/src/api/account.ts b/packages/chain-sdk/src/api/account.ts index 171ccb4c..37ea7c6f 100644 --- a/packages/chain-sdk/src/api/account.ts +++ b/packages/chain-sdk/src/api/account.ts @@ -12,9 +12,9 @@ import { } from '@bnb-chain/greenfield-cosmos-types/cosmos/bank/v1beta1/query'; import { MsgMultiSend, MsgSend } from '@bnb-chain/greenfield-cosmos-types/cosmos/bank/v1beta1/tx'; import { - QueryGetPaymentAccountRequest, - QueryGetPaymentAccountResponse, - QueryGetPaymentAccountsByOwnerResponse, + QueryPaymentAccountRequest, + QueryPaymentAccountResponse, + QueryPaymentAccountsByOwnerResponse, } from '@bnb-chain/greenfield-cosmos-types/greenfield/payment/query'; import { MsgCreatePaymentAccount } from '@bnb-chain/greenfield-cosmos-types/greenfield/payment/tx'; import { container, delay, inject, singleton } from 'tsyringe'; @@ -25,7 +25,7 @@ import { TxResponse, } from '..'; import { Basic } from './basic'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; export interface IAccount { /** @@ -41,9 +41,7 @@ export interface IAccount { /** * takes an address string as parameters and returns a pointer to a paymentTypes. */ - getPaymentAccount( - request: QueryGetPaymentAccountRequest, - ): Promise; + getPaymentAccount(request: QueryPaymentAccountRequest): Promise; getModuleAccounts(): Promise; @@ -52,7 +50,7 @@ export interface IAccount { /** * retrieves all payment accounts owned by the given address */ - getPaymentAccountsByOwner(owner: string): Promise; + getPaymentAccountsByOwner(owner: string): Promise; createPaymentAccount(msg: MsgCreatePaymentAccount): Promise; @@ -95,7 +93,7 @@ export class Account implements IAccount { public async getPaymentAccountsByOwner(owner: string) { const rpc = await this.queryClient.getPaymentQueryClient(); - return await rpc.GetPaymentAccountsByOwner({ + return await rpc.PaymentAccountsByOwner({ owner, }); } @@ -113,8 +111,8 @@ export class Account implements IAccount { } public async getPaymentAccount( - request: QueryGetPaymentAccountRequest, - ): Promise { + request: QueryPaymentAccountRequest, + ): Promise { const rpc = await this.queryClient.getPaymentQueryClient(); return await rpc.PaymentAccount(request); } diff --git a/packages/chain-sdk/src/api/basic.ts b/packages/chain-sdk/src/api/basic.ts index 4c69efa6..94ad4ee4 100644 --- a/packages/chain-sdk/src/api/basic.ts +++ b/packages/chain-sdk/src/api/basic.ts @@ -1,4 +1,4 @@ -import { getPubKeyByPriKey, signEIP712Data } from '@/keymanage'; +import { getPubKeyByPriKey } from '@/keymanage'; import { defaultSignTypedData } from '@/sign/signTx'; import { getGasFeeBySimulate } from '@/utils/units'; import { BaseAccount } from '@bnb-chain/greenfield-cosmos-types/cosmos/auth/v1beta1/auth'; @@ -27,7 +27,15 @@ import { arrayify } from '@ethersproject/bytes'; import { signTypedData, SignTypedDataVersion } from '@metamask/eth-sig-util'; import Long from 'long'; import { container, inject, singleton } from 'tsyringe'; -import { BroadcastOptions, ISimulateGasFee, MetaTxInfo, SimulateOptions, TxResponse } from '..'; +import { + BroadcastOptions, + ISimulateGasFee, + MetaTxInfo, + SignOptions, + SimulateOptions, + TxResponse, +} from '..'; +import { RpcQueryClient } from '../clients/queryclient'; import { DEFAULT_DENOM, ZERO_PUBKEY } from '../constants'; import { createEIP712, @@ -37,10 +45,9 @@ import { mergeMultiEip712, mergeMultiMessage, } from '../messages'; -import { generateMsg, typeWrapper } from '../messages/utils'; +import { generateMsg } from '../messages/utils'; import { eip712Hash, makeCosmsPubKey, recoverPk } from '../sign'; import { Account } from './account'; -import { RpcQueryClient } from './queryclient'; export interface IBasic { /** @@ -104,7 +111,7 @@ export interface IBasic { /** * */ - multiTx(txResList: TxResponse[]): Promise>; + multiTx(txResList: Pick[]): Promise>; } @singleton() @@ -167,32 +174,36 @@ export class Basic implements IBasic { MsgSDK: MetaTxInfo['MsgSDK'], msgBytes: MetaTxInfo['msgBytes'], ) { - const accountInfo = await this.account.getAccount(address); - const bodyBytes = this.getSingleBodyBytes(typeUrl, msgBytes); - - return { - simulate: async (opts: SimulateOptions) => { - return await this.simulateRawTx(bodyBytes, accountInfo, opts); + const txBodyBytes = this.getBodyBytes([ + { + typeUrl, + msgBytes, }, - broadcast: async (opts: BroadcastOptions) => { - const rawTxBytes = await this.getRawTxBytes( + ]); + + const tx = await this.multiTx([ + { + metaTxInfo: { typeUrl, + address, MsgSDKTypeEIP712, MsgSDK, - bodyBytes, - accountInfo, - opts, - ); - - return await this.broadcastRawTx(rawTxBytes); + msgBytes, + bodyBytes: txBodyBytes, + }, }, + ]); + + return { + simulate: tx.simulate, + broadcast: tx.broadcast, metaTxInfo: { typeUrl, address, MsgSDKTypeEIP712, MsgSDK, msgBytes, - bodyBytes, + bodyBytes: txBodyBytes, }, }; } @@ -236,16 +247,10 @@ export class Basic implements IBasic { return await client.broadcastTx(txRawBytes); } - public async multiTx(txResList: TxResponse[]) { + public async multiTx(txResList: Pick[]) { const txs = txResList.map((txRes) => txRes.metaTxInfo); const accountInfo = await this.account.getAccount(txs[0].address); - const multiMsgBytes = txs.map((tx) => { - return generateMsg(tx.typeUrl, tx.msgBytes); - }); - const txBody = TxBody.fromPartial({ - messages: multiMsgBytes, - }); - const txBodyBytes = TxBody.encode(txBody).finish(); + const txBodyBytes = this.getBodyBytes(txs); return { simulate: async (opts: SimulateOptions) => { @@ -256,14 +261,12 @@ export class Basic implements IBasic { denom, gasLimit, gasPrice, - privateKey, payer, granter, + privateKey, signTypedDataCallback = defaultSignTypedData, } = opts; - let signature, - pubKey = undefined; const types = mergeMultiEip712(txs.map((tx) => tx.MsgSDKTypeEIP712)); const fee = generateFee( String(BigInt(gasLimit) * BigInt(gasPrice)), @@ -285,25 +288,9 @@ export class Basic implements IBasic { ); const eip712 = createEIP712(wrapperTypes, this.chainId, messages); - if (privateKey) { - pubKey = getPubKeyByPriKey(privateKey); - signature = signTypedData({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - data: eip712, - version: SignTypedDataVersion.V4, - privateKey: Buffer.from(arrayify(privateKey)), - }); - } else { - signature = await signTypedDataCallback(accountInfo.address, JSON.stringify(eip712)); - const messageHash = eip712Hash(JSON.stringify(eip712)); - - const pk = recoverPk({ - signature, - messageHash, - }); - pubKey = makeCosmsPubKey(pk); - } + const { pubKey, signature } = privateKey + ? this.getSignByPriKey(eip712, privateKey) + : await this.getSignByWallet(eip712, accountInfo.address, signTypedDataCallback); const authInfoBytes = this.getAuthInfoBytes({ denom, @@ -341,130 +328,67 @@ export class Basic implements IBasic { amount: String(BigInt(gasLimit) * BigInt(gasPrice)), }, ]; - const feeGranter = granter; - const feePayer = payer; + const authInfoBytes = makeAuthInfoBytes( [{ pubkey: pubKey, sequence: Number(sequence) }], feeAmount, gasLimit, - feeGranter, - feePayer, + granter, + payer, 712, ); return authInfoBytes; } - /** - * Get the body bytes of a transaction - * - * EIP712 sign or private key sign - */ - protected async getRawTxBytes( - typeUrl: string, - msgEIP712Structor: object, - msgEIP712: object, - bodyBytes: Uint8Array, - accountInfo: BaseAccount, - txOption: BroadcastOptions, - ): Promise { - const { - denom, - gasLimit, - gasPrice, - privateKey, - signTypedDataCallback = defaultSignTypedData, - granter, - payer, - } = txOption; - - // console.log('txOption', txOption); - // console.log('msgEIP712', msgEIP712); - - let signature, - pubKey = undefined; - - if (privateKey) { - pubKey = getPubKeyByPriKey(privateKey); - signature = signEIP712Data( - this.chainId, - accountInfo.accountNumber + '', - accountInfo.sequence + '', - typeUrl, - msgEIP712Structor, - msgEIP712, - txOption, - ); - // console.log('signature', signature); - } else { - const eip712 = this.getEIP712Struct( - typeUrl, - msgEIP712Structor, - accountInfo.accountNumber + '', - accountInfo.sequence + '', - this.chainId, - msgEIP712, - txOption, - ); - signature = await signTypedDataCallback(accountInfo.address, JSON.stringify(eip712)); - const messageHash = eip712Hash(JSON.stringify(eip712)); - const pk = recoverPk({ - signature, - messageHash, - }); - pubKey = makeCosmsPubKey(pk); - } - - const authInfoBytes = this.getAuthInfoBytes({ - denom, - sequence: accountInfo.sequence + '', - gasLimit, - gasPrice, - pubKey, - granter, - payer, + private getBodyBytes(params: { typeUrl: string; msgBytes: Uint8Array }[]) { + const multiMsgBytes = params.map((tx) => { + return generateMsg(tx.typeUrl, tx.msgBytes); }); - const txRaw = TxRaw.fromPartial({ - bodyBytes, - authInfoBytes, - signatures: [arrayify(signature)], + const txBody = TxBody.fromPartial({ + messages: multiMsgBytes, }); - - return TxRaw.encode(txRaw).finish(); + const txBodyBytes = TxBody.encode(txBody).finish(); + return txBodyBytes; } - protected getSingleBodyBytes(typeUrl: string, msgBytes: Uint8Array): Uint8Array { - const msgWrapped = generateMsg(typeUrl, msgBytes); - - const txBody = TxBody.fromPartial({ - messages: [msgWrapped], + private getSignByPriKey( + eip712: ReturnType, + privateKey: SignOptions['privateKey'], + ) { + const pubKey = getPubKeyByPriKey(privateKey); + const signature = signTypedData({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + data: eip712, + version: SignTypedDataVersion.V4, + privateKey: Buffer.from(arrayify(privateKey)), }); - return TxBody.encode(txBody).finish(); + return { + pubKey, + signature, + }; } - protected getEIP712Struct( - typeUrl: string, - types: object, - accountNumber: string, - sequence: string, - chainId: string, - msg: object, - txOption: BroadcastOptions, + private async getSignByWallet( + eip712: ReturnType, + address: string, + signTypedDataCallback: SignOptions['signTypedDataCallback'], ) { - const { gasLimit, gasPrice, denom = DEFAULT_DENOM, payer, granter } = txOption; + const signature = await signTypedDataCallback(address, JSON.stringify(eip712)); + const messageHash = eip712Hash(JSON.stringify(eip712)); - const fee = generateFee( - String(BigInt(gasLimit) * BigInt(gasPrice)), - denom, - String(gasLimit), - payer, - granter, - ); - const wrapperTypes = generateTypes(types); - const wrapperMsg = typeWrapper(typeUrl, msg); - const messages = generateMessage(accountNumber, sequence, chainId, '', fee, wrapperMsg, '0'); - return createEIP712(wrapperTypes, chainId, messages); + const pk = recoverPk({ + signature, + messageHash, + }); + const pubKey = makeCosmsPubKey(pk); + + return { + pubKey, + signature, + }; } } diff --git a/packages/chain-sdk/src/api/bucket.ts b/packages/chain-sdk/src/api/bucket.ts index 5cbef10a..ae8ce4e8 100644 --- a/packages/chain-sdk/src/api/bucket.ts +++ b/packages/chain-sdk/src/api/bucket.ts @@ -1,17 +1,33 @@ +import { getSortQuery, HTTPHeaderUserAddress } from '@/clients/spclient/auth'; +import { getBucketApprovalMetaInfo } from '@/clients/spclient/spApis/bucketApproval'; +import { parseGetBucketMetaResponse } from '@/clients/spclient/spApis/getBucketMeta'; +import { parseGetUserBucketsResponse } from '@/clients/spclient/spApis/getUserBuckets'; +import { + getListBucketReadRecordMetaInfo, + parseListBucketReadRecordResponse, +} from '@/clients/spclient/spApis/listBucketReadRecords'; +import { parseListBucketsByIdsResponse } from '@/clients/spclient/spApis/listBucketsByIds'; +import { getMigrateMetaInfo } from '@/clients/spclient/spApis/migrateApproval'; +import { parseError } from '@/clients/spclient/spApis/parseError'; +import { + getQueryBucketReadQuotaMetaInfo, + parseReadQuotaResponse, +} from '@/clients/spclient/spApis/queryBucketReadQuota'; +import { METHOD_GET, NORMAL_ERROR_CODE } from '@/constants/http'; import { MsgCreateBucketSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgCreateBucket'; import { MsgDeleteBucketSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgDeleteBucket'; import { MsgMigrateBucketSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgMigrateBucket'; import { MsgUpdateBucketInfoSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgUpdateBucketInfo'; -import { getAuthorizationAuthTypeV1, getAuthorizationAuthTypeV2, ReqMeta } from '@/utils/auth'; -import { decodeObjectFromHexString, encodeObjectToHexString } from '@/utils/encoding'; import { - EMPTY_STRING_SHA256, - fetchWithTimeout, - METHOD_GET, - NORMAL_ERROR_CODE, - parseErrorXml, -} from '@/utils/http'; -import { generateUrlByBucketName, isValidAddress, isValidBucketName, isValidUrl } from '@/utils/s3'; + GetBucketMetaRequest, + GetBucketMetaResponse, + GetUserBucketsResponse, + ListBucketsByIDsResponse, +} from '@/types/sp-xml'; +import { ListBucketReadRecordResponse } from '@/types/sp-xml/ListBucketReadRecordResponse'; +import { decodeObjectFromHexString } from '@/utils/encoding'; +import { isValidAddress, isValidBucketName, isValidUrl } from '@/utils/s3'; +import { UInt64Value } from '@bnb-chain/greenfield-cosmos-types/greenfield/common/wrapper'; import { ActionType, Principal, @@ -36,6 +52,7 @@ import { } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; import { bytesFromBase64 } from '@bnb-chain/greenfield-cosmos-types/helpers'; import { Headers } from 'cross-fetch'; +import { bytesToUtf8, hexToBytes } from 'ethereum-cryptography/utils'; import Long from 'long'; import { container, delay, inject, singleton } from 'tsyringe'; import { @@ -45,37 +62,38 @@ import { MsgMigrateBucketTypeUrl, MsgUpdateBucketInfoTypeUrl, newBucketGRN, - TKeyValue, TxResponse, } from '..'; +import { RpcQueryClient } from '../clients/queryclient'; +import { AuthType, SpClient } from '../clients/spclient/spClient'; import { - BucketProps, + IBaseGetCreateBucket, ICreateBucketMsgType, - IMigrateBucket, IMigrateBucketMsgType, IObjectResultType, IQuotaProps, - TCreateBucket, - TGetBucketReadQuota, + TBaseGetBucketReadQuota, TGetUserBuckets, + TListBucketReadRecord, + TListBucketsByIDsRequest, } from '../types/storage'; import { Basic } from './basic'; -import { OffChainAuth } from './offchainauth'; -import { RpcQueryClient } from './queryclient'; import { Sp } from './sp'; import { Storage } from './storage'; -import { VirtualGroup } from './virtualGroup'; export interface IBucket { /** * returns the signature info for the approval of preCreating resources */ - getCreateBucketApproval(params: TCreateBucket): Promise>; + getCreateBucketApproval( + configParam: IBaseGetCreateBucket, + authType: AuthType, + ): Promise>; /** * get approval of creating bucket and send createBucket txn to greenfield chain */ - createBucket(params: TCreateBucket): Promise; + createBucket(params: IBaseGetCreateBucket, authType: AuthType): Promise; /** * query the bucketInfo on chain, return the bucket info if exists @@ -98,22 +116,26 @@ export interface IBucket { actionType: ActionType, ): Promise; - getUserBuckets(configParam: TGetUserBuckets): Promise>>; + getUserBuckets( + configParam: TGetUserBuckets, + ): Promise>; /** * return quota info of bucket of current month, include chain quota, free quota and consumed quota */ - getBucketReadQuota(configParam: TGetBucketReadQuota): Promise>; + getBucketReadQuota( + configParam: TBaseGetBucketReadQuota, + authType: AuthType, + ): Promise>; deleteBucket(msg: MsgDeleteBucket): Promise; - updateBucketInfo(msg: MsgUpdateBucketInfo): Promise; - - putBucketPolicy( - bucketName: string, - srcMsg: Omit, + updateBucketInfo( + srcMsg: Omit & { chargedReadQuota?: string }, ): Promise; + putBucketPolicy(bucketName: string, srcMsg: Omit): Promise; + deleteBucketPolicy( operator: string, bucketName: string, @@ -122,9 +144,26 @@ export interface IBucket { getBucketPolicy(request: QueryPolicyForAccountRequest): Promise; - getMigrateBucketApproval(params: IMigrateBucket): Promise>; + getMigrateBucketApproval( + params: Omit, + authType: AuthType, + ): Promise>; - migrateBucket(configParams: IMigrateBucket): Promise; + migrateBucket( + configParams: Omit, + authType: AuthType, + ): Promise; + + getBucketMeta(params: GetBucketMetaRequest): Promise>; + + listBucketReadRecords( + params: TListBucketReadRecord, + authType: AuthType, + ): Promise>; + + listBucketsByIds( + params: TListBucketsByIDsRequest, + ): Promise>; } @singleton() @@ -136,9 +175,9 @@ export class Bucket implements IBucket { ) {} private queryClient = container.resolve(RpcQueryClient); - private offChainAuthClient = container.resolve(OffChainAuth); + private spClient = container.resolve(SpClient); - public async getCreateBucketApproval(configParam: TCreateBucket) { + public async getCreateBucketApproval(params: IBaseGetCreateBucket, authType: AuthType) { const { bucketName, creator, @@ -146,7 +185,8 @@ export class Bucket implements IBucket { chargedReadQuota, spInfo, duration, - } = configParam; + paymentAddress, + } = params; try { if (!spInfo.primarySpAddress) { @@ -160,7 +200,8 @@ export class Bucket implements IBucket { } const endpoint = await this.sp.getSPUrlByPrimaryAddr(spInfo.primarySpAddress); - const msg: ICreateBucketMsgType = { + + const { reqMeta, optionsWithOutHeaders, url } = await getBucketApprovalMetaInfo(endpoint, { bucket_name: bucketName, creator, visibility, @@ -171,87 +212,34 @@ export class Bucket implements IBucket { global_virtual_group_family_id: 0, }, charged_read_quota: chargedReadQuota, - payment_address: '', - }; - const path = '/greenfield/admin/v1/get-approval'; - const query = 'action=CreateBucket'; - const url = `${endpoint}${path}?${query}`; - const unSignedMessageInHex = encodeObjectToHexString(msg); + payment_address: paymentAddress, + }); - let headerContent: TKeyValue = { - 'X-Gnfd-Unsigned-Msg': unSignedMessageInHex, - }; - - if (configParam.signType === 'authTypeV1') { - const reqMeta: Partial = { - contentSHA256: EMPTY_STRING_SHA256, - txnMsg: unSignedMessageInHex, - method: METHOD_GET, - url: { - hostname: new URL(endpoint).hostname, - query, - path, - }, - date: '', - // contentType: fileType, - }; - const v1Auth = getAuthorizationAuthTypeV1(reqMeta, configParam.privateKey); - - headerContent = { - 'X-Gnfd-Unsigned-Msg': unSignedMessageInHex, - // 'Content-Type': fileType, - 'Content-Type': 'application/octet-stream', - 'X-Gnfd-Content-Sha256': EMPTY_STRING_SHA256, - 'X-Gnfd-Date': '', - Authorization: v1Auth, - }; - } - if (configParam.signType === 'offChainAuth') { - const { seedString, domain } = configParam; - const { code, body, statusCode, message } = await this.offChainAuthClient.sign(seedString); - if (code !== 0) { - throw { - code: -1, - message: message || 'Get create bucket approval error.', - statusCode: statusCode, - }; - } - headerContent = { - ...headerContent, - Authorization: body?.authorization as string, - 'X-Gnfd-User-Address': creator, - 'X-Gnfd-App-Domain': domain, - }; - } + const signHeaders = await this.spClient.signHeaders(reqMeta, authType); - const headers = new Headers(headerContent); - const result = await fetchWithTimeout( + const result = await this.spClient.callApi( url, { - headers, - method: METHOD_GET, + ...optionsWithOutHeaders, + headers: signHeaders, }, duration, + { + code: -1, + message: 'Get create bucket approval error.', + }, ); - const { status } = result; - if (!result.ok) { - const { code, message } = await parseErrorXml(result); - throw { - code: code || -1, - message: message || 'Get create bucket approval error.', - statusCode: status, - }; - } - const signedMsgString = result.headers.get('X-Gnfd-Signed-Msg') || ''; - const signedMsg = decodeObjectFromHexString(signedMsgString) as ICreateBucketMsgType; + const signedMsg = JSON.parse( + bytesToUtf8(hexToBytes(signedMsgString)), + ) as ICreateBucketMsgType; return { code: 0, message: 'Get create bucket approval success.', body: signedMsgString, - statusCode: status, + statusCode: result.status, signedMsg: signedMsg, }; } catch (error: any) { @@ -279,8 +267,8 @@ export class Bucket implements IBucket { ); } - public async createBucket(params: TCreateBucket) { - const { signedMsg } = await this.getCreateBucketApproval(params); + public async createBucket(params: IBaseGetCreateBucket, authType: AuthType) { + const { signedMsg } = await this.getCreateBucketApproval(params, authType); if (!signedMsg) { throw new Error('Get create bucket approval error'); @@ -296,10 +284,8 @@ export class Bucket implements IBucket { sig: bytesFromBase64(signedMsg.primary_sp_approval.sig), globalVirtualGroupFamilyId: signedMsg.primary_sp_approval.global_virtual_group_family_id, }, - chargedReadQuota: signedMsg.charged_read_quota - ? Long.fromString('0') - : Long.fromString(signedMsg.charged_read_quota), - paymentAddress: '', + chargedReadQuota: Long.fromString(signedMsg.charged_read_quota), + paymentAddress: signedMsg.payment_address, }; return await this.createBucketTx(msg, signedMsg); @@ -354,12 +340,11 @@ export class Bucket implements IBucket { throw new Error('Invalid endpoint'); } const url = endpoint; - const headerContent: TKeyValue = { - 'X-Gnfd-User-Address': address, - }; - const headers = new Headers(headerContent); - const result = await fetchWithTimeout( + const headers = new Headers({ + [HTTPHeaderUserAddress]: address, + }); + const result = await this.spClient.callApi( url, { headers, @@ -368,21 +353,25 @@ export class Bucket implements IBucket { duration, ); const { status } = result; + if (!result.ok) { - const { code, message } = await parseErrorXml(result); + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); throw { code: code || -1, message: message || 'Get bucket error.', statusCode: status, }; } - const { buckets } = await result.json(); + + const xmlData = await result.text(); + const res = await parseGetUserBucketsResponse(xmlData); return { code: 0, message: 'Get bucket success.', statusCode: status, - body: buckets, + body: res.GfSpGetUserBucketsResponse.Buckets, }; } catch (error: any) { return { @@ -394,97 +383,52 @@ export class Bucket implements IBucket { } public async getBucketReadQuota( - configParam: TGetBucketReadQuota, + params: TBaseGetBucketReadQuota, + authType: AuthType, ): Promise> { try { - const { bucketName, endpoint, duration = 30000, year, month, signType } = configParam; - if (!isValidUrl(endpoint)) { - throw new Error('Invalid endpoint'); - } + const { bucketName, duration = 30000 } = params; if (!isValidBucketName(bucketName)) { throw new Error('Error bucket name'); } - const currentDate = new Date(); - const finalYear = year ? year : currentDate.getFullYear(); - const finalMonth = month ? month : currentDate.getMonth() + 1; - const formattedMonth = finalMonth.toString().padStart(2, '0'); // format month to 2 digits, like "01" - const url = - generateUrlByBucketName(endpoint, bucketName) + - `/?read-quota&year-month=${finalYear}-${formattedMonth}`; - - let headerContent: TKeyValue = {}; - if (!signType || signType === 'authTypeV2') { - const Authorization = getAuthorizationAuthTypeV2(); - headerContent = { - ...headerContent, - Authorization, - }; - } else if (configParam.signType === 'offChainAuth') { - const { seedString, address, domain } = configParam; - const { code, body, statusCode, message } = await this.offChainAuthClient.sign(seedString); - if (code !== 0) { - return { - code: -1, - message: message || 'Get create bucket approval error.', - statusCode: statusCode, - }; - } - headerContent = { - ...headerContent, - Authorization: body?.authorization as string, - 'X-Gnfd-User-Address': address, - 'X-Gnfd-App-Domain': domain, - }; + let endpoint = params.endpoint; + if (!endpoint) { + endpoint = await this.sp.getSPUrlByBucket(bucketName); } - const headers = new Headers(headerContent); + const { url, optionsWithOutHeaders, reqMeta } = await getQueryBucketReadQuotaMetaInfo( + endpoint, + params, + ); + const signHeaders = await this.spClient.signHeaders(reqMeta, authType); - const result = await fetchWithTimeout( + const result = await this.spClient.callApi( url, { - headers, - method: METHOD_GET, + ...optionsWithOutHeaders, + headers: signHeaders, }, duration, - ); - const { status } = result; - if (!result.ok) { - const { code, message } = await parseErrorXml(result); - return { - code: +(code || 0) || -1, - message: message || 'Get Bucket Quota error.', - statusCode: status, - }; - } - const resultContentType = result.headers.get('Content-Type'); - // Will receive xml when get object met error - if (resultContentType === 'text/xml' || resultContentType === 'application/xml') { - const xmlText = await result.text(); - const xml = await new window.DOMParser().parseFromString(xmlText, 'text/xml'); - const ReadQuotaSize = (xml as XMLDocument).getElementsByTagName('ReadQuotaSize')[0] - .textContent; - const SPFreeReadQuotaSize = (xml as XMLDocument).getElementsByTagName( - 'SPFreeReadQuotaSize', - )[0].textContent; - const ReadConsumedSize = (xml as XMLDocument).getElementsByTagName('ReadConsumedSize')[0] - .textContent; - return { - code: 0, - body: { - readQuota: Number(ReadQuotaSize ?? '0'), - freeQuota: Number(SPFreeReadQuotaSize ?? '0'), - consumedQuota: Number(ReadConsumedSize ?? '0'), - }, - message: 'Get bucket read quota.', - statusCode: status, - }; - } else { - return { + { code: -1, - message: 'Get bucket read quota error.', - statusCode: status, - }; - } + message: 'Get Bucket Quota error.', + }, + ); + + const xmlData = await result.text(); + const res = await parseReadQuotaResponse(xmlData); + + return { + code: 0, + body: { + readQuota: Number(res.GetReadQuotaResult.ReadQuotaSize ?? '0'), + freeQuota: Number(res.GetReadQuotaResult.SPFreeReadQuotaSize ?? '0'), + consumedQuota: Number(res.GetReadQuotaResult.ReadConsumedSize ?? '0'), + freeConsumedSize: Number(res.GetReadQuotaResult.FreeConsumedSize ?? '0'), + }, + message: 'Get bucket read quota.', + statusCode: result.status, + }; } catch (error: any) { return { code: -1, @@ -494,20 +438,32 @@ export class Bucket implements IBucket { } } - public async updateBucketInfo(msg: MsgUpdateBucketInfo) { + public async updateBucketInfo( + srcMsg: Omit & { chargedReadQuota: string }, + ) { + const msg: MsgUpdateBucketInfo = { + ...srcMsg, + visibility: visibilityTypeFromJSON(srcMsg.visibility), + chargedReadQuota: UInt64Value.fromPartial({ + value: Long.fromString(srcMsg.chargedReadQuota), + }), + }; + return await this.basic.tx( MsgUpdateBucketInfoTypeUrl, msg.operator, MsgUpdateBucketInfoSDKTypeEIP712, - MsgUpdateBucketInfo.toSDK(msg), + { + ...MsgUpdateBucketInfo.toSDK(msg), + charged_read_quota: { + value: srcMsg.chargedReadQuota, + }, + }, MsgUpdateBucketInfo.encode(msg).finish(), ); } - public async putBucketPolicy( - bucketName: string, - srcMsg: Omit, - ) { + public async putBucketPolicy(bucketName: string, srcMsg: Omit) { const resource = GRNToString(newBucketGRN(bucketName)); const msg: MsgPutPolicy = { ...srcMsg, @@ -537,71 +493,40 @@ export class Bucket implements IBucket { return rpc.QueryPolicyForAccount(request); } - public async getMigrateBucketApproval(configParams: IMigrateBucket) { - const { params, spInfo, signType } = configParams; + public async getMigrateBucketApproval( + params: Omit & { endpoint?: string }, + authType: AuthType, + ) { + const { bucketName, operator, dstPrimarySpId } = params; try { - const endpoint = spInfo.endpoint; - const url = endpoint + '/greenfield/admin/v1/get-approval?action=MigrateBucket'; - const msg = { - operator: params.operator, - bucket_name: params.bucketName, - dst_primary_sp_id: spInfo.id, + let endpoint = params.endpoint; + if (!endpoint) { + endpoint = await this.sp.getSPUrlById(params.dstPrimarySpId); + } + + const { reqMeta, optionsWithOutHeaders, url } = await getMigrateMetaInfo(endpoint, { + operator: operator, + bucket_name: bucketName, + dst_primary_sp_id: dstPrimarySpId, dst_primary_sp_approval: { expired_height: '0', sig: '', global_virtual_group_family_id: 0, }, - }; - const unSignedMessageInHex = encodeObjectToHexString(msg); + }); - let headerContent: TKeyValue = { - 'X-Gnfd-Unsigned-Msg': unSignedMessageInHex, - }; + const signHeaders = await this.spClient.signHeaders(reqMeta, authType); - if (!signType || signType === 'authTypeV2') { - const Authorization = getAuthorizationAuthTypeV2(); - headerContent = { - ...headerContent, - Authorization, - }; - } - if (signType === 'offChainAuth') { - const { seedString, domain } = configParams; - const { code, body, statusCode, message } = await this.offChainAuthClient.sign(seedString); - if (code !== 0) { - throw { - code: -1, - message: message || 'Get create bucket approval error.', - statusCode: statusCode, - }; - } - headerContent = { - ...headerContent, - Authorization: body?.authorization as string, - 'X-Gnfd-User-Address': msg.operator, - 'X-Gnfd-App-Domain': domain, - }; - } - - const headers = new Headers(headerContent); - const result = await fetchWithTimeout( + const result = await this.spClient.callApi( url, { - headers, - method: METHOD_GET, + ...optionsWithOutHeaders, + headers: signHeaders, }, 30000, ); - const { status } = result; - if (!result.ok) { - const { code, message } = await parseErrorXml(result); - throw { - code: code || -1, - message: message || 'Get create bucket approval error.', - statusCode: status, - }; - } + const signedMsgString = result.headers.get('X-Gnfd-Signed-Msg') || ''; const signedMsg = decodeObjectFromHexString(signedMsgString) as IMigrateBucketMsgType; @@ -609,7 +534,7 @@ export class Bucket implements IBucket { code: 0, message: 'Get migrate bucket approval success.', body: signedMsgString, - statusCode: status, + statusCode: result.status, signedMsg: signedMsg, }; } catch (error: any) { @@ -621,8 +546,11 @@ export class Bucket implements IBucket { } } - public async migrateBucket(configParams: IMigrateBucket) { - const { signedMsg } = await this.getMigrateBucketApproval(configParams); + public async migrateBucket( + configParams: Omit & { endpoint?: string }, + authType: AuthType, + ) { + const { signedMsg } = await this.getMigrateBucketApproval(configParams, authType); if (!signedMsg) { throw new Error('Get migrate bucket approval error'); @@ -651,9 +579,135 @@ export class Bucket implements IBucket { { ...signedMsg, type: MsgMigrateBucketTypeUrl, - primary_sp_approval: signedMsg.dst_primary_sp_approval, + primary_sp_approval: { + expired_height: signedMsg.dst_primary_sp_approval.expired_height, + global_virtual_group_family_id: + signedMsg.dst_primary_sp_approval.global_virtual_group_family_id, + sig: signedMsg.dst_primary_sp_approval.sig, + }, }, MsgMigrateBucket.encode(msg).finish(), ); } + + public async getBucketMeta(params: GetBucketMetaRequest) { + const { bucketName, endpoint } = params; + if (!isValidBucketName(bucketName)) { + throw new Error('Error bucket name'); + } + const queryMap = { + 'bucket-meta': '', + }; + const query = getSortQuery(queryMap); + const path = bucketName; + const url = `${endpoint}/${path}?${query}`; + const result = await this.spClient.callApi(url, { + method: METHOD_GET, + }); + + const xml = await result.text(); + const res = await parseGetBucketMetaResponse(xml); + + return { + code: 0, + message: 'get bucket meta success.', + statusCode: result.status, + body: res, + }; + } + + public async listBucketReadRecords(params: TListBucketReadRecord, authType: AuthType) { + try { + const { bucketName } = params; + // if (!isValidAddress(address)) { + // throw new Error('Error address'); + // } + let endpoint = params.endpoint; + if (!endpoint) { + endpoint = await this.sp.getSPUrlByBucket(bucketName); + } + if (!isValidUrl(endpoint)) { + throw new Error('Invalid endpoint'); + } + + const { url, optionsWithOutHeaders, reqMeta } = await getListBucketReadRecordMetaInfo( + endpoint, + params, + ); + const signHeaders = await this.spClient.signHeaders(reqMeta, authType); + + const result = await this.spClient.callApi( + url, + { + ...optionsWithOutHeaders, + headers: signHeaders, + }, + 3000, + { + code: -1, + message: 'Get Bucket Quota error.', + }, + ); + + const xmlData = await result.text(); + const res = await parseListBucketReadRecordResponse(xmlData); + + return { + code: 0, + body: res, + message: 'success', + statusCode: result.status, + }; + } catch (error: any) { + return { + code: -1, + message: error.message, + statusCode: error?.statusCode || NORMAL_ERROR_CODE, + }; + } + } + + public async listBucketsByIds(params: TListBucketsByIDsRequest) { + try { + const { ids } = params; + + const sp = await this.sp.getInServiceSP(); + const url = `${sp.endpoint}?ids=${ids.join(',')}&buckets-query=null`; + + const result = await this.spClient.callApi( + url, + { + headers: {}, + method: METHOD_GET, + }, + 3000, + ); + const { status } = result; + if (!result.ok) { + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); + throw { + code: code || -1, + message: message || 'error', + statusCode: status, + }; + } + + const xmlData = await result.text(); + const res = await parseListBucketsByIdsResponse(xmlData); + + return { + code: 0, + message: 'success', + statusCode: status, + body: res, + }; + } catch (error: any) { + return { + code: -1, + message: error.message, + statusCode: error?.statusCode || NORMAL_ERROR_CODE, + }; + } + } } diff --git a/packages/chain-sdk/src/api/challenge.ts b/packages/chain-sdk/src/api/challenge.ts index 9bda5fb5..72ffcfdd 100644 --- a/packages/chain-sdk/src/api/challenge.ts +++ b/packages/chain-sdk/src/api/challenge.ts @@ -9,7 +9,7 @@ import { MsgAttest, MsgSubmit } from '@bnb-chain/greenfield-cosmos-types/greenfi import { container, delay, inject, singleton } from 'tsyringe'; import { MsgAttestTypeUrl, MsgSubmitTypeUrl, TxResponse } from '..'; import { Basic } from './basic'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; export interface IChallenge { // TODO: getChallengeInfo(); diff --git a/packages/chain-sdk/src/api/crosschain.ts b/packages/chain-sdk/src/api/crosschain.ts index f91acacd..f4929893 100644 --- a/packages/chain-sdk/src/api/crosschain.ts +++ b/packages/chain-sdk/src/api/crosschain.ts @@ -4,8 +4,11 @@ import { MsgMirrorBucketSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgM import { MsgMirrorGroupSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgMirrorGroup'; import { MsgMirrorObjectSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgMirrorObject'; import { + QueryCrossChainPackageRequest, QueryCrossChainPackageResponse, + QueryReceiveSequenceRequest, QueryReceiveSequenceResponse, + QuerySendSequenceRequest, QuerySendSequenceResponse, } from '@bnb-chain/greenfield-cosmos-types/cosmos/crosschain/v1/query'; import { QueryInturnRelayerResponse } from '@bnb-chain/greenfield-cosmos-types/cosmos/oracle/v1/query'; @@ -17,7 +20,6 @@ import { MsgMirrorGroup, MsgMirrorObject, } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; -import Long from 'long'; import { container, singleton } from 'tsyringe'; import { MsgClaimTypeUrl, @@ -28,7 +30,7 @@ import { TxResponse, } from '..'; import { Basic } from './basic'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; export interface ICrossChain { /** @@ -44,12 +46,14 @@ export interface ICrossChain { /** * gets the next send sequence for a channel */ - getChannelSendSequence(channelId: number): Promise; + getChannelSendSequence(request: QuerySendSequenceRequest): Promise; /** * gets the next receive sequence for a channel */ - getChannelReceiveSequence(channelId: number): Promise; + getChannelReceiveSequence( + request: QueryReceiveSequenceRequest, + ): Promise; /** * gets the in-turn relayer bls public key and its relay interval @@ -57,8 +61,7 @@ export interface ICrossChain { getInturnRelayer(): Promise; getCrosschainPackage( - channelId: number, - sequence: number, + request: QueryCrossChainPackageRequest, ): Promise; /** @@ -104,18 +107,14 @@ export class CrossChain implements ICrossChain { ); } - public async getChannelSendSequence(channelId: number) { + public async getChannelSendSequence(request: QuerySendSequenceRequest) { const rpc = await this.queryClient.getCrosschainQueryClient(); - return await rpc.SendSequence({ - channelId, - }); + return await rpc.SendSequence(request); } - public async getChannelReceiveSequence(channelId: number) { + public async getChannelReceiveSequence(request: QueryReceiveSequenceRequest) { const rpc = await this.queryClient.getCrosschainQueryClient(); - return await rpc.ReceiveSequence({ - channelId, - }); + return await rpc.ReceiveSequence(request); } public async getInturnRelayer() { @@ -123,12 +122,9 @@ export class CrossChain implements ICrossChain { return await rpc.InturnRelayer(); } - public async getCrosschainPackage(channelId: number, sequence: number) { + public async getCrosschainPackage(request: QueryCrossChainPackageRequest) { const rpc = await this.queryClient.getCrosschainQueryClient(); - return await rpc.CrossChainPackage({ - channelId, - sequence: Long.fromNumber(sequence), - }); + return await rpc.CrossChainPackage(request); } public async mirrorGroup(msg: MsgMirrorGroup) { diff --git a/packages/chain-sdk/src/api/distribution.ts b/packages/chain-sdk/src/api/distribution.ts index 1cab4edb..c73560db 100644 --- a/packages/chain-sdk/src/api/distribution.ts +++ b/packages/chain-sdk/src/api/distribution.ts @@ -7,7 +7,7 @@ import { import { Coin } from '@cosmjs/proto-signing'; import { container } from 'tsyringe'; import { Basic } from './basic'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; export interface IDistribution { /** * sets the withdrawal address for a delegator address diff --git a/packages/chain-sdk/src/api/feegrant.ts b/packages/chain-sdk/src/api/feegrant.ts index 0dedc295..f0833dfc 100644 --- a/packages/chain-sdk/src/api/feegrant.ts +++ b/packages/chain-sdk/src/api/feegrant.ts @@ -25,7 +25,7 @@ import { TxResponse, } from '..'; import { Basic } from './basic'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; export interface IFeeGrant { grantAllowance(msg: IGrantAllowance): Promise; @@ -43,12 +43,12 @@ export class FeeGrant implements IFeeGrant { private queryClient: RpcQueryClient = container.resolve(RpcQueryClient); public async grantAllowance(params: IGrantAllowance) { - const { amount, denom, allowedMessages, grantee, granter } = params; + const { amount, denom, allowedMessages, grantee, granter, expirationTime } = params; - const basicAllowance = newBasicAllowance(amount, denom); + const basicAllowance = newBasicAllowance(amount, denom, expirationTime); const allowedMsgAllowance = newAllowedMsgAllowance(allowedMessages, basicAllowance); const grantAllowance = newMsgGrantAllowance(grantee, granter, allowedMsgAllowance); - const marshal = newMarshal(amount, denom, allowedMessages); + const marshal = newMarshal(amount, denom, allowedMessages, expirationTime); return await this.basic.tx( MsgGrantAllowanceTypeUrl, diff --git a/packages/chain-sdk/src/api/gashub.ts b/packages/chain-sdk/src/api/gashub.ts index def04f80..422cfa1b 100644 --- a/packages/chain-sdk/src/api/gashub.ts +++ b/packages/chain-sdk/src/api/gashub.ts @@ -4,7 +4,7 @@ import { QueryMsgGasParamsResponse, QueryParamsResponse, } from '@bnb-chain/greenfield-cosmos-types/cosmos/gashub/v1beta1/query'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; export interface IGashub { getParams(): Promise; diff --git a/packages/chain-sdk/src/api/group.ts b/packages/chain-sdk/src/api/group.ts index 9d9544e0..8cd15168 100644 --- a/packages/chain-sdk/src/api/group.ts +++ b/packages/chain-sdk/src/api/group.ts @@ -2,14 +2,14 @@ import { MsgCreateGroupSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgCr import { MsgDeleteGroupSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgDeleteGroup'; import { MsgLeaveGroupSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgLeaveGroup'; import { MsgUpdateGroupExtraSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgUpdateGroupExtra'; -import { MsgUpdateGroupMemberSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgUpdateGroupMember'; +import { getMsgUpdateGroupMemberSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgUpdateGroupMember'; import { GRNToString, newBucketGRN, newGroupGRN, newObjectGRN } from '@/utils/grn'; import { QueryGroupNFTResponse, QueryHeadGroupMemberResponse, QueryHeadGroupResponse, - QueryListGroupRequest, - QueryListGroupResponse, + QueryListGroupsRequest, + QueryListGroupsResponse, QueryNFTRequest, QueryPolicyForGroupRequest, QueryPolicyForGroupResponse, @@ -24,6 +24,7 @@ import { } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; import { container, delay, inject, singleton } from 'tsyringe'; import { + fromTimestamp, MsgCreateGroupTypeUrl, MsgDeleteGroupTypeUrl, MsgLeaveGroupTypeUrl, @@ -32,7 +33,7 @@ import { TxResponse, } from '..'; import { Basic } from './basic'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; import { Storage } from './storage'; export interface IGroup { @@ -72,7 +73,7 @@ export interface IGroup { member: string, ): Promise; - listGroup(request: QueryListGroupRequest): Promise; + listGroup(request: QueryListGroupsRequest): Promise; headGroupNFT(request: QueryNFTRequest): Promise; @@ -138,8 +139,19 @@ export class Group implements IGroup { return await this.basic.tx( MsgUpdateGroupMemberTypeUrl, msg.operator, - MsgUpdateGroupMemberSDKTypeEIP712, - MsgUpdateGroupMember.toSDK(msg), + getMsgUpdateGroupMemberSDKTypeEIP712({ + membersToAdd: msg.membersToAdd, + membersToDelete: msg.membersToDelete, + }), + { + ...MsgUpdateGroupMember.toSDK(msg), + members_to_add: msg.membersToAdd.map((x) => { + return { + member: x.member, + expiration_time: fromTimestamp(x.expirationTime), + }; + }), + }, MsgUpdateGroupMember.encode(msg).finish(), ); } @@ -186,9 +198,9 @@ export class Group implements IGroup { return await rpc.HeadGroupNFT(request); } - public async listGroup(request: QueryListGroupRequest) { + public async listGroup(request: QueryListGroupsRequest) { const rpc = await this.queryClient.getStorageQueryClient(); - return await rpc.ListGroup(request); + return await rpc.ListGroups(request); } public async getPolicyOfGroup(request: QueryPolicyForGroupRequest) { diff --git a/packages/chain-sdk/src/api/objectt.ts b/packages/chain-sdk/src/api/objectt.ts index 5dde9de6..f25f3dc3 100644 --- a/packages/chain-sdk/src/api/objectt.ts +++ b/packages/chain-sdk/src/api/objectt.ts @@ -1,17 +1,19 @@ +import { encodePath, getMsgToSign, getSortQuery, secpSign } from '@/clients/spclient/auth'; +import { getGetObjectMetaInfo } from '@/clients/spclient/spApis/getObject'; +import { parseGetObjectMetaResponse } from '@/clients/spclient/spApis/getObjectMeta'; +import { parseListObjectsByBucketNameResponse } from '@/clients/spclient/spApis/listObjectsByBucket'; +import { parseListObjectsByIdsResponse } from '@/clients/spclient/spApis/listObjectsByIds'; +import { getObjectApprovalMetaInfo } from '@/clients/spclient/spApis/objectApproval'; +import { parseError } from '@/clients/spclient/spApis/parseError'; +import { getPutObjectMetaInfo } from '@/clients/spclient/spApis/putObject'; +import { METHOD_GET, NORMAL_ERROR_CODE } from '@/constants/http'; import { MsgCancelCreateObjectSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgCancelCreateObject'; import { MsgCreateObjectSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgCreateObject'; import { MsgDeleteObjectSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgDeleteObject'; import { MsgUpdateObjectInfoSDKTypeEIP712 } from '@/messages/greenfield/storage/MsgUpdateObjectInfo'; -import { getAuthorizationAuthTypeV1, getAuthorizationAuthTypeV2, ReqMeta } from '@/utils/auth'; -import { - EMPTY_STRING_SHA256, - fetchWithTimeout, - METHOD_GET, - METHOD_POST, - METHOD_PUT, - NORMAL_ERROR_CODE, - parseErrorXml, -} from '@/utils/http'; +import { signSignatureByEddsa } from '@/offchainauth'; +import { GetObjectMetaRequest, GetObjectMetaResponse } from '@/types/sp-xml/GetObjectMetaResponse'; +import { ListObjectsByBucketNameResponse } from '@/types/sp-xml/ListObjectsByBucketNameResponse'; import { ActionType, Principal, @@ -37,7 +39,9 @@ import { MsgUpdateObjectInfo, } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; import { bytesFromBase64 } from '@bnb-chain/greenfield-cosmos-types/helpers'; +import { hexlify } from '@ethersproject/bytes'; import { Headers } from 'cross-fetch'; +import { bytesToUtf8, hexToBytes, utf8ToBytes } from 'ethereum-cryptography/utils'; import { container, delay, inject, singleton } from 'tsyringe'; import { GRNToString, @@ -47,22 +51,21 @@ import { MsgUpdateObjectInfoTypeUrl, newObjectGRN, } from '..'; +import { RpcQueryClient } from '../clients/queryclient'; +import { AuthType, SpClient } from '../clients/spclient/spClient'; import { ICreateObjectMsgType, - IObjectsProps, IObjectResultType, + ListObjectsByIDsResponse, Long, - SignTypeOffChain, - SignTypeV1, TBaseGetCreateObject, - TCreateObject, - TGetObject, - TKeyValue, + TBaseGetObject, + TBaseGetPrivewObject, + TBasePutObject, TListObjects, - TPutObject, + TListObjectsByIDsRequest, TxResponse, } from '../types'; -import { decodeObjectFromHexString, encodeObjectToHexString } from '../utils/encoding'; import { generateUrlByBucketName, isValidBucketName, @@ -70,18 +73,18 @@ import { isValidUrl, } from '../utils/s3'; import { Basic } from './basic'; -import { Bucket } from './bucket'; -import { OffChainAuth } from './offchainauth'; -import { RpcQueryClient } from './queryclient'; import { Sp } from './sp'; import { Storage } from './storage'; export interface IObject { - getCreateObjectApproval(getApprovalParams: TCreateObject): Promise>; + getCreateObjectApproval( + configParam: TBaseGetCreateObject, + authType: AuthType, + ): Promise>; - createObject(getApprovalParams: TCreateObject): Promise; + createObject(getApprovalParams: TBaseGetCreateObject, authType: AuthType): Promise; - uploadObject(configParam: TPutObject): Promise>; + uploadObject(configParam: TBasePutObject, authType: AuthType): Promise>; cancelCreateObject(msg: MsgCancelCreateObject): Promise; @@ -95,15 +98,25 @@ export interface IObject { headObjectNFT(request: QueryNFTRequest): Promise; - getObject(configParam: TGetObject): Promise>; + /** + * get s3 object's blob + */ + getObject(configParam: TBaseGetObject, authType: AuthType): Promise>; + + getObjectPreviewUrl(configParam: TBaseGetPrivewObject, authType: AuthType): Promise; - downloadFile(configParam: TGetObject): Promise; + /** + * download s3 object + */ + downloadFile(configParam: TBaseGetObject, authType: AuthType): Promise; - listObjects(configParam: TListObjects): Promise>; + listObjects( + configParam: TListObjects, + ): Promise>; createFolder( getApprovalParams: Omit, - signParams: SignTypeOffChain | SignTypeV1, + authType: AuthType, ): Promise; putObjectPolicy( @@ -132,6 +145,12 @@ export interface IObject { objectName: string, principalAddr: string, ): Promise; + + getObjectMeta(params: GetObjectMetaRequest): Promise>; + + listObjectsByIds( + params: TListObjectsByIDsRequest, + ): Promise>; // TODO: GetObjectUploadProgress // TODO: getObjectStatusFromSP } @@ -145,9 +164,9 @@ export class Objectt implements IObject { ) {} private queryClient: RpcQueryClient = container.resolve(RpcQueryClient); - private offChainAuthClient = container.resolve(OffChainAuth); + private spClient = container.resolve(SpClient); - public async getCreateObjectApproval(configParam: TCreateObject) { + public async getCreateObjectApproval(params: TBaseGetCreateObject, authType: AuthType) { const { bucketName, creator, @@ -158,7 +177,7 @@ export class Objectt implements IObject { redundancyType = 'REDUNDANCY_EC_TYPE', contentLength, expectCheckSums, - } = configParam; + } = params; try { if (!isValidBucketName(bucketName)) { @@ -171,11 +190,13 @@ export class Objectt implements IObject { throw new Error('empty creator address'); } - // must sort (Go SDK) - const msg: ICreateObjectMsgType = { + let endpoint = params.endpoint; + if (!endpoint) { + endpoint = await this.sp.getSPUrlByBucket(bucketName); + } + const { reqMeta, optionsWithOutHeaders, url } = await getObjectApprovalMetaInfo(endpoint, { bucket_name: bucketName, content_type: fileType, - // content_type: 'application/octet-stream', creator: creator, expect_checksums: expectCheckSums, object_name: objectName, @@ -187,91 +208,33 @@ export class Objectt implements IObject { }, redundancy_type: redundancyType, visibility, - }; + }); - const endpoint = await this.sp.getSPUrlByBucket(bucketName); - const path = '/greenfield/admin/v1/get-approval'; - const query = 'action=CreateObject'; - const url = `${endpoint}${path}?${query}`; + const signHeaders = await this.spClient.signHeaders(reqMeta, authType); - const unSignedMessageInHex = encodeObjectToHexString(msg); - - let headerContent: TKeyValue = { - 'X-Gnfd-Unsigned-Msg': unSignedMessageInHex, - }; - - if (configParam.signType === 'authTypeV1') { - // const date = new Date().toISOString(); - const reqMeta: Partial = { - contentSHA256: EMPTY_STRING_SHA256, - txnMsg: unSignedMessageInHex, - method: METHOD_GET, - url: { - hostname: new URL(endpoint).hostname, - query, - path, - }, - date: '', - contentType: fileType, - }; - - const Authorization = getAuthorizationAuthTypeV1(reqMeta, configParam.privateKey); - - headerContent = { - 'X-Gnfd-Unsigned-Msg': unSignedMessageInHex, - 'Content-Type': fileType, - // 'Content-Type': 'application/octet-stream', - 'X-Gnfd-Content-Sha256': EMPTY_STRING_SHA256, - 'X-Gnfd-Date': '', - Authorization, - }; - // console.log(x) - } else if (configParam.signType === 'offChainAuth') { - const { seedString, domain } = configParam; - const { code, body, statusCode } = await this.offChainAuthClient.sign(seedString); - if (code !== 0) { - throw { - code: -1, - message: 'Get create bucket approval error.', - statusCode: statusCode, - }; - } - headerContent = { - ...headerContent, - Authorization: body?.authorization as string, - 'X-Gnfd-User-Address': creator, - 'X-Gnfd-App-Domain': domain, - }; - } - const headers = new Headers(headerContent); - const result = await fetchWithTimeout( + const result = await this.spClient.callApi( url, { - headers, - method: METHOD_GET, + ...optionsWithOutHeaders, + headers: signHeaders, }, duration, + { + code: -1, + message: 'Get create object approval error.', + }, ); - const { status } = result; - if (!result.ok) { - const { code, message } = await parseErrorXml(result); - throw { - code: code || -1, - message: message || 'Get create object approval error.', - statusCode: status, - error: result, - }; - } - const signedMsgString = result.headers.get('X-Gnfd-Signed-Msg') || ''; - const signedMsg = decodeObjectFromHexString(signedMsgString) as ICreateObjectMsgType; + const signedMsg = JSON.parse( + bytesToUtf8(hexToBytes(signedMsgString)), + ) as ICreateObjectMsgType; return { code: 0, message: 'Get create object approval success.', body: result.headers.get('X-Gnfd-Signed-Msg') ?? '', - statusCode: status, + statusCode: result.status, signedMsg, }; } catch (error: any) { @@ -302,8 +265,8 @@ export class Objectt implements IObject { ); } - public async createObject(getApprovalParams: TCreateObject) { - const { signedMsg } = await this.getCreateObjectApproval(getApprovalParams); + public async createObject(getApprovalParams: TBaseGetCreateObject, authType: AuthType) { + const { signedMsg } = await this.getCreateObjectApproval(getApprovalParams, authType); if (!signedMsg) { throw new Error('Get create object approval error'); } @@ -327,8 +290,11 @@ export class Objectt implements IObject { return await this.createObjectTx(msg, signedMsg); } - public async uploadObject(configParam: TPutObject): Promise> { - const { bucketName, objectName, txnHash, body, duration = 30000 } = configParam; + public async uploadObject( + params: TBasePutObject, + authType: AuthType, + ): Promise> { + const { bucketName, objectName, txnHash, body, duration = 30000 } = params; if (!isValidBucketName(bucketName)) { throw new Error('Error bucket name'); } @@ -339,76 +305,29 @@ export class Objectt implements IObject { throw new Error('Transaction hash is empty, please check.'); } - const endpoint = await this.sp.getSPUrlByBucket(bucketName); - const path = `/${objectName}`; - const query = ''; - const url = generateUrlByBucketName(endpoint, bucketName) + path; - - let headerContent: TKeyValue = { - 'X-Gnfd-Txn-hash': txnHash, - }; - if (!configParam.signType || configParam.signType === 'authTypeV1') { - const date = new Date().toISOString(); - const reqMeta: Partial = { - contentSHA256: EMPTY_STRING_SHA256, - method: METHOD_PUT, - url: { - hostname: `${bucketName}.${new URL(endpoint).hostname}`, - query, - path, - }, - date: date, - // contentType: '', - txnHash: txnHash, - }; - - const Authorization = getAuthorizationAuthTypeV1(reqMeta, configParam.privateKey); - headerContent = { - ...headerContent, - 'Content-Type': 'application/octet-stream', - 'X-Gnfd-Content-Sha256': EMPTY_STRING_SHA256, - 'X-Gnfd-Date': date, - Authorization, - }; - } else if (configParam.signType === 'offChainAuth') { - const { seedString, address, domain } = configParam; - const { code, body, statusCode, message } = await this.offChainAuthClient.sign(seedString); - if (code !== 0) { - return { - code: -1, - message: message || 'Get create object approval error.', - statusCode: statusCode, - }; - } - headerContent = { - ...headerContent, - Authorization: body?.authorization as string, - 'X-Gnfd-User-Address': address, - 'X-Gnfd-App-Domain': domain, - }; + let endpoint = params.endpoint; + if (!endpoint) { + endpoint = await this.sp.getSPUrlByBucket(bucketName); } - - const headers = new Headers(headerContent); + const { reqMeta, optionsWithOutHeaders, url } = await getPutObjectMetaInfo(endpoint, { + bucketName, + objectName, + contentType: body.type, + txnHash, + body, + }); + const signHeaders = await this.spClient.signHeaders(reqMeta, authType); try { - const result = await fetchWithTimeout( + const result = await this.spClient.callApi( url, { - headers, - method: METHOD_PUT, - body, + ...optionsWithOutHeaders, + headers: signHeaders, }, duration, ); const { status } = result; - if (!result.ok) { - const { code, message } = await parseErrorXml(result); - return { - code: +(code || 0) || -1, - message: message || 'Put object error.', - statusCode: status, - }; - } return { code: 0, message: 'Put object success.', statusCode: status }; } catch (error: any) { @@ -472,58 +391,39 @@ export class Objectt implements IObject { return await rpc.HeadObjectNFT(request); } - public async getObject(configParam: TGetObject) { + public async getObject(params: TBaseGetObject, authType: AuthType) { try { - const { bucketName, objectName, endpoint, duration = 30000 } = configParam; - if (!isValidUrl(endpoint)) { - throw new Error('Invalid endpoint'); - } + const { bucketName, objectName, duration = 30000 } = params; if (!isValidBucketName(bucketName)) { throw new Error('Error bucket name'); } if (!isValidObjectName(objectName)) { throw new Error('Error object name'); } - const url = generateUrlByBucketName(endpoint, bucketName) + '/' + objectName; - - let headerContent: TKeyValue = {}; - if (!configParam.signType || configParam.signType === 'authTypeV2') { - const Authorization = getAuthorizationAuthTypeV2(); - headerContent = { - ...headerContent, - Authorization, - }; - } else if (configParam.signType === 'offChainAuth') { - const { seedString, address, domain } = configParam; - const { code, body, statusCode, message } = await this.offChainAuthClient.sign(seedString); - if (code !== 0) { - throw { - code: -1, - message: message || 'Get create object approval error.', - statusCode: statusCode, - }; - } - headerContent = { - ...headerContent, - Authorization: body?.authorization as string, - 'X-Gnfd-User-Address': address, - 'X-Gnfd-App-Domain': domain, - }; + let endpoint = params.endpoint; + if (!endpoint) { + endpoint = await this.sp.getSPUrlByBucket(bucketName); } - const headers = new Headers(headerContent); + const { reqMeta, optionsWithOutHeaders, url } = await getGetObjectMetaInfo(endpoint, { + bucketName, + objectName, + }); + + const headers = await this.spClient.signHeaders(reqMeta, authType); - const result = await fetchWithTimeout( + const result = await this.spClient.callApi( url, { + ...optionsWithOutHeaders, headers, - method: METHOD_GET, }, duration, ); const { status } = result; if (!result.ok) { - const { code, message } = await parseErrorXml(result); + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); return { code: code || -1, @@ -548,10 +448,49 @@ export class Objectt implements IObject { } } - public async downloadFile(configParam: TGetObject): Promise { + public async getObjectPreviewUrl(params: TBaseGetPrivewObject, authType: AuthType) { + const { bucketName, objectName, queryMap } = params; + if (!isValidBucketName(bucketName)) { + throw new Error('Error bucket name'); + } + if (!isValidObjectName(objectName)) { + throw new Error('Error object name'); + } + let endpoint = params.endpoint; + if (!endpoint) { + endpoint = await this.sp.getSPUrlByBucket(bucketName); + } + + const path = '/' + encodePath(objectName); + const url = generateUrlByBucketName(endpoint, bucketName) + path; + + const queryRaw = getSortQuery(queryMap); + + const canonicalRequest = [ + METHOD_GET, + `/${encodePath(objectName)}`, + queryRaw, + new URL(url).host, + '\n', + ].join('\n'); + + const unsignedMsg = getMsgToSign(utf8ToBytes(canonicalRequest)); + let authorization = ''; + if (authType.type === 'ECDSA') { + const sig = secpSign(unsignedMsg, authType.privateKey); + authorization = `GNFD1-ECDSA, Signature=${sig.slice(2)}`; + } else { + const sig = await signSignatureByEddsa(authType.seed, hexlify(unsignedMsg).slice(2)); + authorization = `GNFD1-EDDSA,Signature=${sig}`; + } + + return `${url}?Authorization=${encodeURIComponent(authorization)}&${queryRaw}`; + } + + public async downloadFile(configParam: TBaseGetObject, authType: AuthType): Promise { try { const { objectName } = configParam; - const getObjectResult = await this.getObject(configParam); + const getObjectResult = await this.getObject(configParam, authType); if (getObjectResult.code !== 0) { throw new Error(getObjectResult.message); @@ -585,10 +524,9 @@ export class Objectt implements IObject { throw new Error('Invalid endpoint'); } const url = `${generateUrlByBucketName(endpoint, bucketName)}?${query?.toString()}`; - const headerContent: TKeyValue = {}; - const headers = new Headers(headerContent); + const headers = new Headers(); - const result = await fetchWithTimeout( + const result = await this.spClient.callApi( url, { headers, @@ -598,19 +536,23 @@ export class Objectt implements IObject { ); const { status } = result; if (!result.ok) { - const { code, message } = await parseErrorXml(result); + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); return { code: code || -1, message: message || 'List object error.', statusCode: status, }; } - const body = await result.json(); + + const xmlData = await result.text(); + const res = await parseListObjectsByBucketNameResponse(xmlData); + return { code: 0, message: 'List object success.', statusCode: status, - body, + body: res, }; } catch (error: any) { return { @@ -623,7 +565,7 @@ export class Objectt implements IObject { public async createFolder( getApprovalParams: Omit, - signParams: SignTypeOffChain | SignTypeV1, + authType: AuthType, ) { if (!getApprovalParams.objectName.endsWith('/')) { throw new Error( @@ -640,7 +582,7 @@ export class Objectt implements IObject { const { contentLength, expectCheckSums } = hashResult; */ - const params: TCreateObject = { + const params: TBaseGetCreateObject = { bucketName: getApprovalParams.bucketName, objectName: getApprovalParams.objectName, contentLength: 0, @@ -655,10 +597,9 @@ export class Objectt implements IObject { '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=', ], creator: getApprovalParams.creator, - ...signParams, }; - return this.createObject(params); + return this.createObject(params, authType); } public async putObjectPolicy( @@ -722,16 +663,77 @@ export class Objectt implements IObject { return await this.storage.deletePolicy(msg); } - // private async getObjectStatusFromSP(params: IGetObjectStaus) { - // const {bucketInfo} = await this.bucket.headBucket(params.bucketName); - // const primarySpAddress = bucketInfo?.primarySpAddress - - // // const url = params.endpoint + '/greenfield/admin/v1/get-approval?upload-progress='; - // // const unSignedMessageInHex = encodeObjectToHexString(msg); - // // const headers = new Headers({ - // // // TODO: replace when offchain release - // // Authorization: `authTypeV2 ECDSA-secp256k1, Signature=${MOCK_SIGNATURE}`, - // // 'X-Gnfd-Unsigned-Msg': unSignedMessageInHex, - // // }); - // } + public async getObjectMeta(params: GetObjectMetaRequest) { + const { bucketName, objectName, endpoint } = params; + if (!isValidBucketName(bucketName)) { + throw new Error('Error bucket name'); + } + if (!isValidObjectName(objectName)) { + throw new Error('Error object name'); + } + + const queryMap = { + 'object-meta': '', + }; + const query = getSortQuery(queryMap); + const path = encodePath(objectName); + const url = `${generateUrlByBucketName(endpoint, bucketName)}/${path}?${query}`; + const result = await this.spClient.callApi(url, { + method: METHOD_GET, + }); + + const xml = await result.text(); + const res = await parseGetObjectMetaResponse(xml); + + return { + code: 0, + message: 'get object meta success.', + statusCode: result.status, + body: res, + }; + } + + public async listObjectsByIds(params: TListObjectsByIDsRequest) { + try { + const { ids } = params; + + const sp = await this.sp.getInServiceSP(); + const url = `${sp.endpoint}?objects-query=null&ids=${ids.join(',')}`; + + const result = await this.spClient.callApi( + url, + { + headers: {}, + method: METHOD_GET, + }, + 3000, + ); + const { status } = result; + if (!result.ok) { + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); + throw { + code: code || -1, + message: message || 'error', + statusCode: status, + }; + } + + const xmlData = await result.text(); + const res = await parseListObjectsByIdsResponse(xmlData); + + return { + code: 0, + message: 'success', + statusCode: status, + body: res, + }; + } catch (error: any) { + return { + code: -1, + message: error.message, + statusCode: error?.statusCode || NORMAL_ERROR_CODE, + }; + } + } } diff --git a/packages/chain-sdk/src/api/offchainauth.ts b/packages/chain-sdk/src/api/offchainauth.ts index fb0b22fe..869c3693 100644 --- a/packages/chain-sdk/src/api/offchainauth.ts +++ b/packages/chain-sdk/src/api/offchainauth.ts @@ -1,25 +1,22 @@ -import { NORMAL_ERROR_CODE } from '@/utils/http'; -import { singleton } from 'tsyringe'; -import { convertTimeStampToDate, getUtcZeroTimestamp } from '..'; -import { - IGenOffChainAuthKeyPairAndUpload, - IObjectResultType, - IReturnOffChainAuthKeyPairAndUpload, - IReturnSignWithSeedString, - ISp, -} from '../types/storage'; +import { NORMAL_ERROR_CODE } from '@/constants/http'; import { fetchNonces, genLocalSignMsg, genSecondSignMsg, - genSeedSignMsg, getCurrentAccountPublicKey, getCurrentSeedString, personalSign, - signSignatureByEddsa, updateSpsPubKey, } from '@/offchainauth'; import { hexlify } from '@ethersproject/bytes'; +import { singleton } from 'tsyringe'; +import { convertTimeStampToDate, getUtcZeroTimestamp } from '..'; +import { + IGenOffChainAuthKeyPairAndUpload, + IObjectResultType, + IReturnOffChainAuthKeyPairAndUpload, + ISp, +} from '../types/storage'; export interface IOffChainAuth { /** @@ -29,11 +26,6 @@ export interface IOffChainAuth { params: IGenOffChainAuthKeyPairAndUpload, provider: any, ): Promise>; - - /** - * Sign a message with a seed string, return the authorization for sp authorizing the user. - */ - sign(seedString: string): Promise>; } @singleton() @@ -54,9 +46,11 @@ export class OffChainAuth implements IOffChainAuth { const spsWithNonce = spsNonceRaw.filter((item: ISp) => item.nonce !== null); // 2. generate signature key pair const seedMsg = genLocalSignMsg(spsWithNonce, domain); + // Uint8Array const seed = await getCurrentSeedString({ message: seedMsg, address, chainId, provider }); const seedString = hexlify(seed); const pubKey = await getCurrentAccountPublicKey(seedString); + // 3. second sign for upload public key to server const curUtcZeroTimestamp = getUtcZeroTimestamp(); const expirationTime = curUtcZeroTimestamp + expirationMs; @@ -73,7 +67,7 @@ export class OffChainAuth implements IOffChainAuth { }); const signRes = await personalSign({ message: signMsg, address, provider }); const jsonSignMsg = JSON.stringify(signMsg).replace(/\"/g, ''); - const authorization = `PersonalSign ECDSA-secp256k1,SignedMsg=${jsonSignMsg},Signature=${signRes}`; + const authorization = `GNFD1-ETH-PERSONAL_SIGN,SignedMsg=${jsonSignMsg},Signature=${signRes}`; // 4. upload signature and pubKey to server const res = await updateSpsPubKey({ address, @@ -111,32 +105,4 @@ export class OffChainAuth implements IOffChainAuth { return { code: -1, message: error.message, statusCode: error?.status || NORMAL_ERROR_CODE }; } } - - public async sign(seedString: string) { - try { - // NOTICE: Smoothing local and server time gap - const expirationMs = 300000 - 100000; - const timestamp = getUtcZeroTimestamp(); - const expireTimestamp = timestamp + expirationMs; - const signMsg = genSeedSignMsg(expireTimestamp); - const signRes = await signSignatureByEddsa(seedString, signMsg); - const authorization = `OffChainAuth EDDSA,SignedMsg=${signMsg},Signature=${signRes}`; - - return { - code: 0, - body: { - unSignedMsg: signMsg, - signature: signRes, - authorization, - }, - message: 'Sign with seed string success', - }; - } catch (error: any) { - return { - code: -1, - message: error.message || 'Sign with seed string failed', - statusCode: error?.status || NORMAL_ERROR_CODE, - }; - } - } } diff --git a/packages/chain-sdk/src/api/payment.ts b/packages/chain-sdk/src/api/payment.ts index 7935feb6..35104328 100644 --- a/packages/chain-sdk/src/api/payment.ts +++ b/packages/chain-sdk/src/api/payment.ts @@ -2,8 +2,28 @@ import { MsgDepositSDKTypeEIP712 } from '@/messages/greenfield/payment/MsgDeposi import { MsgDisableRefundSDKTypeEIP712 } from '@/messages/greenfield/payment/MsgDisableRefund'; import { MsgWithdrawSDKTypeEIP712 } from '@/messages/greenfield/payment/MsgWithdraw'; import { + QueryAutoSettleRecordsRequest, + QueryAutoSettleRecordsResponse, + QueryPaymentAccountCountsRequest, + QueryPaymentAccountCountsResponse, + QueryPaymentAccountsRequest, + QueryPaymentAccountsResponse, + QueryStreamRecordsRequest, + QueryStreamRecordsResponse, + QueryDynamicBalanceRequest, + QueryDynamicBalanceResponse, + QueryPaymentAccountCountRequest, + QueryPaymentAccountCountResponse, + QueryPaymentAccountRequest, + QueryPaymentAccountResponse, + QueryPaymentAccountsByOwnerRequest, + QueryPaymentAccountsByOwnerResponse, QueryGetStreamRecordResponse, + QueryParamsByTimestampRequest, + QueryParamsByTimestampResponse, QueryParamsResponse, + QueryOutFlowsRequest, + QueryOutFlowsResponse, } from '@bnb-chain/greenfield-cosmos-types/greenfield/payment/query'; import { MsgDeposit, @@ -13,14 +33,17 @@ import { import { container, singleton } from 'tsyringe'; import { MsgDepositTypeUrl, MsgDisableRefundTypeUrl, MsgWithdrawTypeUrl, TxResponse } from '..'; import { Basic } from './basic'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; export interface IPayment { /** * retrieves stream record information for a given stream address. + * the account must actions: deposit, withdraw */ getStreamRecord(account: string): Promise; + getStreamRecordAll(request: QueryStreamRecordsRequest): Promise; + /** * deposits BNB to a stream account. */ @@ -37,6 +60,38 @@ export interface IPayment { disableRefund(msg: MsgDisableRefund): Promise; params(): Promise; + + paramsByTimestamp( + request: QueryParamsByTimestampRequest, + ): Promise; + + paymentAccount(request: QueryPaymentAccountRequest): Promise; + + paymentAccountAll(request: QueryPaymentAccountsRequest): Promise; + + /** Queries a PaymentAccountCount by index. */ + getPaymentAccountCount( + request: QueryPaymentAccountCountRequest, + ): Promise; + + /** Queries a list of PaymentAccountCount items. */ + getPaymentAccountCounts( + request: QueryPaymentAccountCountsRequest, + ): Promise; + + /** Queries a list of DynamicBalance items. */ + dynamicBalance(request: QueryDynamicBalanceRequest): Promise; + + /** Queries a list of GetPaymentAccountsByOwner items. */ + getPaymentAccountsByOwner( + request: QueryPaymentAccountsByOwnerRequest, + ): Promise; + + getAutoSettleRecords( + request: QueryAutoSettleRecordsRequest, + ): Promise; + + getOutFlows(request: QueryOutFlowsRequest): Promise; } @singleton() @@ -51,11 +106,61 @@ export class Payment implements IPayment { }); } + public async getStreamRecordAll(request: QueryStreamRecordsRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.StreamRecords(request); + } + public async params() { const rpc = await this.queryClient.getPaymentQueryClient(); return await rpc.Params(); } + public async paramsByTimestamp(request: QueryParamsByTimestampRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.ParamsByTimestamp(request); + } + + public async getPaymentAccountCount(request: QueryPaymentAccountCountRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.PaymentAccountCount(request); + } + + public async getPaymentAccountCounts(request: QueryPaymentAccountCountsRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.PaymentAccountCounts(request); + } + + public async paymentAccount(request: QueryPaymentAccountRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.PaymentAccount(request); + } + + public async paymentAccountAll(request: QueryPaymentAccountsRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.PaymentAccounts(request); + } + + public async dynamicBalance(request: QueryDynamicBalanceRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.DynamicBalance(request); + } + + public async getPaymentAccountsByOwner(request: QueryPaymentAccountsByOwnerRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.PaymentAccountsByOwner(request); + } + + public async getAutoSettleRecords(request: QueryAutoSettleRecordsRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.AutoSettleRecords(request); + } + + public async getOutFlows(request: QueryOutFlowsRequest) { + const rpc = await this.queryClient.getPaymentQueryClient(); + return await rpc.OutFlows(request); + } + public async deposit(msg: MsgDeposit) { return await this.basic.tx( MsgDepositTypeUrl, @@ -79,7 +184,7 @@ export class Payment implements IPayment { public async disableRefund(msg: MsgDisableRefund) { return await this.basic.tx( MsgDisableRefundTypeUrl, - msg.addr, + msg.owner, MsgDisableRefundSDKTypeEIP712, MsgDisableRefund.toSDK(msg), MsgDisableRefund.encode(msg).finish(), diff --git a/packages/chain-sdk/src/api/sp.ts b/packages/chain-sdk/src/api/sp.ts index 4b5dbc54..daa4732b 100644 --- a/packages/chain-sdk/src/api/sp.ts +++ b/packages/chain-sdk/src/api/sp.ts @@ -1,19 +1,41 @@ -import { getAuthorizationAuthTypeV2 } from '@/utils/auth'; -import { fetchWithTimeout, METHOD_GET, parseErrorXml } from '@/utils/http'; -import { QueryParamsResponse } from '@bnb-chain/greenfield-cosmos-types/greenfield/sp/query'; +import { encodePath, HTTPHeaderUserAddress } from '@/clients/spclient/auth'; +import { parseListGroupsResponse } from '@/clients/spclient/spApis/listGroups'; +import { parseListGroupsMembersResponse } from '@/clients/spclient/spApis/listGroupsMembers'; +import { parseListUserGroupsResponse } from '@/clients/spclient/spApis/listUserGroups'; +import { parseListUserOwnedGroupsResponse } from '@/clients/spclient/spApis/listUserOwnedGroups'; +import { parseError } from '@/clients/spclient/spApis/parseError'; +import { parseVerifyPermissionResponse } from '@/clients/spclient/spApis/verifyPermission'; +import { SpClient } from '@/clients/spclient/spClient'; +import { METHOD_GET, NORMAL_ERROR_CODE } from '@/constants/http'; +import { ListUserOwnedGroupsResponse } from '@/types/sp-xml/ListUserOwnedGroupsResponse'; +import { actionTypeFromJSON } from '@bnb-chain/greenfield-cosmos-types/greenfield/permission/common'; import { - SecondarySpStorePrice, - SpStoragePrice, - Status, - StorageProvider, -} from '@bnb-chain/greenfield-cosmos-types/greenfield/sp/types'; -import { SourceType } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/common'; -import { GroupInfo } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/types'; -import { Headers } from 'cross-fetch'; -import Long from 'long'; -import { container, delay, inject, singleton } from 'tsyringe'; + QueryGlobalSpStorePriceByTimeRequest, + QueryGlobalSpStorePriceByTimeResponse, + QueryParamsResponse, + QuerySpStoragePriceRequest, + QuerySpStoragePriceResponse, + QueryStorageProviderByOperatorAddressRequest, + QueryStorageProviderByOperatorAddressResponse, + QueryStorageProviderMaintenanceRecordsRequest, + QueryStorageProviderMaintenanceRecordsResponse, +} from '@bnb-chain/greenfield-cosmos-types/greenfield/sp/query'; +import { Status, StorageProvider } from '@bnb-chain/greenfield-cosmos-types/greenfield/sp/types'; +import { container, singleton } from 'tsyringe'; +import { + IObjectResultType, + ListGroupsMembersResponse, + ListGroupsResponse, + ListUserGroupsResponse, + TListGroups, + TListGroupsMembersRequest, + TListUserGroupRequest, + TListUserOwnedGroupRequest, + TVerifyPermissionRequest, + VerifyPermissionResponse, +} from '..'; +import { RpcQueryClient } from '../clients/queryclient'; import { Bucket } from './bucket'; -import { RpcQueryClient } from './queryclient'; import { VirtualGroup } from './virtualGroup'; export interface ISp { @@ -29,22 +51,54 @@ export interface ISp { getStorageProviderInfo(spId: number): Promise; /** - * returns the storage price for a particular storage provider, including update time, read price, store price and .etc. + * get the latest storage price of specific sp + */ + getQuerySpStoragePrice(request: QuerySpStoragePriceRequest): Promise; + + /** + * get global store price by time */ - getStoragePriceByTime(spAddress: string): Promise; + getQueryGlobalSpStorePriceByTime( + request: QueryGlobalSpStorePriceByTimeRequest, + ): Promise; /** - * returns the secondary storage price, including update time and store price + * Queries a StorageProvider by specify operator address. */ - getSecondarySpStorePrice(): Promise; + getStorageProviderByOperatorAddress( + request: QueryStorageProviderByOperatorAddressRequest, + ): Promise; + + /** + * Queries a StorageProvider by specify operator address. + */ + getStorageProviderMaintenanceRecordsByOperatorAddress( + request: QueryStorageProviderMaintenanceRecordsRequest, + ): Promise; params(): Promise; - listGroup(groupName: string, prefix: string, opts: ListGroupsOptions): Promise; + listGroups(params: TListGroups): Promise>; + + listGroupsMembers( + params: TListGroupsMembersRequest, + ): Promise>; + + listUserGroups(params: TListUserGroupRequest): Promise>; + + listUserOwnedGroups( + params: TListUserOwnedGroupRequest, + ): Promise>; getSPUrlByBucket(bucketName: string): Promise; getSPUrlByPrimaryAddr(parimaryAddr: string): Promise; + + getSPUrlById(primaryId: number): Promise; + + verifyPermission( + params: TVerifyPermissionRequest, + ): Promise>; } @singleton() @@ -52,6 +106,7 @@ export class Sp implements ISp { private bucket = container.resolve(Bucket); private queryClient = container.resolve(RpcQueryClient); private virtualGroup = container.resolve(VirtualGroup); + private spClient = container.resolve(SpClient); public async getStorageProviders() { const rpc = await this.queryClient.getSpQueryClient(); @@ -67,6 +122,35 @@ export class Sp implements ISp { return res.storageProvider; } + public async getQuerySpStoragePrice(request: QuerySpStoragePriceRequest) { + const rpc = await this.queryClient.getSpQueryClient(); + return await rpc.QuerySpStoragePrice(request); + } + + public async getQueryGlobalSpStorePriceByTime(request: QueryGlobalSpStorePriceByTimeRequest) { + const rpc = await this.queryClient.getSpQueryClient(); + return await rpc.QueryGlobalSpStorePriceByTime(request); + } + + public async getStorageProviderByOperatorAddress( + request: QueryStorageProviderByOperatorAddressRequest, + ) { + const rpc = await this.queryClient.getSpQueryClient(); + return await rpc.StorageProviderByOperatorAddress(request); + } + + public async getStorageProviderMaintenanceRecordsByOperatorAddress( + request: QueryStorageProviderMaintenanceRecordsRequest, + ) { + const rpc = await this.queryClient.getSpQueryClient(); + return await rpc.StorageProviderMaintenanceRecordsByOperatorAddress(request); + } + + public async getSPUrlById(primaryId: number) { + const spList = await this.getStorageProviders(); + return spList.filter((sp) => sp.id === primaryId)[0].endpoint; + } + public async getSPUrlByBucket(bucketName: string) { const { bucketInfo } = await this.bucket.headBucket(bucketName); @@ -87,23 +171,6 @@ export class Sp implements ISp { return sps.filter((sp) => sp.operatorAddress === parimaryAddr)[0].endpoint; } - public async getStoragePriceByTime(spAddress: string) { - const rpc = await this.queryClient.getSpQueryClient(); - const res = await rpc.QueryGetSpStoragePriceByTime({ - timestamp: Long.fromNumber(0), - spAddr: spAddress, - }); - return res.spStoragePrice; - } - - public async getSecondarySpStorePrice() { - const rpc = await this.queryClient.getSpQueryClient(); - const res = await rpc.QueryGetSecondarySpStorePriceByTime({ - timestamp: Long.fromNumber(0), - }); - return res.secondarySpStorePrice; - } - public async params() { const rpc = await this.queryClient.getSpQueryClient(); return await rpc.Params(); @@ -116,74 +183,266 @@ export class Sp implements ISp { return spList[0]; } - public async listGroup(groupName: string, prefix: string, opts: ListGroupsOptions) { - const MaximumGetGroupListLimit = 1000; - const MaximumGetGroupListOffset = 100000; - const DefaultGetGroupListLimit = 50; + public async listGroups(params: TListGroups) { + try { + const { name, prefix, sourceType, limit, offset } = params; - const res: ListGroupsResult = { - groups: [], - count: '0', - }; + let res: ListGroupsResponse = { + GfSpGetGroupListResponse: { + Groups: [], + Count: 0, + }, + }; - if (groupName === '' || prefix === '') { - return res; + if (name === '' || prefix === '') { + return { + code: 0, + message: 'success', + body: res, + }; + } + + const sp = await this.getInServiceSP(); + const url = `${sp.endpoint}?group-query=null&name=${name}&prefix=${prefix}&source-type=${sourceType}&limit=${limit}&offset=${offset}`; + + const result = await this.spClient.callApi( + url, + { + headers: {}, + method: METHOD_GET, + }, + 3000, + ); + const { status } = result; + if (!result.ok) { + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); + throw { + code: code || -1, + message: message || 'error', + statusCode: status, + }; + } + + const xmlData = await result.text(); + res = await parseListGroupsResponse(xmlData); + + return { + code: 0, + message: 'success', + statusCode: status, + body: res, + }; + } catch (error: any) { + return { + code: -1, + message: error.message, + statusCode: error?.statusCode || NORMAL_ERROR_CODE, + }; } + } - if (opts.limit < 0) { - return res; - } else if (opts.limit > MaximumGetGroupListLimit) { - opts.limit = MaximumGetGroupListLimit; - } else if (opts.limit === 0) { - opts.limit = DefaultGetGroupListLimit; + public async verifyPermission(params: TVerifyPermissionRequest) { + try { + const { action, bucketName, objectName, operator } = params; + + const sp = await this.getInServiceSP(); + let url = `${sp.endpoint}/permission/${operator}/${bucketName}/${actionTypeFromJSON(action)}`; + + if (objectName) { + url += `?object=${encodePath(objectName)}`; + } + + const result = await this.spClient.callApi( + url, + { + headers: {}, + method: METHOD_GET, + }, + 3000, + ); + const { status } = result; + if (!result.ok) { + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); + throw { + code: code || -1, + message: message || 'error', + statusCode: status, + }; + } + + const xmlData = await result.text(); + const res = await parseVerifyPermissionResponse(xmlData); + + return { + code: 0, + message: 'success', + statusCode: status, + body: res, + }; + } catch (error: any) { + return { + code: -1, + message: error.message, + statusCode: error?.statusCode || NORMAL_ERROR_CODE, + }; } + } - if (opts.offset < 0 || opts.offset > MaximumGetGroupListOffset) { - return res; + public async listGroupsMembers(params: TListGroupsMembersRequest) { + try { + const { groupId, limit, startAfter } = params; + const sp = await this.getInServiceSP(); + let url = `${sp.endpoint}?group-members&group-id=${groupId}`; + + if (limit) { + url += `&limit=${limit}`; + } + if (startAfter) { + url += `&start-after=${startAfter}`; + } + + const result = await this.spClient.callApi( + url, + { + headers: {}, + method: METHOD_GET, + }, + 3000, + ); + const { status } = result; + if (!result.ok) { + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); + throw { + code: code || -1, + message: message || 'error', + statusCode: status, + }; + } + + const xmlData = await result.text(); + const res = await parseListGroupsMembersResponse(xmlData); + + return { + code: 0, + message: 'success', + statusCode: status, + body: res, + }; + } catch (error: any) { + return { + code: -1, + message: error.message, + statusCode: error?.statusCode || NORMAL_ERROR_CODE, + }; } + } - let headerContent: Record = {}; - const sp = await this.getInServiceSP(); - headerContent = { - ...headerContent, - }; - const url = `${sp.endpoint}?group-query&name=${groupName}&prefix=${prefix}&source-type=${opts.sourceType}&limit=${opts.limit}&offset=${opts.offset}`; - - const headers = new Headers(headerContent); - const result = await fetchWithTimeout(url, { - headers, - method: METHOD_GET, - }); - - const { status } = result; - if (!result.ok) { - const { code, message } = await parseErrorXml(result); - throw { - code: code || -1, - message: message || 'Get group list error.', + public async listUserGroups(params: TListUserGroupRequest) { + try { + const { address, limit, startAfter } = params; + const sp = await this.getInServiceSP(); + let url = `${sp.endpoint}?user-groups`; + + if (limit) { + url += `&limit=${limit}`; + } + if (startAfter) { + url += `&start-after=${startAfter}`; + } + + const headers = new Headers({ + [HTTPHeaderUserAddress]: address, + }); + const result = await this.spClient.callApi( + url, + { + headers, + method: METHOD_GET, + }, + 3000, + ); + const { status } = result; + if (!result.ok) { + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); + throw { + code: code || -1, + message: message || 'error', + statusCode: status, + }; + } + + const xmlData = await result.text(); + const res = await parseListUserGroupsResponse(xmlData); + + return { + code: 0, + message: 'success', statusCode: status, + body: res, + }; + } catch (error: any) { + return { + code: -1, + message: error.message, + statusCode: error?.statusCode || NORMAL_ERROR_CODE, }; } + } - return await result.json(); + public async listUserOwnedGroups(params: TListUserOwnedGroupRequest) { + try { + const { address, limit, startAfter } = params; + const sp = await this.getInServiceSP(); + let url = `${sp.endpoint}?owned-groups`; + + if (limit) { + url += `&limit=${limit}`; + } + if (startAfter) { + url += `&start-after=${startAfter}`; + } + + const headers = new Headers({ + [HTTPHeaderUserAddress]: address, + }); + const result = await this.spClient.callApi( + url, + { + headers, + method: METHOD_GET, + }, + 3000, + ); + const { status } = result; + if (!result.ok) { + const xmlError = await result.text(); + const { code, message } = await parseError(xmlError); + throw { + code: code || -1, + message: message || 'error', + statusCode: status, + }; + } + + const xmlData = await result.text(); + const res = await parseListUserOwnedGroupsResponse(xmlData); + + return { + code: 0, + message: 'success', + statusCode: status, + body: res, + }; + } catch (error: any) { + return { + code: -1, + message: error.message, + statusCode: error?.statusCode || NORMAL_ERROR_CODE, + }; + } } } - -type ListGroupsOptions = { - sourceType: keyof typeof SourceType; - limit: number; - offset: number; -}; - -type ListGroupsResult = { - groups: { - group: GroupInfo; - operator: string; - create_at: number; - create_time: number; - update_at: number; - update_time: number; - removed: boolean; - }[]; - count: string; -}; diff --git a/packages/chain-sdk/src/api/storage.ts b/packages/chain-sdk/src/api/storage.ts index 20ab33f9..6071c957 100644 --- a/packages/chain-sdk/src/api/storage.ts +++ b/packages/chain-sdk/src/api/storage.ts @@ -1,6 +1,11 @@ import { MsgDeletePolicySDKTypeEIP712 } from '@/messages/greenfield/storage/MsgDeletePolicy'; import { MsgPutPolicySDKTypeEIP712 } from '@/messages/greenfield/storage/MsgPutPolicy'; import { + QueryGroupMembersExistRequest, + QueryGroupMembersExistResponse, + QueryGroupsExistByIdRequest, + QueryGroupsExistRequest, + QueryGroupsExistResponse, QueryLockFeeRequest, QueryLockFeeResponse, QueryParamsResponse, @@ -16,9 +21,9 @@ import { MsgPutPolicy, } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; import { container, delay, inject, singleton } from 'tsyringe'; -import { MsgDeletePolicyTypeUrl, MsgPutPolicyTypeUrl, TxResponse } from '..'; +import { fromTimestamp, MsgDeletePolicyTypeUrl, MsgPutPolicyTypeUrl, TxResponse } from '..'; import { Basic } from './basic'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; export interface IStorage { params(): Promise; @@ -38,6 +43,14 @@ export interface IStorage { getQueryPolicyById(request: QueryPolicyByIdRequest): Promise; queryLockFee(request: QueryLockFeeRequest): Promise; + + queryGroupMembersExist( + request: QueryGroupMembersExistRequest, + ): Promise; + + queryGroupExist(request: QueryGroupsExistRequest): Promise; + + queryGroupsExistById(request: QueryGroupsExistByIdRequest): Promise; } @singleton() @@ -58,7 +71,7 @@ export class Storage implements IStorage { MsgPutPolicySDKTypeEIP712, { ...toSdk, - expiration_time: '', + expiration_time: msg.expirationTime ? fromTimestamp(msg.expirationTime) : '', statements: toSdk.statements.map((e) => { // @ts-ignore e.expiration_time = ''; @@ -103,4 +116,19 @@ export class Storage implements IStorage { const rpc = await this.queryClient.getStorageQueryClient(); return await rpc.QueryLockFee(request); } + + public async queryGroupMembersExist(request: QueryGroupMembersExistRequest) { + const rpc = await this.queryClient.getStorageQueryClient(); + return await rpc.QueryGroupMembersExist(request); + } + + public async queryGroupExist(request: QueryGroupsExistRequest) { + const rpc = await this.queryClient.getStorageQueryClient(); + return await rpc.QueryGroupsExist(request); + } + + public async queryGroupsExistById(request: QueryGroupsExistByIdRequest) { + const rpc = await this.queryClient.getStorageQueryClient(); + return await rpc.QueryGroupsExistById(request); + } } diff --git a/packages/chain-sdk/src/api/virtualGroup.ts b/packages/chain-sdk/src/api/virtualGroup.ts index 88d3ab56..a8d95ee2 100644 --- a/packages/chain-sdk/src/api/virtualGroup.ts +++ b/packages/chain-sdk/src/api/virtualGroup.ts @@ -10,7 +10,7 @@ import { QueryParamsResponse, } from '@bnb-chain/greenfield-cosmos-types/greenfield/virtualgroup/query'; import { container, singleton } from 'tsyringe'; -import { RpcQueryClient } from './queryclient'; +import { RpcQueryClient } from '../clients/queryclient'; export interface IVirtualGroup { params(): Promise; diff --git a/packages/chain-sdk/src/client.ts b/packages/chain-sdk/src/client.ts index 2f1b09e9..4803c1b4 100644 --- a/packages/chain-sdk/src/client.ts +++ b/packages/chain-sdk/src/client.ts @@ -14,14 +14,31 @@ import { IOffChainAuth, OffChainAuth } from './api/offchainauth'; import { IStorage, Storage } from './api/storage'; import { Basic, IBasic } from './api/basic'; import { Gashub, IGashub } from './api/gashub'; -import { RpcQueryClient } from './api/queryclient'; +import { RpcQueryClient } from './clients/queryclient'; import { IVirtualGroup, VirtualGroup } from './api/virtualGroup'; +import { ISpClient, SpClient } from './clients/spclient/spClient'; @injectable() export class Client { - static create(rpcUrl: string, chainId: string): Client { + /** + * @rpcUrl string + * @chaidId string + * @wasmURL optional, need setting only used for browser + */ + static create( + rpcUrl: string, + chainId: string, + wasmURL?: { + zkCryptoUrl?: string; + }, + ): Client { container.register('RPC_URL', { useValue: rpcUrl }); container.register('CHAIN_ID', { useValue: chainId }); + container.register('ZK_CRYPTO', { useValue: wasmURL?.zkCryptoUrl }); + + if (wasmURL?.zkCryptoUrl) { + (globalThis as any).__PUBLIC_ZKCRYPTO_WASM_PATH__ = wasmURL.zkCryptoUrl; + } const account = container.resolve(Account); const basic = container.resolve(Basic); @@ -36,6 +53,7 @@ export class Client { const payment = container.resolve(Payment); const queryClient = container.resolve(RpcQueryClient); const sp = container.resolve(Sp); + const spClient = container.resolve(SpClient); const storage = container.resolve(Storage); const offchainauth = container.resolve(OffChainAuth); const virtualGroup = container.resolve(VirtualGroup); @@ -54,6 +72,7 @@ export class Client { payment, queryClient, sp, + spClient, storage, offchainauth, virtualGroup, @@ -74,6 +93,7 @@ export class Client { public payment: IPayment, public queryClient: RpcQueryClient, public sp: ISp, + public spClient: ISpClient, public storage: IStorage, public offchainauth: IOffChainAuth, public virtualGroup: IVirtualGroup, diff --git a/packages/chain-sdk/src/api/queryclient.ts b/packages/chain-sdk/src/clients/queryclient.ts similarity index 100% rename from packages/chain-sdk/src/api/queryclient.ts rename to packages/chain-sdk/src/clients/queryclient.ts diff --git a/packages/chain-sdk/src/clients/spclient/auth.ts b/packages/chain-sdk/src/clients/spclient/auth.ts new file mode 100644 index 00000000..bb38700e --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/auth.ts @@ -0,0 +1,212 @@ +import { signSignatureByEddsa } from '@/offchainauth'; +import { ReqMeta } from '@/types/auth'; +import { hexlify, joinSignature } from '@ethersproject/bytes'; +import { SigningKey } from '@ethersproject/signing-key'; +import { Headers } from 'cross-fetch'; +import { keccak256 } from 'ethereum-cryptography/keccak.js'; +import { utf8ToBytes } from 'ethereum-cryptography/utils.js'; +import { AuthType } from './spClient'; + +export const getCanonicalHeaders = (reqMeta: Partial, reqHeaders: Headers) => { + const sortedHeaders = getSortedHeaders(reqHeaders, SUPPORTED_HEADERS); + + const res: string[] = []; + sortedHeaders.forEach((k) => { + const v = reqHeaders.get(k); + res.push(`${k}:${v}`); + }); + + if (reqMeta.url && reqMeta.url.hostname) { + res.push(reqMeta.url.hostname); + } + + res.push(''); + return res.join('\n'); +}; + +const getSortedHeaders = (reqHeaders: Headers, supportHeaders: string[]) => { + const signedHeaders: string[] = []; + + reqHeaders.forEach((v, k) => { + if (supportHeaders.includes(k)) { + signedHeaders.push(k); + } + }); + + return signedHeaders.sort(); +}; + +const getSignedHeaders = (reqHeaders: Headers) => { + const sortedHeaders = getSortedHeaders(reqHeaders, SUPPORTED_HEADERS); + + return sortedHeaders.join(';'); +}; + +export const getAuthorization = async ( + reqMeta: Partial, + reqHeaders: Headers, + authType: AuthType, +) => { + const canonicalHeaders = getCanonicalHeaders(reqMeta, reqHeaders); + const signedHeaders = getSignedHeaders(reqHeaders); + + const canonicalRequestArr = [ + reqMeta.method, + reqMeta.url?.path, + reqMeta.url?.query, + canonicalHeaders, + signedHeaders, + ]; + + const canonicalRequest = canonicalRequestArr.join('\n'); + // console.log('canonicalRequest', canonicalRequest); + + const unsignedMsg = getMsgToSign(utf8ToBytes(canonicalRequest)); + let authorization = ''; + if (authType.type === 'ECDSA') { + const sig = secpSign(unsignedMsg, authType.privateKey); + authorization = `GNFD1-ECDSA, Signature=${sig.slice(2)}`; + } else { + const sig = await signSignatureByEddsa(authType.seed, hexlify(unsignedMsg).slice(2)); + authorization = `GNFD1-EDDSA,Signature=${sig}`; + } + + return authorization; +}; + +export const newRequestHeadersByMeta = (meta: Partial) => { + const headers = new Headers(); + if (meta.contentType) { + headers.set(HTTPHeaderContentType.toLocaleLowerCase(), meta.contentType); + } + + if (meta.txnHash && meta.txnHash !== '') { + headers.set(HTTPHeaderTransactionHash.toLocaleLowerCase(), meta.txnHash); + } + + if (meta.contentSHA256) { + headers.set(HTTPHeaderContentSHA256.toLocaleLowerCase(), meta.contentSHA256); + } + + if (meta.unsignMsg) { + headers.set(HTTPHeaderUnsignedMsg.toLocaleLowerCase(), meta.unsignMsg); + } + + const date = new Date(); + // NOTICE: Smoothing local and server time gap + date.setSeconds(date.getSeconds() + 200); + headers.set(HTTPHeaderDate.toLocaleLowerCase(), formatDate(date)); + + date.setDate(date.getDate() + 6); + headers.set(HTTPHeaderExpiryTimestamp.toLocaleLowerCase(), formatDate(date)); + + return headers; +}; + +function formatDate(date: Date): string { + const res = date.toISOString(); + return res.replace(/\.\d{3}/gi, ''); +} + +export const HTTPHeaderAuthorization = 'Authorization'; +export const HTTPHeaderContentSHA256 = 'X-Gnfd-Content-Sha256'; +export const HTTPHeaderTransactionHash = 'X-Gnfd-Txn-Hash'; +export const HTTPHeaderObjectID = 'X-Gnfd-Object-ID'; +export const HTTPHeaderRedundancyIndex = 'X-Gnfd-Redundancy-Index'; +export const HTTPHeaderResource = 'X-Gnfd-Resource'; +export const HTTPHeaderDate = 'X-Gnfd-Date'; +export const HTTPHeaderExpiryTimestamp = 'X-Gnfd-Expiry-Timestamp'; +export const HTTPHeaderRange = 'Range'; +export const HTTPHeaderPieceIndex = 'X-Gnfd-Piece-Index'; +export const HTTPHeaderContentType = 'Content-Type'; +export const HTTPHeaderContentMD5 = 'Content-MD5'; +export const HTTPHeaderUnsignedMsg = 'X-Gnfd-Unsigned-Msg'; +export const HTTPHeaderUserAddress = 'X-Gnfd-User-Address'; +// const HTTPHeaderAppDomain = 'X-Gnfd-App-Domain'; + +const SUPPORTED_HEADERS = [ + HTTPHeaderContentSHA256.toLocaleLowerCase(), + HTTPHeaderTransactionHash.toLocaleLowerCase(), + HTTPHeaderObjectID.toLocaleLowerCase(), + HTTPHeaderRedundancyIndex.toLocaleLowerCase(), + HTTPHeaderResource.toLocaleLowerCase(), + HTTPHeaderDate.toLocaleLowerCase(), + HTTPHeaderExpiryTimestamp.toLocaleLowerCase(), + HTTPHeaderRange.toLocaleLowerCase(), + HTTPHeaderPieceIndex.toLocaleLowerCase(), + HTTPHeaderContentType.toLocaleLowerCase(), + HTTPHeaderContentMD5.toLocaleLowerCase(), + HTTPHeaderUnsignedMsg.toLocaleLowerCase(), + HTTPHeaderUserAddress.toLocaleLowerCase(), + // HTTPHeaderAppDomain.toLocaleLowerCase(), +]; + +// https://github.com/ethers-io/ethers.js/discussions/4339 +export const secpSign = (digestBz: Uint8Array, privateKey: string) => { + const signingKey = new SigningKey(privateKey); + const signature = signingKey.signDigest(digestBz); + let res = joinSignature(signature); + + const v = res.slice(-2); + if (v === '1c') res = res.slice(0, -2) + '01'; + if (v === '1b') res = res.slice(0, -2) + '00'; + + return res; +}; + +export const getMsgToSign = (unsignedBytes: Uint8Array): Uint8Array => { + const res = keccak256(unsignedBytes); + return res; +}; + +export const encodePath = (pathName: string) => { + const reservedNames = /^[a-zA-Z0-9-_.~/]+$/; + if (reservedNames.test(pathName)) { + return pathName; + } + + let encodedPathName = ''; + for (let i = 0; i < pathName.length; i++) { + const s = pathName[i]; + + // soft characters + if (('A' <= s && s <= 'Z') || ('a' <= s && s <= 'z') || ('0' <= s && s <= '9')) { + encodedPathName += s; + continue; + } + + switch (s) { + // special characters are allowed + case '-': + case '_': + case '.': + case '~': + case '/': + encodedPathName += s; + continue; + + // others characters need to be encoded + default: + // . ! @ # $ % ^ & * ) ( - + = { } [ ] / " , ' < > ~ \ .` ? : ; | \\ + if (/[.!@#\$%\^&\*\)\(\-+=\{\}\[\]\/\",'<>~\·`\?:;|\\]+$/.test(s)) { + // english characters + const hexStr = s.charCodeAt(0).toString(16); + encodedPathName += '%' + hexStr.toUpperCase(); + } else { + // others characters + encodedPathName += encodeURI(s); + } + } + } + return encodedPathName; +}; + +export const getSortQuery = (queryMap: Record) => { + const queryParams = new URLSearchParams(); + for (const k in queryMap) { + queryParams.append(k, queryMap[k]); + } + queryParams.sort(); + + return queryParams.toString(); +}; diff --git a/packages/chain-sdk/src/offchainauth/sign.ts b/packages/chain-sdk/src/clients/spclient/sign.ts similarity index 68% rename from packages/chain-sdk/src/offchainauth/sign.ts rename to packages/chain-sdk/src/clients/spclient/sign.ts index 8c4517df..6bc7439c 100644 --- a/packages/chain-sdk/src/offchainauth/sign.ts +++ b/packages/chain-sdk/src/clients/spclient/sign.ts @@ -1,12 +1,21 @@ import { hexlify, arrayify } from '@ethersproject/bytes'; import { toUtf8Bytes } from '@ethersproject/strings'; -import { TGetCurrentSeedStringParams } from '../types/storage'; +import { TGetCurrentSeedStringParams } from '../../types/storage'; +import { getEddsaCompressedPublicKey, eddsaSign } from '@bnb-chain/greenfield-zk-crypto'; -const getCurrentAccountPublicKey = (seedString: string) => - (window as any).getEddsaCompressedPublicKey(seedString); +const getCurrentAccountPublicKey = async (seedString: string) => { + if ((window as any).getEddsaCompressedPublicKey) { + return (window as any).getEddsaCompressedPublicKey(seedString); + } + return await getEddsaCompressedPublicKey(seedString); +}; -const signSignatureByEddsa = (seedString: string, message: string) => - (window as any).eddsaSign(seedString, message); +const signSignatureByEddsa = async (seedString: string, message: string) => { + if ((window as any).eddsaSign) { + return (window as any).eddsaSign(seedString, message); + } + return await eddsaSign(seedString, message); +}; const signMessagePersonalAPI = async ( provider: any, diff --git a/packages/chain-sdk/src/clients/spclient/spApis/bucketApproval.ts b/packages/chain-sdk/src/clients/spclient/spApis/bucketApproval.ts new file mode 100644 index 00000000..9d6ca5fd --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/bucketApproval.ts @@ -0,0 +1,36 @@ +import { EMPTY_STRING_SHA256, METHOD_GET } from '@/constants'; +import { ICreateBucketMsgType, ReqMeta } from '@/types'; +import { toHex, utf8ToBytes } from 'ethereum-cryptography/utils'; +import { getSortQuery } from '../auth'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/get_approval +export const getBucketApprovalMetaInfo = async (endpoint: string, msg: ICreateBucketMsgType) => { + const path = '/greenfield/admin/v1/get-approval'; + const queryMap = { + action: 'CreateBucket', + }; + const query = getSortQuery(queryMap); + const url = `${endpoint}${path}?${query}`; + const unSignedMessageInHex = toHex(utf8ToBytes(JSON.stringify(msg))); + + const reqMeta: Partial = { + contentSHA256: EMPTY_STRING_SHA256, + unsignMsg: unSignedMessageInHex, + method: METHOD_GET, + url: { + hostname: new URL(endpoint).hostname, + query, + path, + }, + }; + + const optionsWithOutHeaders: Omit = { + method: METHOD_GET, + }; + + return { + url, + optionsWithOutHeaders, + reqMeta, + }; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/getBucketMeta.ts b/packages/chain-sdk/src/clients/spclient/spApis/getBucketMeta.ts new file mode 100644 index 00000000..129eb7ea --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/getBucketMeta.ts @@ -0,0 +1,21 @@ +import { GetBucketMetaResponse } from '@/types'; +import { formatBucketInfo } from '@/types/sp-xml/Common'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/get_bucket_meta +export const parseGetBucketMetaResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as GetBucketMetaResponse; + + res.GfSpGetBucketMetaResponse.Bucket = { + ...res.GfSpGetBucketMetaResponse.Bucket, + BucketInfo: formatBucketInfo(res.GfSpGetBucketMetaResponse.Bucket.BucketInfo), + DeleteAt: Number(res.GfSpGetBucketMetaResponse.Bucket.DeleteAt), + UpdateAt: Number(res.GfSpGetBucketMetaResponse.Bucket.UpdateAt), + UpdateTime: Number(res.GfSpGetBucketMetaResponse.Bucket.UpdateTime), + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/getNonce.ts b/packages/chain-sdk/src/clients/spclient/spApis/getNonce.ts new file mode 100644 index 00000000..886f4552 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/getNonce.ts @@ -0,0 +1,39 @@ +import { IFetchNonce, RequestNonceResponse } from '@/types'; +import { fetchWithTimeout } from '@/utils/http'; +import { XMLParser } from 'fast-xml-parser'; +import { Headers } from 'cross-fetch'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/get_nonce +export const getNonce = async ({ spEndpoint, spName, spAddress, address, domain }: IFetchNonce) => { + let result; + let res; + const url = `${spEndpoint}/auth/request_nonce`; + const headers = new Headers({ + 'X-Gnfd-User-Address': address, + 'X-Gnfd-App-Domain': domain, + }); + try { + result = await fetchWithTimeout(url, { + headers, + }); + if (!result.ok) { + return { code: -1, nonce: null }; + } + + const data = await result.text(); + + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + res = xmlParser.parse(data) as RequestNonceResponse; + } catch (error) { + return { code: -1, nonce: null }; + } + + return { + endpoint: spEndpoint, + address: spAddress, + name: spName, + nonce: res.RequestNonceResp.NextNonce, + }; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/getObject.ts b/packages/chain-sdk/src/clients/spclient/spApis/getObject.ts new file mode 100644 index 00000000..e789bda6 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/getObject.ts @@ -0,0 +1,39 @@ +import { EMPTY_STRING_SHA256, METHOD_GET } from '@/constants'; +import { ReqMeta } from '@/types'; +import { generateUrlByBucketName } from '@/utils/s3'; +import { encodePath } from '../auth'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/get_object +export const getGetObjectMetaInfo = async ( + endpoint: string, + params: { + objectName: string; + bucketName: string; + }, +) => { + const { bucketName, objectName } = params; + const path = `/${encodePath(objectName)}`; + const query = ''; + const url = generateUrlByBucketName(endpoint, bucketName) + path; + + const reqMeta: Partial = { + contentSHA256: EMPTY_STRING_SHA256, + method: METHOD_GET, + url: { + hostname: new URL(url).hostname, + query, + path, + }, + contentType: 'application/octet-stream', + }; + + const optionsWithOutHeaders: Omit = { + method: METHOD_GET, + }; + + return { + url, + optionsWithOutHeaders, + reqMeta, + }; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/getObjectMeta.ts b/packages/chain-sdk/src/clients/spclient/spApis/getObjectMeta.ts new file mode 100644 index 00000000..3d625753 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/getObjectMeta.ts @@ -0,0 +1,28 @@ +import { convertStrToBool, formatObjectInfo } from '@/types/sp-xml/Common'; +import { GetObjectMetaResponse } from '@/types/sp-xml/GetObjectMetaResponse'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/get_object_meta +export const parseGetObjectMetaResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as GetObjectMetaResponse; + + const Object = res.GfSpGetObjectMetaResponse.Object || {}; + if (Object) { + // @ts-ignore + Object.Removed = convertStrToBool(Object.Removed); + Object.UpdateAt = Number(Object.UpdateAt); + Object.DeleteAt = Number(Object.DeleteAt); + + Object.ObjectInfo = formatObjectInfo(Object.ObjectInfo); + } + + res.GfSpGetObjectMetaResponse = { + ...res.GfSpGetObjectMetaResponse, + Object, + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/getUserBuckets.ts b/packages/chain-sdk/src/clients/spclient/spApis/getUserBuckets.ts new file mode 100644 index 00000000..21f62149 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/getUserBuckets.ts @@ -0,0 +1,36 @@ +import { GetUserBucketsResponse } from '@/types'; +import { convertStrToBool, formatBucketInfo } from '@/types/sp-xml/Common'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/get_user_buckets +export const parseGetUserBucketsResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as GetUserBucketsResponse; + + let Buckets = res.GfSpGetUserBucketsResponse.Buckets || []; + if (Buckets) { + if (!Array.isArray(Buckets)) { + Buckets = [Buckets]; + } + + Buckets = Buckets.map((item) => { + return { + ...item, + BucketInfo: formatBucketInfo(item.BucketInfo), + // @ts-ignore + Removed: convertStrToBool(item.Removed), + DeleteAt: Number(item.DeleteAt), + UpdateAt: Number(item.UpdateAt), + UpdateTime: Number(item.UpdateTime), + }; + }); + } + + res.GfSpGetUserBucketsResponse = { + Buckets, + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/listBucketReadRecords.ts b/packages/chain-sdk/src/clients/spclient/spApis/listBucketReadRecords.ts new file mode 100644 index 00000000..ce80b6f3 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/listBucketReadRecords.ts @@ -0,0 +1,69 @@ +import { EMPTY_STRING_SHA256, METHOD_GET } from '@/constants'; +import { ReqMeta, TListBucketReadRecord } from '@/types'; +import { formatReadRecord } from '@/types/sp-xml/Common'; +import { ListBucketReadRecordResponse } from '@/types/sp-xml/ListBucketReadRecordResponse'; +import { generateUrlByBucketName } from '@/utils'; +import { XMLParser } from 'fast-xml-parser'; +import { getSortQuery } from '../auth'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/list_bucket_read_records +export const parseListBucketReadRecordResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + + const res = xmlParser.parse(data) as ListBucketReadRecordResponse; + + let readRecords = res.GetBucketReadQuotaResult?.ReadRecords || []; + if (readRecords) { + if (!Array.isArray(readRecords)) { + readRecords = [readRecords]; + } + + readRecords = readRecords.map((readRecord) => formatReadRecord(readRecord)); + } + + res.GetBucketReadQuotaResult = { + ...res.GetBucketReadQuotaResult, + ReadRecords: readRecords, + }; + + return res; +}; + +export const getListBucketReadRecordMetaInfo = async ( + endpoint: string, + params: TListBucketReadRecord, +) => { + const { bucketName, endTimeStamp, maxRecords, startTimeStamp } = params; + const path = '/'; + const queryMap = { + 'list-read-record': 'null', + 'end-timestamp': String(endTimeStamp), + 'max-records': String(maxRecords), + 'start-timestamp': String(startTimeStamp), + }; + const query = getSortQuery(queryMap); + + const url = `${generateUrlByBucketName(endpoint, bucketName)}${path}?${query}`; + + const reqMeta: Partial = { + contentSHA256: EMPTY_STRING_SHA256, + method: METHOD_GET, + url: { + hostname: new URL(url).hostname, + query, + path, + }, + }; + + const optionsWithOutHeaders: Omit = { + method: METHOD_GET, + }; + + return { + url, + optionsWithOutHeaders, + reqMeta, + }; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/listBucketsByIds.ts b/packages/chain-sdk/src/clients/spclient/spApis/listBucketsByIds.ts new file mode 100644 index 00000000..72041ac8 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/listBucketsByIds.ts @@ -0,0 +1,45 @@ +import { ListBucketsByIDsResponse } from '@/types'; +import { convertStrToBool, formatBucketInfo } from '@/types/sp-xml/Common'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/list_buckets_by_ids +export const parseListBucketsByIdsResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as ListBucketsByIDsResponse; + + let BucketEntry = res.GfSpListBucketsByIDsResponse.BucketEntry || []; + if (BucketEntry) { + if (!Array.isArray(BucketEntry)) { + BucketEntry = [BucketEntry]; + } + + BucketEntry = BucketEntry.map((item) => { + let Value = item.Value; + if (Value) { + Value = { + ...item.Value, + BucketInfo: formatBucketInfo(item.Value.BucketInfo), + // @ts-ignore + Removed: convertStrToBool(item.Value.Removed), + UpdateAt: Number(item.Value.UpdateAt), + DeleteAt: Number(item.Value.DeleteAt), + }; + } + + return { + ...item, + Id: Number(item.Id), + Value, + }; + }); + } + + res.GfSpListBucketsByIDsResponse = { + ...res.GfSpListBucketsByIDsResponse, + BucketEntry, + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/listGroups.ts b/packages/chain-sdk/src/clients/spclient/spApis/listGroups.ts new file mode 100644 index 00000000..5a4f2170 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/listGroups.ts @@ -0,0 +1,39 @@ +import { ListGroupsResponse } from '@/types'; +import { formatGroupInfo, convertStrToBool } from '@/types/sp-xml/Common'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/get_group_list +export const parseListGroupsResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + + const res = xmlParser.parse(data) as ListGroupsResponse; + + let Groups = res.GfSpGetGroupListResponse.Groups || []; + if (Groups) { + if (!Array.isArray(Groups)) { + Groups = [Groups]; + } + + Groups = Groups.map((item) => { + return { + ...item, + CreateAt: Number(item.CreateAt), + CreateTime: Number(item.CreateTime), + UpdateAt: Number(item.UpdateAt), + UpdateTime: Number(item.UpdateTime), + // @ts-ignore + Removed: convertStrToBool(item.Removed), + Group: formatGroupInfo(item.Group), + }; + }); + } + + res.GfSpGetGroupListResponse = { + Groups: Groups, + Count: Number(res.GfSpGetGroupListResponse.Count), + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/listGroupsMembers.ts b/packages/chain-sdk/src/clients/spclient/spApis/listGroupsMembers.ts new file mode 100644 index 00000000..bc3e76cf --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/listGroupsMembers.ts @@ -0,0 +1,33 @@ +import { ListGroupsMembersResponse } from '@/types'; +import { formatGroupInfo, convertStrToBool } from '@/types/sp-xml/Common'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/list_group_members +export const parseListGroupsMembersResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as ListGroupsMembersResponse; + + let Groups = res.GfSpGetGroupMembersResponse.Groups || []; + if (Groups) { + if (!Array.isArray(Groups)) { + Groups = [Groups]; + } + + Groups = Groups.map((item) => { + return { + ...item, + CreateAt: Number(item.CreateAt), + CreateTime: Number(item.CreateTime), + UpdateAt: Number(item.UpdateAt), + UpdateTime: Number(item.UpdateTime), + // @ts-ignore + Removed: convertStrToBool(item.Removed), + Group: formatGroupInfo(item.Group), + }; + }); + } + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/listObjectsByBucket.ts b/packages/chain-sdk/src/clients/spclient/spApis/listObjectsByBucket.ts new file mode 100644 index 00000000..0a381fce --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/listObjectsByBucket.ts @@ -0,0 +1,46 @@ +import { convertStrToBool, formatObjectInfo } from '@/types/sp-xml/Common'; +import { ListObjectsByBucketNameResponse } from '@/types/sp-xml/ListObjectsByBucketNameResponse'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/list_objects_by_bucket +export const parseListObjectsByBucketNameResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as ListObjectsByBucketNameResponse; + + let Objects = res.GfSpListObjectsByBucketNameResponse.Objects || []; + if (Objects) { + if (!Array.isArray(Objects)) { + Objects = [Objects]; + } + + Objects = Objects.map((item) => { + return { + ...item, + // @ts-ignore + Removed: convertStrToBool(item.Removed), + UpdateAt: Number(item.UpdateAt), + DeleteAt: Number(item.DeleteAt), + ObjectInfo: formatObjectInfo(item.ObjectInfo), + }; + }); + } + + let CommonPrefixes = res.GfSpListObjectsByBucketNameResponse.CommonPrefixes || []; + if (CommonPrefixes) { + if (!Array.isArray(CommonPrefixes)) { + CommonPrefixes = [CommonPrefixes]; + } + } + + res.GfSpListObjectsByBucketNameResponse = { + ...res.GfSpListObjectsByBucketNameResponse, + Objects, + CommonPrefixes, + // @ts-ignore + IsTruncated: convertStrToBool(res.GfSpListObjectsByBucketNameResponse.IsTruncated), + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/listObjectsByIds.ts b/packages/chain-sdk/src/clients/spclient/spApis/listObjectsByIds.ts new file mode 100644 index 00000000..22540413 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/listObjectsByIds.ts @@ -0,0 +1,46 @@ +import { ListObjectsByIDsResponse } from '@/types'; +import { formatObjectInfo, convertStrToBool } from '@/types/sp-xml/Common'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/list_objects_by_ids +export const parseListObjectsByIdsResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as ListObjectsByIDsResponse; + + let ObjectEntry = res.GfSpListObjectsByIDsResponse.ObjectEntry; + + if (ObjectEntry) { + if (!Array.isArray(ObjectEntry)) { + ObjectEntry = [ObjectEntry]; + } + + ObjectEntry = ObjectEntry.map((item) => { + let Value = item.Value; + if (Value) { + Value = { + ...item.Value, + ObjectInfo: formatObjectInfo(item.Value.ObjectInfo), + // @ts-ignore + Removed: convertStrToBool(item.Value.Removed), + UpdateAt: Number(item.Value.UpdateAt), + DeleteAt: Number(item.Value.DeleteAt), + }; + } + + return { + ...item, + Id: Number(item.Id), + Value, + }; + }); + } + + res.GfSpListObjectsByIDsResponse = { + ...res.GfSpListObjectsByIDsResponse, + ObjectEntry, + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/listUserGroups.ts b/packages/chain-sdk/src/clients/spclient/spApis/listUserGroups.ts new file mode 100644 index 00000000..d9173219 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/listUserGroups.ts @@ -0,0 +1,38 @@ +import { ListUserGroupsResponse } from '@/types'; +import { formatGroupInfo, convertStrToBool } from '@/types/sp-xml/Common'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/list_user_groups +export const parseListUserGroupsResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + + const res = xmlParser.parse(data) as ListUserGroupsResponse; + + let Groups = res.GfSpGetUserGroupsResponse.Groups || []; + if (Groups) { + if (!Array.isArray(Groups)) { + Groups = [Groups]; + } + + Groups = Groups.map((item) => { + return { + ...item, + CreateAt: Number(item.CreateAt), + CreateTime: Number(item.CreateTime), + UpdateAt: Number(item.UpdateAt), + UpdateTime: Number(item.UpdateTime), + // @ts-ignore + Removed: convertStrToBool(item.Removed), + Group: formatGroupInfo(item.Group), + }; + }); + } + + res.GfSpGetUserGroupsResponse = { + Groups: Groups, + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/listUserOwnedGroups.ts b/packages/chain-sdk/src/clients/spclient/spApis/listUserOwnedGroups.ts new file mode 100644 index 00000000..d1ef8bcb --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/listUserOwnedGroups.ts @@ -0,0 +1,38 @@ +import { formatGroupInfo, convertStrToBool } from '@/types/sp-xml/Common'; +import { ListUserOwnedGroupsResponse } from '@/types/sp-xml/ListUserOwnedGroupsResponse'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/list_user_owned_groups +export const parseListUserOwnedGroupsResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + + const res = xmlParser.parse(data) as ListUserOwnedGroupsResponse; + + let Groups = res.GfSpGetUserOwnedGroupsResponse.Groups || []; + if (Groups) { + if (!Array.isArray(Groups)) { + Groups = [Groups]; + } + + Groups = Groups.map((item) => { + return { + ...item, + CreateAt: Number(item.CreateAt), + CreateTime: Number(item.CreateTime), + UpdateAt: Number(item.UpdateAt), + UpdateTime: Number(item.UpdateTime), + // @ts-ignore + Removed: convertStrToBool(item.Removed), + Group: formatGroupInfo(item.Group), + }; + }); + } + + res.GfSpGetUserOwnedGroupsResponse = { + Groups: Groups, + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/metaInfos.ts b/packages/chain-sdk/src/clients/spclient/spApis/metaInfos.ts new file mode 100644 index 00000000..992f68ee --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/metaInfos.ts @@ -0,0 +1,13 @@ +import { getBucketApprovalMetaInfo } from './bucketApproval'; +import { getGetObjectMetaInfo } from './getObject'; +import { getObjectApprovalMetaInfo } from './objectApproval'; +import { getPutObjectMetaInfo } from './putObject'; +import { getQueryBucketReadQuotaMetaInfo } from './queryBucketReadQuota'; + +export const SpMetaInfo = { + getBucketApprovalMetaInfo, + getGetObjectMetaInfo, + getObjectApprovalMetaInfo, + getPutObjectMetaInfo, + getQueryBucketReadQuotaMetaInfo, +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/migrateApproval.ts b/packages/chain-sdk/src/clients/spclient/spApis/migrateApproval.ts new file mode 100644 index 00000000..903039be --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/migrateApproval.ts @@ -0,0 +1,36 @@ +import { EMPTY_STRING_SHA256, METHOD_GET } from '@/constants'; +import { IMigrateBucketMsgType, ReqMeta } from '@/types'; +import { toHex, utf8ToBytes } from 'ethereum-cryptography/utils'; +import { getSortQuery } from '../auth'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/get_approval +export const getMigrateMetaInfo = async (endpoint: string, msg: IMigrateBucketMsgType) => { + const path = '/greenfield/admin/v1/get-approval'; + const queryMap = { + action: 'MigrateBucket', + }; + const query = getSortQuery(queryMap); + const url = `${endpoint}${path}?${query}`; + const unSignedMessageInHex = toHex(utf8ToBytes(JSON.stringify(msg))); + + const reqMeta: Partial = { + contentSHA256: EMPTY_STRING_SHA256, + method: METHOD_GET, + url: { + hostname: new URL(url).hostname, + query, + path, + }, + unsignMsg: unSignedMessageInHex, + }; + + const optionsWithOutHeaders: Omit = { + method: METHOD_GET, + }; + + return { + url, + optionsWithOutHeaders, + reqMeta, + }; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/objectApproval.ts b/packages/chain-sdk/src/clients/spclient/spApis/objectApproval.ts new file mode 100644 index 00000000..a30339de --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/objectApproval.ts @@ -0,0 +1,36 @@ +import { EMPTY_STRING_SHA256, METHOD_GET } from '@/constants'; +import { ICreateObjectMsgType, ReqMeta } from '@/types'; +import { toHex, utf8ToBytes } from 'ethereum-cryptography/utils'; +import { getSortQuery } from '../auth'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/get_approval +export const getObjectApprovalMetaInfo = async (endpoint: string, msg: ICreateObjectMsgType) => { + const path = '/greenfield/admin/v1/get-approval'; + const queryMap = { + action: 'CreateObject', + }; + const query = getSortQuery(queryMap); + const url = `${endpoint}${path}?${query}`; + const unSignedMessageInHex = toHex(utf8ToBytes(JSON.stringify(msg))); + + const reqMeta: Partial = { + contentSHA256: EMPTY_STRING_SHA256, + unsignMsg: unSignedMessageInHex, + method: METHOD_GET, + url: { + hostname: new URL(endpoint).hostname, + query, + path, + }, + }; + + const optionsWithOutHeaders: Omit = { + method: METHOD_GET, + }; + + return { + url, + optionsWithOutHeaders, + reqMeta, + }; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/parseError.ts b/packages/chain-sdk/src/clients/spclient/spApis/parseError.ts new file mode 100644 index 00000000..c3ec8ade --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/parseError.ts @@ -0,0 +1,14 @@ +import { RequestErrorResponse } from '@/types'; +import { XMLParser } from 'fast-xml-parser'; + +export const parseError = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as RequestErrorResponse; + + return { + code: res.Error.Code, + message: res.Error.Message, + }; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/putObject.ts b/packages/chain-sdk/src/clients/spclient/spApis/putObject.ts new file mode 100644 index 00000000..9f174759 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/putObject.ts @@ -0,0 +1,45 @@ +import { EMPTY_STRING_SHA256, METHOD_PUT } from '@/constants'; +import { ReqMeta } from '@/types'; +import { generateUrlByBucketName } from '@/utils/s3'; +import { encodePath } from '../auth'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/put_object +export const getPutObjectMetaInfo = async ( + endpoint: string, + params: { + objectName: string; + bucketName: string; + txnHash: string; + contentType: string; + body: File; + }, +) => { + const { bucketName, objectName, txnHash, contentType, body } = params; + const path = `/${encodePath(objectName)}`; + + const query = ''; + const url = `${generateUrlByBucketName(endpoint, bucketName)}${path}`; + + const reqMeta: Partial = { + contentSHA256: EMPTY_STRING_SHA256, + txnHash: txnHash, + method: METHOD_PUT, + url: { + hostname: new URL(url).hostname, + query, + path, + }, + contentType, + }; + + const optionsWithOutHeaders: Omit = { + method: METHOD_PUT, + body, + }; + + return { + url, + optionsWithOutHeaders, + reqMeta, + }; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spApis/queryBucketReadQuota.ts b/packages/chain-sdk/src/clients/spclient/spApis/queryBucketReadQuota.ts new file mode 100644 index 00000000..98dedde6 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/queryBucketReadQuota.ts @@ -0,0 +1,56 @@ +import { EMPTY_STRING_SHA256, METHOD_GET } from '@/constants'; +import { ReqMeta, TBaseGetBucketReadQuota } from '@/types'; +import { ReadQuotaResponse } from '@/types/sp-xml'; +import { generateUrlByBucketName } from '@/utils/s3'; +import { XMLParser } from 'fast-xml-parser'; +import { getSortQuery } from '../auth'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/query_bucket_read_quota +export const getQueryBucketReadQuotaMetaInfo = async ( + endpoint: string, + params: TBaseGetBucketReadQuota, +) => { + const { year, month, bucketName } = params; + const currentDate = new Date(); + const finalYear = year ? year : currentDate.getFullYear(); + const finalMonth = month ? month : currentDate.getMonth() + 1; + // format month to 2 digits, like "01" + const formattedMonth = finalMonth.toString().padStart(2, '0'); + + const path = '/'; + const queryMap = { + 'year-month': `${finalYear}-${formattedMonth}`, + 'read-quota': 'null', + }; + const query = getSortQuery(queryMap); + const url = `${generateUrlByBucketName(endpoint, bucketName)}${path}?${query}`; + + const reqMeta: Partial = { + contentSHA256: EMPTY_STRING_SHA256, + method: METHOD_GET, + url: { + hostname: new URL(url).hostname, + query, + path, + }, + }; + + const optionsWithOutHeaders: Omit = { + method: METHOD_GET, + }; + + return { + url, + optionsWithOutHeaders, + reqMeta, + }; +}; + +export const parseReadQuotaResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as ReadQuotaResponse; + + return res; +}; diff --git a/packages/chain-sdk/src/offchainauth/fetch.ts b/packages/chain-sdk/src/clients/spclient/spApis/updateUserAccountKey.ts similarity index 52% rename from packages/chain-sdk/src/offchainauth/fetch.ts rename to packages/chain-sdk/src/clients/spclient/spApis/updateUserAccountKey.ts index 205daef3..e606a28b 100644 --- a/packages/chain-sdk/src/offchainauth/fetch.ts +++ b/packages/chain-sdk/src/clients/spclient/spApis/updateUserAccountKey.ts @@ -1,42 +1,8 @@ +import { IUpdateOneSpPubKeyParams } from '@/types'; import { fetchWithTimeout } from '@/utils/http'; import { Headers } from 'cross-fetch'; -import { IFetchNonce, IUpdateOneSpPubKeyParams } from '../types/storage'; -export const fetchNonce = async ({ - spEndpoint, - spName, - spAddress, - address, - domain, -}: IFetchNonce) => { - let result; - const url = `${spEndpoint}/auth/request_nonce`; - const headers = new Headers({ - 'X-Gnfd-User-Address': address, - 'X-Gnfd-App-Domain': domain, - }); - try { - result = await fetchWithTimeout(url, { - headers, - }); - if (!result.ok) { - return { code: -1, nonce: null }; - } - } catch (error) { - return { code: -1, nonce: null }; - } - - const res = await result.json(); - - return { - endpoint: spEndpoint, - address: spAddress, - name: spName, - nonce: res.next_nonce, - }; -}; - -export const updateOneSpPubKey = async ({ +export const updateUserAccountKey = async ({ address, domain, sp, @@ -52,7 +18,7 @@ export const updateOneSpPubKey = async ({ 'X-Gnfd-App-Domain': domain, 'X-Gnfd-App-Reg-Nonce': nonce, 'X-Gnfd-App-Reg-Public-Key': pubKey, - 'X-Gnfd-App-Reg-Expiry-Date': expireDate, + 'X-Gnfd-Expiry-Timestamp': expireDate, Authorization: authorization, }); diff --git a/packages/chain-sdk/src/clients/spclient/spApis/verifyPermission.ts b/packages/chain-sdk/src/clients/spclient/spApis/verifyPermission.ts new file mode 100644 index 00000000..76b4c7ca --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spApis/verifyPermission.ts @@ -0,0 +1,17 @@ +import { VerifyPermissionResponse } from '@/types/sp-xml/VerifyPermissionResponse'; +import { XMLParser } from 'fast-xml-parser'; + +// https://docs.bnbchain.org/greenfield-docs/docs/api/storgae-provider-rest/verify_permission +export const parseVerifyPermissionResponse = async (data: string) => { + const xmlParser = new XMLParser({ + parseTagValue: false, + }); + const res = xmlParser.parse(data) as VerifyPermissionResponse; + + res.QueryVerifyPermissionResponse = { + ...res.QueryVerifyPermissionResponse, + Effect: Number(res.QueryVerifyPermissionResponse.Effect), + }; + + return res; +}; diff --git a/packages/chain-sdk/src/clients/spclient/spClient.ts b/packages/chain-sdk/src/clients/spclient/spClient.ts new file mode 100644 index 00000000..f968fb36 --- /dev/null +++ b/packages/chain-sdk/src/clients/spclient/spClient.ts @@ -0,0 +1,94 @@ +import { + getAuthorization, + HTTPHeaderAuthorization, + newRequestHeadersByMeta, +} from '@/clients/spclient/auth'; +import { parseError } from '@/clients/spclient/spApis/parseError'; +import { ReqMeta } from '@/types/auth'; +import { Headers } from 'cross-fetch'; +import { singleton } from 'tsyringe'; + +/** + * V1 + */ +export type ECDSA = { + type: 'ECDSA'; + privateKey: string; +}; +/** + * OffChainAuth + */ +export type EDDSA = { + type: 'EDDSA'; + seed: string; + domain: string; + address: string; +}; +export type AuthType = ECDSA | EDDSA; + +export interface ISpClient { + callApi( + url: string, + options: RequestInit, + duration: number, + customError?: { + message: string; + code: number; + }, + ): Promise; + + signHeaders(reqMeta: Partial, authType: AuthType): Promise; +} + +@singleton() +export class SpClient implements ISpClient { + public async callApi( + url: string, + options: RequestInit, + timeout = 30000, + customError?: { + message: string; + code: number; + }, + ) { + try { + const controller = new AbortController(); + const _id = setTimeout(() => controller.abort(), timeout); + const response = await fetch(url, { + ...options, + signal: controller.signal, + }); + clearTimeout(_id); + + const { status } = response; + + if (!response.ok) { + const xmlError = await response.text(); + const { code, message } = await parseError(xmlError); + throw { + code: code || customError?.code, + message: message || customError?.message, + statusCode: status, + }; + } + + return response; + } catch (error) { + return Promise.reject(error); + } + } + + public async signHeaders(reqMeta: Partial, authType: AuthType) { + const metaHeaders: Headers = newRequestHeadersByMeta(reqMeta); + if (authType.type === 'EDDSA') { + const { domain, address } = authType; + metaHeaders.append('X-Gnfd-User-Address', address); + metaHeaders.append('X-Gnfd-App-Domain', domain); + } + + const auth = await getAuthorization(reqMeta, metaHeaders, authType); + metaHeaders.set(HTTPHeaderAuthorization, auth); + + return metaHeaders; + } +} diff --git a/packages/chain-sdk/src/constants/http.ts b/packages/chain-sdk/src/constants/http.ts new file mode 100644 index 00000000..1b6a9e93 --- /dev/null +++ b/packages/chain-sdk/src/constants/http.ts @@ -0,0 +1,7 @@ +export const EMPTY_STRING_SHA256 = + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'; +export const MOCK_SIGNATURE = '1234567812345678123456781234567812345678123456781234567812345678'; +export const NORMAL_ERROR_CODE = 404; +export const METHOD_GET = 'GET'; +export const METHOD_POST = 'POST'; +export const METHOD_PUT = 'PUT'; diff --git a/packages/chain-sdk/src/constants/index.ts b/packages/chain-sdk/src/constants/index.ts index fa6e6e53..d825b2fa 100644 --- a/packages/chain-sdk/src/constants/index.ts +++ b/packages/chain-sdk/src/constants/index.ts @@ -1,4 +1,5 @@ export * from './typeUrl'; +export * from './http'; export const ZERO_PUBKEY = '0x000000000000000000000000000000000000000000000000000000000000000000'; export const DEFAULT_DENOM = 'BNB'; diff --git a/packages/chain-sdk/src/index.ts b/packages/chain-sdk/src/index.ts index 8532cf27..0272b319 100644 --- a/packages/chain-sdk/src/index.ts +++ b/packages/chain-sdk/src/index.ts @@ -1,6 +1,7 @@ export * from './client'; +export * from './clients/spclient/spApis/metaInfos'; export * from './constants'; +export * from './messages'; export * from './sign'; export * from './types'; -export * from './messages'; export * from './utils'; diff --git a/packages/chain-sdk/src/messages/greenfield/payment/MsgCreatePaymentAccount.ts b/packages/chain-sdk/src/messages/greenfield/payment/MsgCreatePaymentAccount.ts index f817b3ec..d8101703 100644 --- a/packages/chain-sdk/src/messages/greenfield/payment/MsgCreatePaymentAccount.ts +++ b/packages/chain-sdk/src/messages/greenfield/payment/MsgCreatePaymentAccount.ts @@ -1,11 +1,11 @@ export const MsgCreatePaymentAccountSDKTypeEIP712 = { - Msg: [ + Msg1: [ { name: 'type', type: 'string', }, { - name: 'creaator', + name: 'creator', type: 'string', }, ], diff --git a/packages/chain-sdk/src/messages/greenfield/storage/MsgCreateGroup.ts b/packages/chain-sdk/src/messages/greenfield/storage/MsgCreateGroup.ts index 26f2a280..6badab86 100644 --- a/packages/chain-sdk/src/messages/greenfield/storage/MsgCreateGroup.ts +++ b/packages/chain-sdk/src/messages/greenfield/storage/MsgCreateGroup.ts @@ -1,11 +1,11 @@ export const MsgCreateGroupSDKTypeEIP712 = { Msg1: [ { - name: 'type', + name: 'creator', type: 'string', }, { - name: 'creator', + name: 'extra', type: 'string', }, { @@ -13,11 +13,7 @@ export const MsgCreateGroupSDKTypeEIP712 = { type: 'string', }, { - name: 'members', - type: 'string[]', - }, - { - name: 'extra', + name: 'type', type: 'string', }, ], diff --git a/packages/chain-sdk/src/messages/greenfield/storage/MsgMigrateBucket.ts b/packages/chain-sdk/src/messages/greenfield/storage/MsgMigrateBucket.ts index 7a45b92c..6c2538fa 100644 --- a/packages/chain-sdk/src/messages/greenfield/storage/MsgMigrateBucket.ts +++ b/packages/chain-sdk/src/messages/greenfield/storage/MsgMigrateBucket.ts @@ -30,5 +30,9 @@ export const MsgMigrateBucketSDKTypeEIP712 = { name: 'global_virtual_group_family_id', type: 'uint32', }, + { + name: 'sig', + type: 'bytes', + }, ], }; diff --git a/packages/chain-sdk/src/messages/greenfield/storage/MsgMirrorGroup.ts b/packages/chain-sdk/src/messages/greenfield/storage/MsgMirrorGroup.ts index 5b41f86f..8e3fb5be 100644 --- a/packages/chain-sdk/src/messages/greenfield/storage/MsgMirrorGroup.ts +++ b/packages/chain-sdk/src/messages/greenfield/storage/MsgMirrorGroup.ts @@ -1,11 +1,11 @@ export const MsgMirrorGroupSDKTypeEIP712 = { Msg1: [ { - name: 'type', - type: 'string', + name: 'dest_chain_id', + type: 'uint32', }, { - name: 'operator', + name: 'group_name', type: 'string', }, { @@ -13,7 +13,11 @@ export const MsgMirrorGroupSDKTypeEIP712 = { type: 'string', }, { - name: 'group_name', + name: 'operator', + type: 'string', + }, + { + name: 'type', type: 'string', }, ], diff --git a/packages/chain-sdk/src/messages/greenfield/storage/MsgUpdateBucketInfo.ts b/packages/chain-sdk/src/messages/greenfield/storage/MsgUpdateBucketInfo.ts index eaaa5ecf..8500cd36 100644 --- a/packages/chain-sdk/src/messages/greenfield/storage/MsgUpdateBucketInfo.ts +++ b/packages/chain-sdk/src/messages/greenfield/storage/MsgUpdateBucketInfo.ts @@ -12,6 +12,10 @@ export const MsgUpdateBucketInfoSDKTypeEIP712 = { name: 'bucket_name', type: 'string', }, + { + name: 'charged_read_quota', + type: 'TypeMsg1ChargedReadQuota', + }, { name: 'payment_address', type: 'string', @@ -21,4 +25,10 @@ export const MsgUpdateBucketInfoSDKTypeEIP712 = { type: 'string', }, ], + TypeMsg1ChargedReadQuota: [ + { + name: 'value', + type: 'uint64', + }, + ], }; diff --git a/packages/chain-sdk/src/messages/greenfield/storage/MsgUpdateGroupMember.ts b/packages/chain-sdk/src/messages/greenfield/storage/MsgUpdateGroupMember.ts index aedde54e..50bcea61 100644 --- a/packages/chain-sdk/src/messages/greenfield/storage/MsgUpdateGroupMember.ts +++ b/packages/chain-sdk/src/messages/greenfield/storage/MsgUpdateGroupMember.ts @@ -1,28 +1,79 @@ -export const MsgUpdateGroupMemberSDKTypeEIP712 = { +import { MsgGroupMember } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; +import cloneDeep from 'lodash.clonedeep'; + +export const getMsgUpdateGroupMemberSDKTypeEIP712 = ({ + membersToAdd, + membersToDelete, +}: { + membersToAdd: MsgGroupMember[]; + membersToDelete: string[]; +}) => { + const res: Record> = cloneDeep( + MsgUpdateGroupMemberSDKTypeEIP712, + ); + + if (membersToAdd.length > 0) { + res.Msg1.push({ + name: 'members_to_add', + type: 'TypeMsg1MembersToAdd[]', + }); + res.TypeMsg1MembersToAdd = [ + { + name: 'expiration_time', + type: 'string', + }, + { + name: 'member', + type: 'string', + }, + ]; + } + + if (membersToDelete.length > 0) { + res.Msg1.push({ + name: 'members_to_delete', + type: 'string[]', + }); + } + + return res; +}; + +const MsgUpdateGroupMemberSDKTypeEIP712 = { Msg1: [ { - name: 'type', - type: 'string', - }, - { - name: 'operator', + name: 'group_name', type: 'string', }, { name: 'group_owner', type: 'string', }, + // { + // name: 'members_to_add', + // type: 'TypeMsg1MembersToAdd[]', + // }, + // { + // name: 'members_to_delete', + // type: 'string[]', + // }, { - name: 'group_name', + name: 'operator', type: 'string', }, { - name: 'members_to_add', - type: 'string[]', - }, - { - name: 'members_to_delete', - type: 'string[]', + name: 'type', + type: 'string', }, ], + // TypeMsg1MembersToAdd: [ + // { + // name: 'expiration_time', + // type: 'string', + // }, + // { + // name: 'member', + // type: 'string', + // }, + // ], }; diff --git a/packages/chain-sdk/src/messages/utils.ts b/packages/chain-sdk/src/messages/utils.ts index ee01e6e6..c6aa1d81 100644 --- a/packages/chain-sdk/src/messages/utils.ts +++ b/packages/chain-sdk/src/messages/utils.ts @@ -29,7 +29,7 @@ export const createEIP712 = (types: object, chainId: string, message: object) => salt: '0', }, message, - }; + } as const; }; export const generateMessage = ( diff --git a/packages/chain-sdk/src/offchainauth/index.ts b/packages/chain-sdk/src/offchainauth/index.ts index bf37a528..29f4b07f 100644 --- a/packages/chain-sdk/src/offchainauth/index.ts +++ b/packages/chain-sdk/src/offchainauth/index.ts @@ -1,3 +1,2 @@ -export * from './sign'; -export * from './fetch'; +export * from '../clients/spclient/sign'; export * from './utils'; diff --git a/packages/chain-sdk/src/offchainauth/utils.ts b/packages/chain-sdk/src/offchainauth/utils.ts index d5b88420..48d903d8 100644 --- a/packages/chain-sdk/src/offchainauth/utils.ts +++ b/packages/chain-sdk/src/offchainauth/utils.ts @@ -1,4 +1,3 @@ -import { fetchNonce, updateOneSpPubKey } from './fetch'; import { IFetchNonces, TGenSecondSignMsgParams, @@ -6,6 +5,8 @@ import { ISp, IUpdateSpsPubKeyParams, } from '../types/storage'; +import { getNonce } from '@/clients/spclient/spApis/getNonce'; +import { updateUserAccountKey } from '@/clients/spclient/spApis/updateUserAccountKey'; const delay = (ms: number, value: { code: number; nonce?: null | number; message?: any }) => new Promise((resolve) => setTimeout(() => resolve(value), ms)); @@ -36,7 +37,7 @@ export const genLocalSignMsg = (sps: ISp[], domain: string) => { export const fetchNonces = async ({ sps, address, domain }: IFetchNonces): Promise => { const promises = sps.map((sp: ISp) => - fetchNonce({ + getNonce({ spEndpoint: sp.endpoint, spAddress: sp.address, spName: sp.name, @@ -58,7 +59,7 @@ export const updateSpsPubKey = async ({ authorization, }: IUpdateSpsPubKeyParams) => { const promises = sps.map((sp: ISp) => - updateOneSpPubKey({ + updateUserAccountKey({ address, domain, sp, @@ -116,7 +117,3 @@ export const personalSign = async ({ message, address, provider }: IPersonalSign return sign; }; - -export const genSeedSignMsg = (timestamp: number) => { - return `InvokeSPAPI_offChainSign_${timestamp}`; -}; diff --git a/packages/chain-sdk/src/tests/parsexml.spec.ts b/packages/chain-sdk/src/tests/parsexml.spec.ts new file mode 100644 index 00000000..f0e3fb07 --- /dev/null +++ b/packages/chain-sdk/src/tests/parsexml.spec.ts @@ -0,0 +1,465 @@ +import { expect } from 'chai'; +import { parseListObjectsByBucketNameResponse } from '../clients/spclient/spApis/listObjectsByBucket'; +import { parseGetUserBucketsResponse } from '../clients/spclient/spApis/getUserBuckets'; + +describe('parseListObjectsByBucketNameResponse', () => { + test('parse CommonPrefixes contains 2 element at least', async () => { + const data = ` + + xxx + yyy + 36 + 50 + false + + foo + + + + + `; + + const res = await parseListObjectsByBucketNameResponse(data); + + expect(res.GfSpListObjectsByBucketNameResponse).to.have.deep.property('CommonPrefixes', [ + 'xxx', + 'yyy', + ]); + }); + + test('parse CommonPrefixes contains 1 element', async () => { + const data = ` + + xxx + 36 + 50 + false + + foo + + + + + `; + + const res = await parseListObjectsByBucketNameResponse(data); + + expect(res.GfSpListObjectsByBucketNameResponse).to.have.deep.property('CommonPrefixes', [ + 'xxx', + ]); + }); + + test('parse empty CommonPrefixes', async () => { + const data = ` + + 36 + 50 + false + + foo + + + + + `; + + const res = await parseListObjectsByBucketNameResponse(data); + + expect(res.GfSpListObjectsByBucketNameResponse).to.have.deep.property('CommonPrefixes', []); + }); + + test('parse empty Objects', async () => { + const data = ` + + 36 + 50 + false + + foo + + + + + `; + + const res = await parseListObjectsByBucketNameResponse(data); + + expect(res.GfSpListObjectsByBucketNameResponse).to.have.deep.property('Objects', []); + }); + + test('parse Objects contains 1 element', async () => { + const data = ` + + + + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + true + obk + 739112 + 1 + 205 + 1 + application/x-yaml + 1692971564 + 1 + 0 + 0 + a7554caa696d4db8c8ea4500ad2eac6244bca79e3c3193db8cc699a7e89cf341 + b054fd20d637bfa706182288d687f2c0f0ef5afaa4114170bc18699b268e9c28 + + 0x0000000000000000000000000000000000000000000000000000000000000000 + false + 232480 + 0 + + 0x8b97D152149309C15B1C339F547a9aca9Bf629D2 + 0x42d854103bc83359b86b55b8a0452895865c2de1ac51f26d551fca0e2a587214 + 0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75 + 0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75 + + 36 + 50 + false + + foo + + + + + `; + + const res = await parseListObjectsByBucketNameResponse(data); + + expect(res.GfSpListObjectsByBucketNameResponse).to.have.deep.property('Objects', [ + { + ObjectInfo: { + Owner: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + Creator: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + BucketName: 'true', + ObjectName: 'obk', + Id: 739112, + LocalVirtualGroupId: 1, + PayloadSize: 205, + Visibility: 1, + ContentType: 'application/x-yaml', + CreateAt: 1692971564, + ObjectStatus: 1, + RedundancyType: 0, + SourceType: 0, + Checksums: [ + 'a7554caa696d4db8c8ea4500ad2eac6244bca79e3c3193db8cc699a7e89cf341', + 'b054fd20d637bfa706182288d687f2c0f0ef5afaa4114170bc18699b268e9c28', + ], + }, + LockedBalance: '0x0000000000000000000000000000000000000000000000000000000000000000', + Removed: false, + UpdateAt: 232480, + DeleteAt: 0, + DeleteReason: '', + Operator: '0x8b97D152149309C15B1C339F547a9aca9Bf629D2', + CreateTxHash: '0x42d854103bc83359b86b55b8a0452895865c2de1ac51f26d551fca0e2a587214', + UpdateTxHash: '0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75', + SealTxHash: '0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75', + }, + ]); + }); + + test('parse Objects contains 2 element at least', async () => { + const data = ` + + + + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + foo + obk + 739112 + 1 + 205 + 1 + application/x-yaml + 1692971564 + 1 + 0 + 0 + a7554caa696d4db8c8ea4500ad2eac6244bca79e3c3193db8cc699a7e89cf341 + b054fd20d637bfa706182288d687f2c0f0ef5afaa4114170bc18699b268e9c28 + + 0x0000000000000000000000000000000000000000000000000000000000000000 + false + 232480 + 0 + + 0x8b97D152149309C15B1C339F547a9aca9Bf629D2 + 0x42d854103bc83359b86b55b8a0452895865c2de1ac51f26d551fca0e2a587214 + 0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75 + 0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75 + + + + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + foo + obk + 739112 + 1 + 205 + 1 + application/x-yaml + 1692971564 + 1 + 0 + 0 + a7554caa696d4db8c8ea4500ad2eac6244bca79e3c3193db8cc699a7e89cf341 + b054fd20d637bfa706182288d687f2c0f0ef5afaa4114170bc18699b268e9c28 + + 0x0000000000000000000000000000000000000000000000000000000000000000 + false + 232480 + 0 + + 0x8b97D152149309C15B1C339F547a9aca9Bf629D2 + 0x42d854103bc83359b86b55b8a0452895865c2de1ac51f26d551fca0e2a587214 + 0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75 + 0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75 + + 36 + 50 + false + + foo + + + + + `; + + const res = await parseListObjectsByBucketNameResponse(data); + + expect(res.GfSpListObjectsByBucketNameResponse).to.have.deep.property('Objects', [ + { + ObjectInfo: { + Owner: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + Creator: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + BucketName: 'foo', + ObjectName: 'obk', + Id: 739112, + LocalVirtualGroupId: 1, + PayloadSize: 205, + Visibility: 1, + ContentType: 'application/x-yaml', + CreateAt: 1692971564, + ObjectStatus: 1, + RedundancyType: 0, + SourceType: 0, + Checksums: [ + 'a7554caa696d4db8c8ea4500ad2eac6244bca79e3c3193db8cc699a7e89cf341', + 'b054fd20d637bfa706182288d687f2c0f0ef5afaa4114170bc18699b268e9c28', + ], + }, + LockedBalance: '0x0000000000000000000000000000000000000000000000000000000000000000', + Removed: false, + UpdateAt: 232480, + DeleteAt: 0, + DeleteReason: '', + Operator: '0x8b97D152149309C15B1C339F547a9aca9Bf629D2', + CreateTxHash: '0x42d854103bc83359b86b55b8a0452895865c2de1ac51f26d551fca0e2a587214', + UpdateTxHash: '0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75', + SealTxHash: '0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75', + }, + { + ObjectInfo: { + Owner: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + Creator: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + BucketName: 'foo', + ObjectName: 'obk', + Id: 739112, + LocalVirtualGroupId: 1, + PayloadSize: 205, + Visibility: 1, + ContentType: 'application/x-yaml', + CreateAt: 1692971564, + ObjectStatus: 1, + RedundancyType: 0, + SourceType: 0, + Checksums: [ + 'a7554caa696d4db8c8ea4500ad2eac6244bca79e3c3193db8cc699a7e89cf341', + 'b054fd20d637bfa706182288d687f2c0f0ef5afaa4114170bc18699b268e9c28', + ], + }, + LockedBalance: '0x0000000000000000000000000000000000000000000000000000000000000000', + Removed: false, + UpdateAt: 232480, + DeleteAt: 0, + DeleteReason: '', + Operator: '0x8b97D152149309C15B1C339F547a9aca9Bf629D2', + CreateTxHash: '0x42d854103bc83359b86b55b8a0452895865c2de1ac51f26d551fca0e2a587214', + UpdateTxHash: '0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75', + SealTxHash: '0x704ba83349018ddcdd0774fb4a01e84eda3d32d8577d958cfa272fbcc2892f75', + }, + ]); + }); +}); + +describe('parseGetUserBucketsResponse', () => { + test('parse Buckets containers 2 element at least', async () => { + const data = ` + + + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + false + 1 + 1156769 + 0 + 1693279149 + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + 8 + 0 + 0 + + false + 0 + + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + 0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292 + 0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292 + 363707 + 1693279149 + + + + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + false + 1 + 1156769 + 0 + 1693279149 + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + 8 + 0 + 0 + + false + 0 + + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + 0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292 + 0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292 + 363707 + 1693279149 + +`; + + const res = await parseGetUserBucketsResponse(data); + + expect(res.GfSpGetUserBucketsResponse).to.have.deep.property('Buckets', [ + { + BucketInfo: { + Owner: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + BucketName: 'false', + Visibility: 1, + Id: '1156769', + SourceType: 0, + CreateAt: 1693279149, + PaymentAddress: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + GlobalVirtualGroupFamilyId: 8, + ChargedReadQuota: 0, + BucketStatus: 0, + }, + Removed: false, + DeleteAt: 0, + DeleteReason: '', + Operator: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + CreateTxHash: '0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292', + UpdateTxHash: '0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292', + UpdateAt: 363707, + UpdateTime: 1693279149, + }, + { + BucketInfo: { + Owner: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + BucketName: 'false', + Visibility: 1, + Id: '1156769', + SourceType: 0, + CreateAt: 1693279149, + PaymentAddress: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + GlobalVirtualGroupFamilyId: 8, + ChargedReadQuota: 0, + BucketStatus: 0, + }, + Removed: false, + DeleteAt: 0, + DeleteReason: '', + Operator: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + CreateTxHash: '0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292', + UpdateTxHash: '0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292', + UpdateAt: 363707, + UpdateTime: 1693279149, + }, + ]); + }); + + test('parse Buckets containers 1 element', async () => { + const data = ` + + + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + false + 1 + 1156769 + 0 + 1693279149 + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + 8 + 0 + 0 + + false + 0 + + 0x1C893441AB6c1A75E01887087ea508bE8e07AAae + 0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292 + 0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292 + 363707 + 1693279149 + +`; + + const res = await parseGetUserBucketsResponse(data); + + expect(res.GfSpGetUserBucketsResponse).to.have.deep.property('Buckets', [ + { + BucketInfo: { + Owner: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + BucketName: 'false', + Visibility: 1, + Id: '1156769', + SourceType: 0, + CreateAt: 1693279149, + PaymentAddress: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + GlobalVirtualGroupFamilyId: 8, + ChargedReadQuota: 0, + BucketStatus: 0, + }, + Removed: false, + DeleteAt: 0, + DeleteReason: '', + Operator: '0x1C893441AB6c1A75E01887087ea508bE8e07AAae', + CreateTxHash: '0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292', + UpdateTxHash: '0xea5f91a6ba8e558e35ecc416579b4585cc494d5dcc99bce519cc54968b0b1292', + UpdateAt: 363707, + UpdateTime: 1693279149, + }, + ]); + }); + + test('parse empty Buckets', async () => { + const data = ``; + + const res = await parseGetUserBucketsResponse(data); + + expect(res.GfSpGetUserBucketsResponse).to.have.deep.property('Buckets', []); + }); +}); diff --git a/packages/chain-sdk/src/tests/utils.spec.ts b/packages/chain-sdk/src/tests/utils.spec.ts new file mode 100644 index 00000000..336db589 --- /dev/null +++ b/packages/chain-sdk/src/tests/utils.spec.ts @@ -0,0 +1,26 @@ +import { expect } from 'chai'; +import { encodePath } from '../clients/spclient/auth'; + +describe('encodePaths', () => { + it('encode english chars', () => { + expect(encodePath('~!@#$%^&*()')).equals('~%21%40%23%24%25%5E%26%2A%28%29'); + + expect(encodePath('-=_+`:;\'[]{}<>,./?\\|"')).equals( + '-%3D_%2B%60%3A%3B%27%5B%5D%7B%7D%3C%3E%2C./%3F%5C%7C%22', + ); + }); + + it('encode chinese chars', () => { + expect(encodePath('中文')).equals('%E4%B8%AD%E6%96%87'); + }); + + it('encode complex utf-8 chars', () => { + expect( + encodePath( + '(h~d…-o#y))$i…nla@)l!zq%ja.!m…)ug(z@a…*sd(tz.#(—$…!)-tz…*ko#$l&jz$bu…@q(cf+k()a)¥dya%!)(qo@raz@$d@d~rl.y…ga—)ep*#+mqmu¥…ril—)vde…@p……l+hif—%z~!%li~o((kh%—%—#u)$zhunu@.~#t…#di.jfohw…!)@z…xm*…m—)gzop*q%…qxxzqrno%h-…k~&)~w)!%w.…u20230830112135_jim _255MB', + ), + ).equals( + '%EF%BC%88h%EF%BD%9Ed%E2%80%A6-o%23y%29%29%24i%E2%80%A6nla%40%29l%EF%BC%81zq%25ja.%EF%BC%81m%E2%80%A6%29ug%28z%40a%E2%80%A6%2Asd%EF%BC%88tz.%23%28%E2%80%94%24%E2%80%A6%21%29-tz%E2%80%A6%2Ako%23%24l%26jz%24bu%E2%80%A6%40q%28cf%2Bk%28%29a%EF%BC%89%C2%A5dya%25%EF%BC%81%29%28qo%40raz%40%24d%40d%EF%BD%9Erl.y%E2%80%A6ga%E2%80%94%29ep%2A%23%2Bmqmu%C2%A5%E2%80%A6ril%E2%80%94%29vde%E2%80%A6%40p%E2%80%A6%E2%80%A6l%2Bhif%E2%80%94%25z%EF%BD%9E%EF%BC%81%25li%EF%BD%9Eo%EF%BC%88%28kh%25%E2%80%94%25%E2%80%94%23u%29%24zhunu%40.%EF%BD%9E%23t%E2%80%A6%23di.jfohw%E2%80%A6%21%EF%BC%89%40z%E2%80%A6xm%2A%E2%80%A6m%E2%80%94%29gzop%2Aq%25%E2%80%A6qxxzqrno%25h-%E2%80%A6k%EF%BD%9E%26%29%EF%BD%9Ew%29%21%25w.%E2%80%A6u20230830112135_jim%20_255MB', + ); + }); +}); diff --git a/packages/chain-sdk/src/types/auth.ts b/packages/chain-sdk/src/types/auth.ts new file mode 100644 index 00000000..9fd7e016 --- /dev/null +++ b/packages/chain-sdk/src/types/auth.ts @@ -0,0 +1,16 @@ +import { METHOD_GET, METHOD_POST, METHOD_PUT } from '@/constants/http'; + +export interface ReqMeta { + method: typeof METHOD_GET | typeof METHOD_POST | typeof METHOD_PUT; + contentType: string; + url: { + hostname: string; + path: string; + query: string; + }; + date: string; + expiryTimestamp: string; + contentSHA256: string; + unsignMsg: string; + txnHash: string; +} diff --git a/packages/chain-sdk/src/types/common.ts b/packages/chain-sdk/src/types/common.ts index 2c1a07a9..c206a047 100644 --- a/packages/chain-sdk/src/types/common.ts +++ b/packages/chain-sdk/src/types/common.ts @@ -3,6 +3,4 @@ import * as PermissionTypes from '@bnb-chain/greenfield-cosmos-types/greenfield/ import * as TimestampTypes from '@bnb-chain/greenfield-cosmos-types/google/protobuf/timestamp'; import Long from 'long'; -type TKeyValue = { [key: string]: string }; - -export { Long, StorageEnums, PermissionTypes, TimestampTypes, TKeyValue }; +export { Long, StorageEnums, PermissionTypes, TimestampTypes }; diff --git a/packages/chain-sdk/src/types/index.ts b/packages/chain-sdk/src/types/index.ts index 52927e7f..a48ca381 100644 --- a/packages/chain-sdk/src/types/index.ts +++ b/packages/chain-sdk/src/types/index.ts @@ -1,3 +1,5 @@ +export * from './auth'; export * from './common'; export * from './storage'; export * from './tx'; +export * from './sp-xml'; diff --git a/packages/chain-sdk/src/types/sp-xml/Common.ts b/packages/chain-sdk/src/types/sp-xml/Common.ts new file mode 100644 index 00000000..931c1e67 --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/Common.ts @@ -0,0 +1,131 @@ +export interface BucketMeta { + BucketInfo: BucketInfo; + Removed: boolean; + DeleteAt: number; + DeleteReason: string; + Operator: string; + CreateTxHash: string; + UpdateTxHash: string; + UpdateAt: number; + UpdateTime: number; +} + +export interface BucketInfo { + // PrimarySpId: number; + BucketName: string; + BucketStatus: number; + ChargedReadQuota: number; + CreateAt: number; + GlobalVirtualGroupFamilyId: number; + Id: string; + Owner: string; + PaymentAddress: string; + SourceType: number; + Visibility: number; +} + +export interface StreamRecord { + Account: string; + CrudTimestamp: string; + NetflowRate: string; + StaticBalance: string; + BufferBalance: string; + LockBalance: string; + Status: string; + SettleTimestamp: string; + OutFlowCount: string; + FrozenNetflowRate: string; +} + +export interface ObjectMeta { + ObjectInfo: ObjectInfo; + LockedBalance: string; + Removed: boolean; + UpdateAt: number; + DeleteAt: number; + DeleteReason: string; + Operator: string; + CreateTxHash: string; + UpdateTxHash: string; + SealTxHash: string; +} + +export interface ObjectInfo { + BucketName: string; + Checksums: string[]; + ContentType: string; + CreateAt: number; + Creator: string; + Id: number; + LocalVirtualGroupId: number; + ObjectName: string; + ObjectStatus: number; + Owner: string; + PayloadSize: number; + RedundancyType: number; + SourceType: number; + Visibility: number; +} + +export function formatBucketInfo(o: BucketInfo) { + return { + ...o, + // PrimarySpId: Number(item.BucketInfo.PrimarySpId), + BucketStatus: Number(o.BucketStatus), + ChargedReadQuota: Number(o.ChargedReadQuota), + CreateAt: Number(o.CreateAt), + GlobalVirtualGroupFamilyId: Number(o.GlobalVirtualGroupFamilyId), + SourceType: Number(o.SourceType), + Visibility: Number(o.Visibility), + }; +} + +export function formatObjectInfo(o: ObjectInfo) { + return { + ...o, + CreateAt: Number(o.CreateAt), + Id: Number(o.Id), + LocalVirtualGroupId: Number(o.LocalVirtualGroupId), + ObjectStatus: Number(o.ObjectStatus), + PayloadSize: Number(o.PayloadSize), + RedundancyType: Number(o.RedundancyType), + SourceType: Number(o.SourceType), + Visibility: Number(o.Visibility), + }; +} + +export function convertStrToBool(str: string) { + return String(str).toLowerCase() === 'true'; +} + +export interface ReadRecord { + ObjectName: string; + ObjectID: string; + ReadAccountAddress: string; + ReadTimestampUs: number; + ReadSize: number; +} + +export function formatReadRecord(o: ReadRecord) { + return { + ...o, + ReadTimestampUs: Number(o.ReadTimestampUs), + ReadSize: Number(o.ReadSize), + }; +} + +export interface GroupInfo { + Owner: string; + GroupName: string; + SourceType: number; + Id: number; + Extra: string; +} + +export function formatGroupInfo(o: GroupInfo) { + return { + ...o, + SourceType: Number(o.SourceType), + Id: Number(o.Id), + }; +} diff --git a/packages/chain-sdk/src/types/sp-xml/GetBucketMetaResponse.ts b/packages/chain-sdk/src/types/sp-xml/GetBucketMetaResponse.ts new file mode 100644 index 00000000..55272bf9 --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/GetBucketMetaResponse.ts @@ -0,0 +1,15 @@ +import { BucketMeta, StreamRecord } from './Common'; + +export interface GetBucketMetaRequest { + bucketName: string; + endpoint: string; +} + +export interface GetBucketMetaResponse { + GfSpGetBucketMetaResponse: GfSPGetBucketMetaResponse; +} + +export interface GfSPGetBucketMetaResponse { + Bucket: BucketMeta; + StreamRecord: StreamRecord; +} diff --git a/packages/chain-sdk/src/types/sp-xml/GetObjectMetaResponse.ts b/packages/chain-sdk/src/types/sp-xml/GetObjectMetaResponse.ts new file mode 100644 index 00000000..5c6d215d --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/GetObjectMetaResponse.ts @@ -0,0 +1,15 @@ +import { ObjectMeta } from './Common'; + +export interface GetObjectMetaRequest { + bucketName: string; + objectName: string; + endpoint: string; +} + +export interface GetObjectMetaResponse { + GfSpGetObjectMetaResponse: GfSPGetObjectMetaResponse; +} + +export interface GfSPGetObjectMetaResponse { + Object: ObjectMeta; +} diff --git a/packages/chain-sdk/src/types/sp-xml/GetUserBucketsResponse.ts b/packages/chain-sdk/src/types/sp-xml/GetUserBucketsResponse.ts new file mode 100644 index 00000000..45c09a1e --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/GetUserBucketsResponse.ts @@ -0,0 +1,9 @@ +import { BucketMeta } from './Common'; + +export interface GetUserBucketsResponse { + GfSpGetUserBucketsResponse: GfSPGetUserBucketsResponse; +} + +export interface GfSPGetUserBucketsResponse { + Buckets: BucketMeta[]; +} diff --git a/packages/chain-sdk/src/types/sp-xml/ListBucketReadRecordResponse.ts b/packages/chain-sdk/src/types/sp-xml/ListBucketReadRecordResponse.ts new file mode 100644 index 00000000..ed7906aa --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/ListBucketReadRecordResponse.ts @@ -0,0 +1,10 @@ +import { ReadRecord } from './Common'; + +export interface ListBucketReadRecordResponse { + GetBucketReadQuotaResult: GetBucketReadQuotaResult; +} + +export interface GetBucketReadQuotaResult { + NextStartTimestampUs: string; + ReadRecords: ReadRecord[]; +} diff --git a/packages/chain-sdk/src/types/sp-xml/ListBucketsByIDsResponse.ts b/packages/chain-sdk/src/types/sp-xml/ListBucketsByIDsResponse.ts new file mode 100644 index 00000000..84c2c223 --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/ListBucketsByIDsResponse.ts @@ -0,0 +1,24 @@ +import { BucketInfo } from './Common'; + +export interface ListBucketsByIDsResponse { + GfSpListBucketsByIDsResponse: GfSpListBucketsByIDsResponse; +} + +export interface GfSpListBucketsByIDsResponse { + BucketEntry: BucketEntry[]; +} + +export interface BucketEntry { + Id: number; + Value: { + BucketInfo: BucketInfo; + Removed: boolean; + DeleteAt: number; + DeleteReason: string; + Operator: string; + CreateTxHash: string; + UpdateTxHash: string; + UpdateAt: number; + UpdateTime: number; + }; +} diff --git a/packages/chain-sdk/src/types/sp-xml/ListGroupsMembersResponse.ts b/packages/chain-sdk/src/types/sp-xml/ListGroupsMembersResponse.ts new file mode 100644 index 00000000..7c4b9441 --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/ListGroupsMembersResponse.ts @@ -0,0 +1,21 @@ +import { GroupInfo } from './Common'; + +export interface ListGroupsMembersResponse { + GfSpGetGroupMembersResponse: GfSpGetGroupMembersResponse; +} + +export interface GfSpGetGroupMembersResponse { + Groups: Group[]; +} + +interface Group { + Group: GroupInfo; + AccountId: string; + Operator: string; + CreateAt: number; + CreateTime: number; + UpdateAt: number; + UpdateTime: number; + Removed: boolean; + ExpirationTime: string; +} diff --git a/packages/chain-sdk/src/types/sp-xml/ListGroupsResponse.ts b/packages/chain-sdk/src/types/sp-xml/ListGroupsResponse.ts new file mode 100644 index 00000000..d2755afd --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/ListGroupsResponse.ts @@ -0,0 +1,20 @@ +import { GroupInfo } from './Common'; + +export interface ListGroupsResponse { + GfSpGetGroupListResponse: GfSpGetGroupListResponse; +} + +interface Group { + Group: GroupInfo; + Operator: string; + CreateAt: number; + CreateTime: number; + UpdateAt: number; + UpdateTime: number; + Removed: boolean; +} + +export interface GfSpGetGroupListResponse { + Groups: Group[]; + Count: number; +} diff --git a/packages/chain-sdk/src/types/sp-xml/ListObjectsByBucketNameResponse.ts b/packages/chain-sdk/src/types/sp-xml/ListObjectsByBucketNameResponse.ts new file mode 100644 index 00000000..8579f978 --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/ListObjectsByBucketNameResponse.ts @@ -0,0 +1,18 @@ +import { ObjectMeta } from './Common'; + +export interface ListObjectsByBucketNameResponse { + GfSpListObjectsByBucketNameResponse: GfSPListObjectsByBucketNameResponse; +} + +export interface GfSPListObjectsByBucketNameResponse { + Objects: ObjectMeta[]; + KeyCount: string; + MaxKeys: string; + IsTruncated: boolean; + NextContinuationToken: string; + Name: string; + Prefix: string; + Delimiter: string; + CommonPrefixes: string[]; + ContinuationToken: string; +} diff --git a/packages/chain-sdk/src/types/sp-xml/ListObjectsByIDsResponse.ts b/packages/chain-sdk/src/types/sp-xml/ListObjectsByIDsResponse.ts new file mode 100644 index 00000000..967b742e --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/ListObjectsByIDsResponse.ts @@ -0,0 +1,25 @@ +import { ObjectInfo } from './Common'; + +export interface ListObjectsByIDsResponse { + GfSpListObjectsByIDsResponse: GfSpListObjectsByIDsResponse; +} + +export interface GfSpListObjectsByIDsResponse { + ObjectEntry: ObjectEntry[]; +} + +export interface ObjectEntry { + Id: number; + Value: { + ObjectInfo: ObjectInfo; + LockedBalance: string; + Removed: boolean; + UpdateAt: number; + DeleteAt: number; + DeleteReason: string; + Operator: string; + CreateTxHash: string; + UpdateTxHash: string; + SealTxHash: string; + }; +} diff --git a/packages/chain-sdk/src/types/sp-xml/ListUserGroupsResponse.ts b/packages/chain-sdk/src/types/sp-xml/ListUserGroupsResponse.ts new file mode 100644 index 00000000..b89398af --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/ListUserGroupsResponse.ts @@ -0,0 +1,21 @@ +import { GroupInfo } from './Common'; + +export interface ListUserGroupsResponse { + GfSpGetUserGroupsResponse: GfSpGetUserGroupsResponse; +} + +export interface GfSpGetUserGroupsResponse { + Groups: Group[]; +} + +interface Group { + Group: GroupInfo; + AccountId: string; + Operator: string; + CreateAt: number; + CreateTime: number; + UpdateAt: number; + UpdateTime: number; + Removed: boolean; + ExpirationTime: string; +} diff --git a/packages/chain-sdk/src/types/sp-xml/ListUserOwnedGroupsResponse.ts b/packages/chain-sdk/src/types/sp-xml/ListUserOwnedGroupsResponse.ts new file mode 100644 index 00000000..c3e1a058 --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/ListUserOwnedGroupsResponse.ts @@ -0,0 +1,21 @@ +import { GroupInfo } from './Common'; + +export interface ListUserOwnedGroupsResponse { + GfSpGetUserOwnedGroupsResponse: GfSpGetUserOwnedGroupsResponse; +} + +export interface GfSpGetUserOwnedGroupsResponse { + Groups: Group[]; +} + +interface Group { + Group: GroupInfo; + AccountId: string; + Operator: string; + CreateAt: number; + CreateTime: number; + UpdateAt: number; + UpdateTime: number; + Removed: boolean; + ExpirationTime: string; +} diff --git a/packages/chain-sdk/src/types/sp-xml/ReadQuotaResponse.ts b/packages/chain-sdk/src/types/sp-xml/ReadQuotaResponse.ts new file mode 100644 index 00000000..5498c434 --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/ReadQuotaResponse.ts @@ -0,0 +1,10 @@ +export interface ReadQuotaResponse { + GetReadQuotaResult: { + BucketName: string; + BucketID: string; + ReadQuotaSize: number; + SPFreeReadQuotaSize: number; + ReadConsumedSize: number; + FreeConsumedSize: number; + }; +} diff --git a/packages/chain-sdk/src/types/sp-xml/RequestErrorResponse.ts b/packages/chain-sdk/src/types/sp-xml/RequestErrorResponse.ts new file mode 100644 index 00000000..c9ebf94c --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/RequestErrorResponse.ts @@ -0,0 +1,6 @@ +export interface RequestErrorResponse { + Error: { + Code: string; + Message: string; + }; +} diff --git a/packages/chain-sdk/src/types/sp-xml/RequestNonceResponse.ts b/packages/chain-sdk/src/types/sp-xml/RequestNonceResponse.ts new file mode 100644 index 00000000..aa129d46 --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/RequestNonceResponse.ts @@ -0,0 +1,8 @@ +export interface RequestNonceResponse { + RequestNonceResp: { + CurrentNonce: number; + CurrentPublicKey: string; + ExpiryDate: string; + NextNonce: number; + }; +} diff --git a/packages/chain-sdk/src/types/sp-xml/VerifyPermissionResponse.ts b/packages/chain-sdk/src/types/sp-xml/VerifyPermissionResponse.ts new file mode 100644 index 00000000..f0aed8ae --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/VerifyPermissionResponse.ts @@ -0,0 +1,7 @@ +export interface VerifyPermissionResponse { + QueryVerifyPermissionResponse: QueryVerifyPermissionResponse; +} + +export interface QueryVerifyPermissionResponse { + Effect: number; +} diff --git a/packages/chain-sdk/src/types/sp-xml/index.ts b/packages/chain-sdk/src/types/sp-xml/index.ts new file mode 100644 index 00000000..3bf8b1a8 --- /dev/null +++ b/packages/chain-sdk/src/types/sp-xml/index.ts @@ -0,0 +1,14 @@ +export * from './GetBucketMetaResponse'; +export * from './GetObjectMetaResponse'; +export * from './GetUserBucketsResponse'; +export * from './ListBucketReadRecordResponse'; +export * from './ListBucketsByIDsResponse'; +export * from './ListGroupsMembersResponse'; +export * from './ListGroupsResponse'; +export * from './ListObjectsByBucketNameResponse'; +export * from './ListObjectsByIDsResponse'; +export * from './ListUserGroupsResponse'; +export * from './ReadQuotaResponse'; +export * from './RequestErrorResponse'; +export * from './RequestNonceResponse'; +export * from './VerifyPermissionResponse'; diff --git a/packages/chain-sdk/src/types/storage.ts b/packages/chain-sdk/src/types/storage.ts index 82f47420..ce8b83d1 100644 --- a/packages/chain-sdk/src/types/storage.ts +++ b/packages/chain-sdk/src/types/storage.ts @@ -1,8 +1,9 @@ +import { ActionType } from '@bnb-chain/greenfield-cosmos-types/greenfield/permission/common'; import { RedundancyType, + SourceType, VisibilityType, } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/common'; -import { MsgMigrateBucket } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; export interface IBaseGetCreateBucket { bucketName: string; @@ -13,21 +14,9 @@ export interface IBaseGetCreateBucket { primarySpAddress: string; }; duration?: number; + paymentAddress: string; } -export interface ICreateBucketByOffChainAuth extends IBaseGetCreateBucket { - signType: 'offChainAuth'; - domain: string; - seedString: string; -} - -export interface ICreateBucketByAuthV1 extends IBaseGetCreateBucket { - signType?: 'authTypeV1'; - privateKey: string; -} - -export type TCreateBucket = ICreateBucketByOffChainAuth | ICreateBucketByAuthV1; - export interface ISpInfo { id: number; endpoint: string; @@ -37,7 +26,7 @@ export interface ISpInfo { } export interface IObjectResultType { - code: number; + code: number | string; xml?: Document; message?: string; statusCode?: number; @@ -91,31 +80,17 @@ export type BucketProps = { export type TBaseGetBucketReadQuota = { bucketName: string; - endpoint: string; + endpoint?: string; duration?: number; year?: number; month?: number; }; -export type TGetBucketReadQuotaByAuthTypeV2 = TBaseGetBucketReadQuota & { - signType?: 'authTypeV2'; -}; - -export type TGetBucketReadQuotaByOffChainAuth = TBaseGetBucketReadQuota & { - signType: 'offChainAuth'; - domain: string; - seedString: string; - address: string; -}; - -export type TGetBucketReadQuota = - | TGetBucketReadQuotaByAuthTypeV2 - | TGetBucketReadQuotaByOffChainAuth; - export interface IQuotaProps { readQuota: number; freeQuota: number; consumedQuota: number; + freeConsumedSize: number; } export type TBaseGetCreateObject = { @@ -128,25 +103,9 @@ export type TBaseGetCreateObject = { duration?: number; contentLength: number; expectCheckSums: string[]; + endpoint?: string; }; -export type SignTypeV1 = { - signType: 'authTypeV1'; - privateKey: string; -}; - -export type SignTypeOffChain = { - signType: 'offChainAuth'; - domain: string; - seedString: string; -}; - -export type TCreateObjectByOffChainAuth = TBaseGetCreateObject & SignTypeOffChain; - -export type TCreateObjectByAuthTypeV1 = TBaseGetCreateObject & SignTypeV1; - -export type TCreateObject = TCreateObjectByOffChainAuth | TCreateObjectByAuthTypeV1; - export interface ICreateObjectMsgType { creator: string; bucket_name: string; @@ -169,44 +128,26 @@ export type TBasePutObject = { bucketName: string; objectName: string; txnHash: string; - body: Blob; + body: File; duration?: number; + endpoint?: string; }; -export type TPutObjectByAuthTypeV1 = TBasePutObject & { - signType?: 'authTypeV1'; - privateKey: string; -}; - -export type TPutObjectByOffChainAuth = TBasePutObject & { - signType: 'offChainAuth'; - domain: string; - seedString: string; - address: string; -}; - -export type TPutObject = TPutObjectByAuthTypeV1 | TPutObjectByOffChainAuth; - export type TBaseGetObject = { bucketName: string; objectName: string; - endpoint?: string; duration?: number; + endpoint?: string; }; -export type TGetObjectByAuthTypeV2 = TBaseGetObject & { - signType?: 'authTypeV2'; -}; - -export type TGetObjectByOffChainAuth = TBaseGetObject & { - signType: 'offChainAuth'; - domain: string; - seedString: string; - address: string; +export type TBaseGetPrivewObject = { + bucketName: string; + objectName: string; + duration?: number; + queryMap: Record; + endpoint?: string; }; -export type TGetObject = TGetObjectByAuthTypeV2 | TGetObjectByOffChainAuth; - export type TListObjects = { bucketName: string; duration?: number; @@ -223,46 +164,6 @@ export type TDownloadFile = { month?: number; }; -export interface IObjectResponse { - create_tx_hash: string; - delete_at: string; - delete_reason: string; - locked_balance: string; - operator: string; - removed: boolean; - seal_tx_hash: string; - update_at: string; - update_tx_hash: string; - object_info: { - bucket_name: string; - checksums: Array; - content_type: string; - create_at: string; - creator: string; - id: string; - local_virtual_group_id: number; - object_name: string; - object_status: number; - owner: string; - payload_size: string; - redundancy_type: string; - source_type: string; - visibility: number; - }; -} -export interface IObjectsProps { - common_prefixes: Array; - continuation_token: string; - delimiter: string; - is_truncated: boolean; - key_count: string; - max_keys: string; - name: string; - next_continuation_token: string; - objects: IObjectResponse[]; - prefix: string; -} - export interface IGetObjectStaus { bucketName: string; objectName: string; @@ -348,23 +249,6 @@ export interface TGetCurrentSeedStringParams { provider: any; } -export interface IBaseMigrateBucket { - params: MsgMigrateBucket; - spInfo: ISpInfo; -} - -export interface IMigrateBucketByOffChainAuth extends IBaseMigrateBucket { - signType: 'offChainAuth'; - domain: string; - seedString: string; -} - -export interface IMigrateBucketByAuthTypeV2 extends IBaseMigrateBucket { - signType?: 'authTypeV2'; -} - -export type IMigrateBucket = IMigrateBucketByOffChainAuth | IMigrateBucketByAuthTypeV2; - export interface IMigrateBucketMsgType { operator: string; bucket_name: string; @@ -375,3 +259,52 @@ export interface IMigrateBucketMsgType { global_virtual_group_family_id: number; }; } + +export type TListBucketReadRecord = { + bucketName: string; + endpoint?: string; + maxRecords: number; + startTimeStamp: number; + endTimeStamp: number; +}; + +export type TListGroups = { + name: string; + prefix: string; + sourceType?: keyof typeof SourceType; + limit?: number; + offset?: number; +}; + +export type TListObjectsByIDsRequest = { + ids: string[]; +}; + +export type TListBucketsByIDsRequest = { + ids: string[]; +}; + +export type TVerifyPermissionRequest = { + operator: string; + bucketName: string; + objectName?: string; + action: keyof typeof ActionType; +}; + +export type TListGroupsMembersRequest = { + groupId: number; + limit?: number; + startAfter?: string; +}; + +export type TListUserGroupRequest = { + address: string; + limit?: number; + startAfter?: string; +}; + +export type TListUserOwnedGroupRequest = { + address: string; + limit?: number; + startAfter?: string; +}; diff --git a/packages/chain-sdk/src/utils/allowance.ts b/packages/chain-sdk/src/utils/allowance.ts index d35fc54b..7338aad5 100644 --- a/packages/chain-sdk/src/utils/allowance.ts +++ b/packages/chain-sdk/src/utils/allowance.ts @@ -4,7 +4,13 @@ import { } from '@bnb-chain/greenfield-cosmos-types/cosmos/feegrant/v1beta1/feegrant'; import { MsgGrantAllowance } from '@bnb-chain/greenfield-cosmos-types/cosmos/feegrant/v1beta1/tx'; import { Any } from '@bnb-chain/greenfield-cosmos-types/google/protobuf/any'; -import { AllowedMsgAllowanceTypeUrl, BasicAllowanceTypeUrl, DEFAULT_DENOM } from '..'; +import { Timestamp } from '@bnb-chain/greenfield-cosmos-types/google/protobuf/timestamp'; +import { + AllowedMsgAllowanceTypeUrl, + BasicAllowanceTypeUrl, + DEFAULT_DENOM, + fromTimestamp, +} from '..'; export interface IGrantAllowance { amount: string; @@ -12,11 +18,13 @@ export interface IGrantAllowance { allowedMessages: string[]; granter: MsgGrantAllowance['granter']; grantee: MsgGrantAllowance['grantee']; + expirationTime: Timestamp; } export const newBasicAllowance = ( amount: string, denom: string = DEFAULT_DENOM, + expirationTime: Timestamp, ): BasicAllowance => { return { spendLimit: [ @@ -25,7 +33,7 @@ export const newBasicAllowance = ( denom, }, ], - // expiration: null, + expiration: expirationTime, }; }; @@ -61,12 +69,13 @@ export const newMarshal = ( amount: string, denom: string = DEFAULT_DENOM, allowed_messages: string[], + expirationTime: Timestamp, ) => { return { '@type': AllowedMsgAllowanceTypeUrl, allowance: { '@type': BasicAllowanceTypeUrl, - expiration: null, + expiration: fromTimestamp(expirationTime), spend_limit: [ { amount, diff --git a/packages/chain-sdk/src/utils/auth.ts b/packages/chain-sdk/src/utils/auth.ts deleted file mode 100644 index 23edbaff..00000000 --- a/packages/chain-sdk/src/utils/auth.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { hexlify, joinSignature } from '@ethersproject/bytes'; -import { SigningKey } from '@ethersproject/signing-key'; -import { Headers } from 'cross-fetch'; -import { keccak256 } from 'ethereum-cryptography/keccak.js'; -import { sha256 } from 'ethereum-cryptography/sha256.js'; -import { utf8ToBytes } from 'ethereum-cryptography/utils.js'; -import { METHOD_GET, METHOD_POST, METHOD_PUT, MOCK_SIGNATURE } from './http'; - -export const getCanonicalHeaders = (reqMeta: Partial, reqHeaders: Headers) => { - const sortedHeaders = getSortedHeaders(reqHeaders, SUPPORTED_HEADERS); - - const res: string[] = []; - sortedHeaders.forEach((k) => { - const v = reqHeaders.get(k); - res.push(`${k}:${v}`); - }); - - if (reqMeta.url && reqMeta.url.hostname) { - res.push(reqMeta.url.hostname); - } - - res.push(''); - return res.join('\n'); -}; - -const getSortedHeaders = (reqHeaders: Headers, supportHeaders: string[]) => { - const signedHeaders: string[] = []; - - reqHeaders.forEach((v, k) => { - if (supportHeaders.includes(k)) { - signedHeaders.push(k); - } - }); - - return signedHeaders.sort(); -}; - -const getSignedHeaders = (reqHeaders: Headers) => { - const sortedHeaders = getSortedHeaders(reqHeaders, SUPPORTED_HEADERS); - - return sortedHeaders.join(';'); -}; - -export const getAuthorizationAuthTypeV1 = (reqMeta: Partial, privateKey: string) => { - const reqHeaders = newRequestHeadersByMeta(reqMeta); - - const canonicalHeaders = getCanonicalHeaders(reqMeta, reqHeaders); - - const signedHeaders = getSignedHeaders(reqHeaders); - - const canonicalRequestArr = [ - reqMeta.method!, - reqMeta.url?.path, - reqMeta.url?.query, - canonicalHeaders, - signedHeaders, - ]; - - const canonicalRequest = canonicalRequestArr.join('\n'); - // console.log('canonicalRequest', canonicalRequest); - - const unsignedMsg = getMsgToSign(utf8ToBytes(canonicalRequest)); - const sig = secpSign(unsignedMsg, privateKey); - - const authorization = `authTypeV1 ECDSA-secp256k1, SignedMsg=${hexlify( - Buffer.from(unsignedMsg), - ).slice(2)}, Signature=${sig.slice(2)}`; - return authorization; -}; - -const newRequestHeadersByMeta = (meta: Partial) => { - const headers = new Headers(); - - // console.log('meta', meta); - if (meta.contentType || meta.contentType === '') { - headers.set(HTTPHeaderContentType, meta.contentType); - } else { - headers.set(HTTPHeaderContentType, 'application/octet-stream'); - } - - if (meta.txnHash && meta.txnHash !== '') { - headers.set(HTTPHeaderTransactionHash, meta.txnHash); - } - - if (meta.contentSHA256) { - headers.set(HTTPHeaderContentSHA256, meta.contentSHA256); - } - - if (meta.txnMsg) { - headers.set(HTTPHeaderUnsignedMsg, meta.txnMsg); - } - - headers.set(HTTPHeaderDate, meta.date!); - - return headers; -}; - -export const getAuthorizationAuthTypeV2 = () => { - const signature = MOCK_SIGNATURE; - const authorization = `authTypeV2 ECDSA-secp256k1, Signature=${signature}`; - - return authorization; -}; - -const HTTPHeaderContentSHA256 = 'X-Gnfd-Content-Sha256'.toLocaleLowerCase(); -const HTTPHeaderTransactionHash = 'X-Gnfd-Txn-Hash'.toLocaleLowerCase(); -const HTTPHeaderObjectID = 'X-Gnfd-Object-ID'.toLocaleLowerCase(); -const HTTPHeaderRedundancyIndex = 'X-Gnfd-Redundancy-Index'.toLocaleLowerCase(); -const HTTPHeaderResource = 'X-Gnfd-Resource'.toLocaleLowerCase(); -const HTTPHeaderDate = 'X-Gnfd-Date'.toLocaleLowerCase(); -const HTTPHeaderRange = 'Range'.toLocaleLowerCase(); -const HTTPHeaderPieceIndex = 'X-Gnfd-Piece-Index'.toLocaleLowerCase(); -const HTTPHeaderContentType = 'Content-Type'.toLocaleLowerCase(); -const HTTPHeaderContentMD5 = 'Content-MD5'.toLocaleLowerCase(); -const HTTPHeaderUnsignedMsg = 'X-Gnfd-Unsigned-Msg'.toLocaleLowerCase(); -const HTTPHeaderUserAddress = 'X-Gnfd-User-Address'.toLocaleLowerCase(); - -const SUPPORTED_HEADERS = [ - HTTPHeaderContentSHA256, - HTTPHeaderTransactionHash, - HTTPHeaderObjectID, - HTTPHeaderRedundancyIndex, - HTTPHeaderResource, - HTTPHeaderDate, - HTTPHeaderRange, - HTTPHeaderPieceIndex, - HTTPHeaderContentType, - HTTPHeaderContentMD5, - HTTPHeaderUnsignedMsg, - HTTPHeaderUserAddress, -]; - -const secpSign = (digestBz: Uint8Array, privateKey: string) => { - const signingKey = new SigningKey(privateKey); - const signature = signingKey.signDigest(digestBz); - let res = joinSignature(signature); - - const v = res.slice(-2); - if (v === '1c') res = res.slice(0, -2) + '01'; - if (v === '1b') res = res.slice(0, -2) + '00'; - - return res; -}; - -const getMsgToSign = (unsignedBytes: Uint8Array): Uint8Array => { - const signBytes = sha256(unsignedBytes); - const res = keccak256(signBytes); - return res; -}; - -export interface ReqMeta { - method: typeof METHOD_GET | typeof METHOD_POST | typeof METHOD_PUT; - bucketName: string; - objectName: string; - contentType: string; - url: { - hostname: string; - query: string; - path: string; - }; - date: string; - contentSHA256: string; - txnMsg: string; - txnHash: string; -} diff --git a/packages/chain-sdk/src/utils/encoding.ts b/packages/chain-sdk/src/utils/encoding.ts index dc22e1e2..eec9c444 100644 --- a/packages/chain-sdk/src/utils/encoding.ts +++ b/packages/chain-sdk/src/utils/encoding.ts @@ -14,7 +14,7 @@ const decodeFromHex = (hex = '') => { return result.join(''); }; -const encodeObjectToHexString = (jsonObject: any) => { +const encodeObjectToHexString = (jsonObject: object) => { const utf8Encoder = new TextEncoder(); const utf8Bytes = utf8Encoder.encode(JSON.stringify(jsonObject)); return Array.from(utf8Bytes) diff --git a/packages/chain-sdk/src/utils/helpers.ts b/packages/chain-sdk/src/utils/helpers.ts new file mode 100644 index 00000000..ef70c27b --- /dev/null +++ b/packages/chain-sdk/src/utils/helpers.ts @@ -0,0 +1 @@ +export * from '@bnb-chain/greenfield-cosmos-types/helpers'; diff --git a/packages/chain-sdk/src/utils/http.ts b/packages/chain-sdk/src/utils/http.ts index a2d83b47..1bc6aaae 100644 --- a/packages/chain-sdk/src/utils/http.ts +++ b/packages/chain-sdk/src/utils/http.ts @@ -1,13 +1,6 @@ import fetch from 'cross-fetch'; -const EMPTY_STRING_SHA256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'; -const MOCK_SIGNATURE = '1234567812345678123456781234567812345678123456781234567812345678'; -const NORMAL_ERROR_CODE = 404; -const METHOD_GET = 'GET'; -const METHOD_POST = 'POST'; -const METHOD_PUT = 'PUT'; - -function timeoutAfter(duration: number) { +export function delayMs(duration: number) { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('request time out')); @@ -18,7 +11,7 @@ function timeoutAfter(duration: number) { const fetchWithTimeout = async (fetchUrl = '', fetchOptions: any = {}, duration = 30000) => { try { const response = (await Promise.race([ - timeoutAfter(duration), + delayMs(duration), fetch(fetchUrl, fetchOptions), ])) as Response; return response; @@ -27,25 +20,4 @@ const fetchWithTimeout = async (fetchUrl = '', fetchOptions: any = {}, duration } }; -const parseErrorXml = async (result: Response) => { - const xmlText = await result.text(); - const xml = await new window.DOMParser().parseFromString(xmlText, 'text/xml'); - const code = (xml as XMLDocument).getElementsByTagName('Code')[0].textContent; - const message = (xml as XMLDocument).getElementsByTagName('Message')[0].textContent; - - return { - code, - message, - }; -}; - -export { - EMPTY_STRING_SHA256, - MOCK_SIGNATURE, - NORMAL_ERROR_CODE, - METHOD_GET, - METHOD_POST, - METHOD_PUT, - fetchWithTimeout, - parseErrorXml, -}; +export { fetchWithTimeout }; diff --git a/packages/chain-sdk/src/utils/index.ts b/packages/chain-sdk/src/utils/index.ts index f04608fd..6758d309 100644 --- a/packages/chain-sdk/src/utils/index.ts +++ b/packages/chain-sdk/src/utils/index.ts @@ -4,3 +4,4 @@ export * from './grn'; export * from './s3'; export * from './time'; export * from './units'; +export * from './helpers'; diff --git a/packages/file-handle/CHANGELOG.md b/packages/file-handle/CHANGELOG.md index a3147bdb..78c51c6c 100644 --- a/packages/file-handle/CHANGELOG.md +++ b/packages/file-handle/CHANGELOG.md @@ -1,5 +1,21 @@ # @bnb-chain/greenfiled-file-handle +## 0.2.1 + +### Patch Changes + +- [#212](https://github.com/bnb-chain/greenfield-js-sdk/pull/212) + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Using tinygo reduce size + +## 0.2.1-alpha.0 + +### Patch Changes + +- [#212](https://github.com/bnb-chain/greenfield-js-sdk/pull/212) + [`ab9d200`](https://github.com/bnb-chain/greenfield-js-sdk/commit/ab9d20036dda8e972db029025a6140e43c19464d) + Thanks [@rrr523](https://github.com/rrr523)! - feat: Using tinygo reduce size + ## 0.2.0 ### Minor Changes diff --git a/packages/file-handle/config/tsconfig-cjs.json b/packages/file-handle/config/tsconfig-cjs.json deleted file mode 100644 index cb04ce46..00000000 --- a/packages/file-handle/config/tsconfig-cjs.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "target": "ES2015", - "module": "ESNext", - "outDir": "../dist/cjs/", - "esModuleInterop": true, - "moduleResolution": "node" - } -} diff --git a/packages/file-handle/config/tsconfig-esm.json b/packages/file-handle/config/tsconfig-esm.json deleted file mode 100644 index 3f112c77..00000000 --- a/packages/file-handle/config/tsconfig-esm.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../dist/esm/", - "moduleResolution": "node" - } -} diff --git a/packages/file-handle/config/tsconfig-test.json b/packages/file-handle/config/tsconfig-test.json deleted file mode 100644 index 7a0229c2..00000000 --- a/packages/file-handle/config/tsconfig-test.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "target": "ES6", - "module": "CommonJS", - "outDir": "./dist/test", - "esModuleInterop": true, - "moduleResolution": "node" - } -} diff --git a/packages/file-handle/config/tsconfig.json b/packages/file-handle/config/tsconfig.json deleted file mode 100644 index 772fb487..00000000 --- a/packages/file-handle/config/tsconfig.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "baseUrl": ".", - "lib": [ - "dom", - "esnext", - "es6" - ], - "compilerOptions": { - "target": "esnext", - "module": "esnext", - "moduleResolution": "NodeNext", - "strict": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationDir": "../dist/types", - "outDir": "../dist", - "emitDeclarationOnly": false, - "allowUmdGlobalAccess": true, - "allowJs": true - }, - "include": [ - "../src", - "../typing/*.d.ts" - ], - "exclude": [ - "../node_modules", - "../src/files-handle-wasm", - "../wasm" - ] -} diff --git a/packages/file-handle/package.json b/packages/file-handle/package.json index 5307d354..1d3ecef6 100644 --- a/packages/file-handle/package.json +++ b/packages/file-handle/package.json @@ -1,38 +1,25 @@ { "name": "@bnb-chain/greenfiled-file-handle", - "version": "0.2.0", - "description": "greenfield js chain sdk", - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", - "types": "./dist/esm/index.d.ts", + "version": "0.2.1", + "description": "checksums wasm package", + "module": "./dist/browser/esm/index.js", + "main": "./dist/node/index.js", + "types": "./types/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/esm/**.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/**.d.ts", - "default": "./dist/cjs/index.js" - } + "import": "./dist/browser/esm/index.js", + "main": "./dist/node/index.js", + "default": "./dist/node/index.js", + "types": "./types/index.d.ts" }, - "./files": { - "default": "./dist/files-handle-wasm/cjs/node.js" - } - }, - "browser": { - "./dist/cjs/index.js": "./dist/cjs/index.js", - "./dist/esm/index.js": "./dist/esm/index.js", - "./dist/files-handle-wasm/cjs/node.js": "./dist/files-handle-wasm/web/index.js", - "./dist/files-handle-wasm/esm/node.js": "./dist/files-handle-wasm/web/index.js" + "./file-handle.wasm": "./dist/node/file-handle.wasm" }, "scripts": { "predev": "rimraf ./dist", - "dev": "rollup -cw", + "dev": "weboack -w", "prebuild": "rimraf ./dist", - "build": "rollup -c", - "test": "jest", - "lint": "prettier --write './src/*.{ts,tsx,js,jsx}' && eslint ./src/ --ext .js,.jsx,.ts,.tsx --fix" + "build": "webpack", + "test": "" }, "keywords": [], "author": "", @@ -58,19 +45,11 @@ ] }, "devDependencies": { - "@jest/globals": "^29.5.0", - "@rollup/plugin-wasm": "^6.1.2", - "@types/jest": "^29.5.1", - "jest": "^29.5.0", - "rollup": "^2.79.1", - "rollup-plugin-auto-external": "^2.0.0", - "rollup-plugin-copy": "^3.4.0", - "rollup-plugin-node-builtins": "^2.1.2", - "rollup-plugin-polyfill-node": "^0.10.2", - "rollup-plugin-terser": "^7.0.2", - "ts-jest": "^29.1.0", - "ts-node": "^10.9.1", - "tslib": "^2.5.0", - "typescript": "^4.9.5" + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^11.0.0", + "rimraf": "^3.0.2", + "typescript": "^5.1.6", + "webpack": "^5.88.1", + "webpack-cli": "^5.1.4" } -} +} \ No newline at end of file diff --git a/packages/file-handle/rollup.config.js b/packages/file-handle/rollup.config.js deleted file mode 100644 index 4f04f215..00000000 --- a/packages/file-handle/rollup.config.js +++ /dev/null @@ -1,120 +0,0 @@ -import resolve from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import typescript from '@rollup/plugin-typescript'; -import json from '@rollup/plugin-json'; -import autoExternal from 'rollup-plugin-auto-external'; -import { terser } from 'rollup-plugin-terser'; -import { babel } from '@rollup/plugin-babel'; -import builtins from 'rollup-plugin-node-builtins'; -import nodePolyfills from 'rollup-plugin-polyfill-node'; -import wasm from '@rollup/plugin-wasm'; -import copy from 'rollup-plugin-copy'; - -const buildConfig = ({ - input = './src/index.ts', - es5, - browser = true, - minifiedVersion = true, - ...config -}) => { - const build = ({ minified }) => ({ - input, - ...config, - output: { - ...config.output, - }, - plugins: [ - json(), - resolve({ browser }), - commonjs(), - minified && terser(), - ...(es5 - ? [ - babel({ - // babelHelpers: 'bundled', - // presets: ['@babel/preset-env'], - }), - ] - : []), - ...(config.plugins || []), - ], - }); - - const configs = [build({ minified: false })]; - - if (minifiedVersion) { - build({ minified: true }); - } - - return configs; -}; - -export default async () => { - return [ - ...buildConfig({ - input: './src/index.ts', - es5: true, - output: { - dir: './dist/esm', - format: 'esm', - }, - plugins: [ - builtins(), - nodePolyfills(), - wasm({ - targetEnv: 'auto-inline', - }), - resolve({ - preferBuiltins: true, - browser: true, - }), - typescript({ - tsconfig: './config/tsconfig-esm.json', - declarationDir: './dist/esm', - }), - ], - }), - ...buildConfig({ - input: './src/index.ts', - output: { - dir: './dist/cjs', - format: 'cjs', - }, - plugins: [ - autoExternal(), - resolve({ - preferBuiltins: false, - browser: false, - }), - wasm({ - targetEnv: 'node', - }), - commonjs(), - nodePolyfills(), - typescript({ - tsconfig: './config/tsconfig-cjs.json', - declarationDir: './dist/cjs', - }), - ], - }), - { - input: ['./src/files-handle-wasm/node.js', './src/files-handle-wasm/web.js'], - output: [ - { dir: './dist/files-handle-wasm/cjs', format: 'cjs', sourcemap: true }, - { dir: './dist/files-handle-wasm/esm', format: 'esm', sourcemap: true }, - ], - plugins: [ - wasm({ - targetEnv: 'auto-inline', - }), - copy({ - targets: [ - { src: 'src/files-handle-wasm/main.wasm', dest: 'dist/files-handle-wasm/cjs' }, - { src: 'src/files-handle-wasm/wasm_exec_node.js', dest: 'dist/files-handle-wasm/cjs' }, - { src: 'src/files-handle-wasm/wasm_exec.js', dest: 'dist/files-handle-wasm/cjs' }, - ], - }), - ], - }, - ]; -}; diff --git a/packages/file-handle/src/browser/index.js b/packages/file-handle/src/browser/index.js new file mode 100644 index 00000000..bd30832a --- /dev/null +++ b/packages/file-handle/src/browser/index.js @@ -0,0 +1,28 @@ +import { ensureServiceIsRunning, initialize, instantiateWASM } from './init'; +import { DEFAULT_DATA_BLOCKS, DEFAULT_PARITY_BLOCKS, DEFAULT_SEGMENT_SIZE } from '../constants'; + +// 1. modify method of `exports` and `globalThis` export. +export const startRunningService = async (wasmURL) => { + const module = await instantiateWASM(wasmURL); + module.instance.exports; + + // `exports` is a map to `//export` way of TinyGo way. + // const { add } = exports; + + // `globalThis` is a map to complex way of `syscall/js` way. + const { getCheckSums } = globalThis; + + return { + getCheckSums, + }; +}; + +export const getCheckSums = async ( + bytes, + segmentSize = DEFAULT_SEGMENT_SIZE, + dataBlocks = DEFAULT_DATA_BLOCKS, + parityBlocks = DEFAULT_PARITY_BLOCKS, +) => { + await initialize(); + return ensureServiceIsRunning().getCheckSums(bytes, segmentSize, dataBlocks, parityBlocks); +}; diff --git a/packages/file-handle/src/browser/init.js b/packages/file-handle/src/browser/init.js new file mode 100644 index 00000000..c753ec0f --- /dev/null +++ b/packages/file-handle/src/browser/init.js @@ -0,0 +1,47 @@ +import { startRunningService } from '.'; +import Go from './wasm_exec.js'; + +export const initialize = async () => { + if (!initializePromise) { + const input = window.__PUBLIC_FILE_HANDLE_WASM_PATH__; + initializePromise = startRunningService(input).catch((err) => { + // Let the caller try again if this fails. + initializePromise = void 0; + // But still, throw the error back up the caller. + throw err; + }); + } + longLivedService = longLivedService || (await initializePromise); +}; + +export const instantiateWASM = async (wasmURL) => { + let module = undefined; + const go = new Go(); + + if (!WebAssembly.instantiateStreaming) { + WebAssembly.instantiateStreaming = async (resp, importObject) => { + const source = await (await resp).arrayBuffer(); + return await WebAssembly.instantiate(source, importObject); + }; + } + + const fetchAndInstantiateTask = async () => { + return WebAssembly.instantiateStreaming(fetch(wasmURL), go.importObject); + }; + module = await fetchAndInstantiateTask(); + go.run(module.instance); + + return module; +}; + +let initializePromise; +let longLivedService; + +export const ensureServiceIsRunning = () => { + if (!initializePromise) throw new Error('You need to call "initialize" before calling this'); + if (!longLivedService) + throw new Error( + 'You need to wait for the promise returned from "initialize" to be resolved before calling this', + ); + return longLivedService; +}; diff --git a/packages/file-handle/src/browser/wasm_exec.js b/packages/file-handle/src/browser/wasm_exec.js new file mode 100644 index 00000000..56d7352b --- /dev/null +++ b/packages/file-handle/src/browser/wasm_exec.js @@ -0,0 +1,608 @@ +/* eslint-disable */ +// @ts-nocheck +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// This file has been modified for use by the TinyGo compiler. + +// Map multiple JavaScript environments to a single common API, +// preferring web standards over Node.js API. +// +// Environments considered: +// - Browsers +// - Node.js +// - Electron +// - Parcel + +if (typeof global !== 'undefined') { + // global already exists +} else if (typeof window !== 'undefined') { + window.global = window; +} else if (typeof self !== 'undefined') { + self.global = self; +} else { + throw new Error('cannot export Go (neither global, window nor self is defined)'); +} + +if (!global.require && typeof require !== 'undefined') { + global.require = require; +} + +if (!global.fs && global.require) { + global.fs = require('fs'); +} + +const enosys = () => { + const err = new Error('not implemented'); + err.code = 'ENOSYS'; + return err; +}; + +if (!global.fs) { + let outputBuf = ''; + global.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf('\n'); + if (nl != -1) { + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); + } + return buf.length; + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + const n = this.writeSync(fd, buf); + callback(null, n); + }, + chmod(path, mode, callback) { + callback(enosys()); + }, + chown(path, uid, gid, callback) { + callback(enosys()); + }, + close(fd, callback) { + callback(enosys()); + }, + fchmod(fd, mode, callback) { + callback(enosys()); + }, + fchown(fd, uid, gid, callback) { + callback(enosys()); + }, + fstat(fd, callback) { + callback(enosys()); + }, + fsync(fd, callback) { + callback(null); + }, + ftruncate(fd, length, callback) { + callback(enosys()); + }, + lchown(path, uid, gid, callback) { + callback(enosys()); + }, + link(path, link, callback) { + callback(enosys()); + }, + lstat(path, callback) { + callback(enosys()); + }, + mkdir(path, perm, callback) { + callback(enosys()); + }, + open(path, flags, mode, callback) { + callback(enosys()); + }, + read(fd, buffer, offset, length, position, callback) { + callback(enosys()); + }, + readdir(path, callback) { + callback(enosys()); + }, + readlink(path, callback) { + callback(enosys()); + }, + rename(from, to, callback) { + callback(enosys()); + }, + rmdir(path, callback) { + callback(enosys()); + }, + stat(path, callback) { + callback(enosys()); + }, + symlink(path, link, callback) { + callback(enosys()); + }, + truncate(path, length, callback) { + callback(enosys()); + }, + unlink(path, callback) { + callback(enosys()); + }, + utimes(path, atime, mtime, callback) { + callback(enosys()); + }, + }; +} + +if (!global.process) { + global.process = { + getuid() { + return -1; + }, + getgid() { + return -1; + }, + geteuid() { + return -1; + }, + getegid() { + return -1; + }, + getgroups() { + throw enosys(); + }, + pid: -1, + ppid: -1, + umask() { + throw enosys(); + }, + cwd() { + throw enosys(); + }, + chdir() { + throw enosys(); + }, + }; +} + +if (!global.crypto) { + const nodeCrypto = require('crypto'); + global.crypto = { + getRandomValues(b) { + nodeCrypto.randomFillSync(b); + }, + }; +} + +if (!global.performance) { + global.performance = { + now() { + const [sec, nsec] = process.hrtime(); + return sec * 1000 + nsec / 1000000; + }, + }; +} + +if (!global.TextEncoder) { + global.TextEncoder = require('util').TextEncoder; +} + +if (!global.TextDecoder) { + global.TextDecoder = require('util').TextDecoder; +} + +// End of polyfills for common API. + +const encoder = new TextEncoder('utf-8'); +const decoder = new TextDecoder('utf-8'); +var logLine = []; + +export default class Go { + importObject; + constructor() { + this._callbackTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const mem = () => { + // The buffer may change when requesting more memory. + return new DataView(this._inst.exports.memory.buffer); + }; + + const setInt64 = (addr, v) => { + mem().setUint32(addr + 0, v, true); + mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); + }; + + const getInt64 = (addr) => { + const low = mem().getUint32(addr + 0, true); + const high = mem().getInt32(addr + 4, true); + return low + high * 4294967296; + }; + + const loadValue = (addr) => { + const f = mem().getFloat64(addr, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = mem().getUint32(addr, true); + return this._values[id]; + }; + + const storeValue = (addr, v) => { + const nanHead = 0x7ff80000; + + if (typeof v === 'number') { + if (isNaN(v)) { + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 0, true); + return; + } + if (v === 0) { + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 1, true); + return; + } + mem().setFloat64(addr, v, true); + return; + } + + switch (v) { + case undefined: + mem().setFloat64(addr, 0, true); + return; + case null: + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 2, true); + return; + case true: + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 3, true); + return; + case false: + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 4, true); + return; + } + + let id = this._ids.get(v); + if (id === undefined) { + id = this._idPool.pop(); + if (id === undefined) { + id = this._values.length; + } + this._values[id] = v; + this._goRefCounts[id] = 0; + this._ids.set(v, id); + } + this._goRefCounts[id]++; + let typeFlag = 1; + switch (typeof v) { + case 'string': + typeFlag = 2; + break; + case 'symbol': + typeFlag = 3; + break; + case 'function': + typeFlag = 4; + break; + } + mem().setUint32(addr + 4, nanHead | typeFlag, true); + mem().setUint32(addr, id, true); + }; + + const loadSlice = (array, len, cap) => { + return new Uint8Array(this._inst.exports.memory.buffer, array, len); + }; + + const loadSliceOfValues = (array, len, cap) => { + const a = new Array(len); + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8); + } + return a; + }; + + const loadString = (ptr, len) => { + return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); + }; + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + wasi_snapshot_preview1: { + // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write + fd_write: function (fd, iovs_ptr, iovs_len, nwritten_ptr) { + let nwritten = 0; + if (fd == 1) { + for (let iovs_i = 0; iovs_i < iovs_len; iovs_i++) { + let iov_ptr = iovs_ptr + iovs_i * 8; // assuming wasm32 + let ptr = mem().getUint32(iov_ptr + 0, true); + let len = mem().getUint32(iov_ptr + 4, true); + nwritten += len; + for (let i = 0; i < len; i++) { + let c = mem().getUint8(ptr + i); + if (c == 13) { + // CR + // ignore + } else if (c == 10) { + // LF + // write line + let line = decoder.decode(new Uint8Array(logLine)); + logLine = []; + console.log(line); + } else { + logLine.push(c); + } + } + } + } else { + console.error('invalid file descriptor:', fd); + } + mem().setUint32(nwritten_ptr, nwritten, true); + return 0; + }, + fd_close: () => 0, // dummy + fd_fdstat_get: () => 0, // dummy + fd_seek: () => 0, // dummy + proc_exit: (code) => { + if (global.process) { + // Node.js + process.exit(code); + } else { + // Can't exit in a browser. + throw 'trying to exit with code ' + code; + } + }, + random_get: (bufPtr, bufLen) => { + crypto.getRandomValues(loadSlice(bufPtr, bufLen)); + return 0; + }, + }, + env: { + // func ticks() float64 + 'runtime.ticks': () => { + return timeOrigin + performance.now(); + }, + + // func sleepTicks(timeout float64) + 'runtime.sleepTicks': (timeout) => { + // Do not sleep, only reactivate scheduler after the given timeout. + setTimeout(this._inst.exports.go_scheduler, timeout); + }, + + // func finalizeRef(v ref) + 'syscall/js.finalizeRef': (v_addr) => { + // Note: TinyGo does not support finalizers so this is only called + // for one specific case, by js.go:jsString. + const id = mem().getUint32(v_addr, true); + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + }, + + // func stringVal(value string) ref + 'syscall/js.stringVal': (ret_ptr, value_ptr, value_len) => { + const s = loadString(value_ptr, value_len); + storeValue(ret_ptr, s); + }, + + // func valueGet(v ref, p string) ref + 'syscall/js.valueGet': (retval, v_addr, p_ptr, p_len) => { + let prop = loadString(p_ptr, p_len); + let value = loadValue(v_addr); + let result = Reflect.get(value, prop); + storeValue(retval, result); + }, + + // func valueSet(v ref, p string, x ref) + 'syscall/js.valueSet': (v_addr, p_ptr, p_len, x_addr) => { + const v = loadValue(v_addr); + const p = loadString(p_ptr, p_len); + const x = loadValue(x_addr); + Reflect.set(v, p, x); + }, + + // func valueDelete(v ref, p string) + 'syscall/js.valueDelete': (v_addr, p_ptr, p_len) => { + const v = loadValue(v_addr); + const p = loadString(p_ptr, p_len); + Reflect.deleteProperty(v, p); + }, + + // func valueIndex(v ref, i int) ref + 'syscall/js.valueIndex': (ret_addr, v_addr, i) => { + storeValue(ret_addr, Reflect.get(loadValue(v_addr), i)); + }, + + // valueSetIndex(v ref, i int, x ref) + 'syscall/js.valueSetIndex': (v_addr, i, x_addr) => { + Reflect.set(loadValue(v_addr), i, loadValue(x_addr)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + 'syscall/js.valueCall': (ret_addr, v_addr, m_ptr, m_len, args_ptr, args_len, args_cap) => { + const v = loadValue(v_addr); + const name = loadString(m_ptr, m_len); + const args = loadSliceOfValues(args_ptr, args_len, args_cap); + try { + const m = Reflect.get(v, name); + storeValue(ret_addr, Reflect.apply(m, v, args)); + mem().setUint8(ret_addr + 8, 1); + } catch (err) { + storeValue(ret_addr, err); + mem().setUint8(ret_addr + 8, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + 'syscall/js.valueInvoke': (ret_addr, v_addr, args_ptr, args_len, args_cap) => { + try { + const v = loadValue(v_addr); + const args = loadSliceOfValues(args_ptr, args_len, args_cap); + storeValue(ret_addr, Reflect.apply(v, undefined, args)); + mem().setUint8(ret_addr + 8, 1); + } catch (err) { + storeValue(ret_addr, err); + mem().setUint8(ret_addr + 8, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + 'syscall/js.valueNew': (ret_addr, v_addr, args_ptr, args_len, args_cap) => { + const v = loadValue(v_addr); + const args = loadSliceOfValues(args_ptr, args_len, args_cap); + try { + storeValue(ret_addr, Reflect.construct(v, args)); + mem().setUint8(ret_addr + 8, 1); + } catch (err) { + storeValue(ret_addr, err); + mem().setUint8(ret_addr + 8, 0); + } + }, + + // func valueLength(v ref) int + 'syscall/js.valueLength': (v_addr) => { + return loadValue(v_addr).length; + }, + + // valuePrepareString(v ref) (ref, int) + 'syscall/js.valuePrepareString': (ret_addr, v_addr) => { + const s = String(loadValue(v_addr)); + const str = encoder.encode(s); + storeValue(ret_addr, str); + setInt64(ret_addr + 8, str.length); + }, + + // valueLoadString(v ref, b []byte) + 'syscall/js.valueLoadString': (v_addr, slice_ptr, slice_len, slice_cap) => { + const str = loadValue(v_addr); + loadSlice(slice_ptr, slice_len, slice_cap).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + 'syscall/js.valueInstanceOf': (v_addr, t_addr) => { + return loadValue(v_addr) instanceof loadValue(t_addr); + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + 'syscall/js.copyBytesToGo': (ret_addr, dest_addr, dest_len, dest_cap, source_addr) => { + let num_bytes_copied_addr = ret_addr; + let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable + + const dst = loadSlice(dest_addr, dest_len); + const src = loadValue(source_addr); + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + mem().setUint8(returned_status_addr, 0); // Return "not ok" status + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(num_bytes_copied_addr, toCopy.length); + mem().setUint8(returned_status_addr, 1); // Return "ok" status + }, + + // copyBytesToJS(dst ref, src []byte) (int, bool) + // Originally copied from upstream Go project, then modified: + // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 + 'syscall/js.copyBytesToJS': (ret_addr, dest_addr, source_addr, source_len, source_cap) => { + let num_bytes_copied_addr = ret_addr; + let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable + + const dst = loadValue(dest_addr); + const src = loadSlice(source_addr, source_len); + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + mem().setUint8(returned_status_addr, 0); // Return "not ok" status + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(num_bytes_copied_addr, toCopy.length); + mem().setUint8(returned_status_addr, 1); // Return "ok" status + }, + }, + }; + } + + async run(instance) { + this._inst = instance; + this._values = [ + // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + global, + this, + ]; + this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map(); // mapping from JS values to reference ids + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + + const mem = new DataView(this._inst.exports.memory.buffer); + + while (true) { + const callbackPromise = new Promise((resolve) => { + this._resolveCallbackPromise = () => { + if (this.exited) { + throw new Error('bad callback: Go program has already exited'); + } + setTimeout(resolve, 0); // make sure it is asynchronous + }; + }); + this._inst.exports._start(); + if (this.exited) { + break; + } + await callbackPromise; + } + } + + _resume() { + if (this.exited) { + throw new Error('Go program has already exited'); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); + } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } +} + +// if ( +// global.require && +// global.require.main === module && +// global.process && +// global.process.versions && +// !global.process.versions.electron +// ) { +// if (process.argv.length != 3) { +// console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); +// process.exit(1); +// } + +// const go = new Go(); +// WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { +// return go.run(result.instance); +// }).catch((err) => { +// console.error(err); +// process.exit(1); +// }); +// } diff --git a/packages/file-handle/src/constants.js b/packages/file-handle/src/constants.js new file mode 100644 index 00000000..93d69989 --- /dev/null +++ b/packages/file-handle/src/constants.js @@ -0,0 +1,3 @@ +export const DEFAULT_SEGMENT_SIZE = 16 * 1024 * 1024; +export const DEFAULT_DATA_BLOCKS = 4; +export const DEFAULT_PARITY_BLOCKS = 2; diff --git a/packages/file-handle/src/files-handle-wasm/main.wasm b/packages/file-handle/src/files-handle-wasm/main.wasm deleted file mode 100755 index 10a82571bba18447469841d79e723959bba5bf2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3079990 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

    #_e!e0ez`B@*V0d6 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

    !)k&`G3`SP%_DU+#1 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

    RnLmNRa2|w7a8# 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�ZO$XvThzk3HsgXQdl!^1s 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

    E*SZRhZLG!Jk6z)N@LzX%E5M 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