diff --git a/packages/boot/package.json b/packages/boot/package.json index 91b5823f1d7..c5723f500f2 100644 --- a/packages/boot/package.json +++ b/packages/boot/package.json @@ -19,12 +19,12 @@ "license": "Apache-2.0", "dependencies": { "@agoric/assert": "^0.6.0", - "@agoric/cosmic-proto": "^0.4.0", "@agoric/builders": "^0.1.0", + "@agoric/cosmic-proto": "^0.4.0", "@agoric/cosmic-swingset": "^0.41.3", "@agoric/ertp": "^0.16.2", - "@agoric/internal": "^0.3.2", "@agoric/inter-protocol": "^0.16.1", + "@agoric/internal": "^0.3.2", "@agoric/kmarshal": "^0.1.0", "@agoric/notifier": "^0.6.2", "@agoric/orchestration": "^0.1.0", @@ -37,12 +37,12 @@ "@agoric/vm-config": "^0.1.0", "@agoric/zoe": "^0.26.2", "@agoric/zone": "^0.2.2", - "@endo/marshal": "^1.4.1", "@endo/bundle-source": "^3.2.2", "@endo/captp": "^4.1.1", "@endo/eventual-send": "^1.2.1", - "@endo/init": "^1.1.1", "@endo/far": "^1.1.1", + "@endo/init": "^1.1.1", + "@endo/marshal": "^1.4.1", "@endo/promise-kit": "^1.1.1", "@endo/stream": "^1.2.1", "import-meta-resolve": "^2.2.1" diff --git a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts index 8f96c46e1eb..e4fc8ad6294 100644 --- a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts @@ -1,12 +1,15 @@ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import type { ExecutionContext, TestFn } from 'ava'; + +import type { AnyJson } from '@agoric/cosmic-proto'; import { MsgDelegate, MsgDelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; +import { Any } from '@agoric/cosmic-proto/google/protobuf/any'; +import type { ChainAccount, Orchestration } from '@agoric/orchestration'; import { decodeBase64 } from '@endo/base64'; import { M, matches } from '@endo/patterns'; -import { txToBase64 } from '@agoric/orchestration'; import { makeWalletFactoryContext } from './walletFactory.ts'; const makeTestContext = async (t: ExecutionContext) => @@ -22,13 +25,13 @@ const test: TestFn = anyTest; * If adding a new msg, reference the mock in the `sendPacket` switch statement * in [supports.ts](../../tools/supports.ts). */ -const delegateMsgSuccess = txToBase64( +const delegateMsgSuccess = Any.toJSON( MsgDelegate.toProtoMsg({ delegatorAddress: 'cosmos1test', validatorAddress: 'cosmosvaloper1test', amount: { denom: 'uatom', amount: '10' }, }), -); +) as AnyJson; test.before(async t => { t.context = await makeTestContext(t); @@ -97,7 +100,8 @@ test('ICA connection can be closed', async t => { runUtils: { EV }, } = t.context; - const orchestration = await EV.vat('bootstrap').consumeItem('orchestration'); + const orchestration: Orchestration = + await EV.vat('bootstrap').consumeItem('orchestration'); const account = await EV(orchestration).createAccount( 'connection-0', @@ -120,13 +124,13 @@ test('ICA connection can send msg with proto3', async t => { const orchestration = await EV.vat('bootstrap').consumeItem('orchestration'); - /** @type {ChainAccount} */ - const account = await EV(orchestration).createAccount( + const account: ChainAccount = await EV(orchestration).createAccount( 'connection-0', 'connection-0', ); t.truthy(account, 'createAccount returns an account'); + // @ts-expect-error intentional await t.throwsAsync(EV(account).executeEncodedTx('malformed'), { message: 'In "executeEncodedTx" method of (ChainAccount account): arg 0: string "malformed" - Must be a copyArray', @@ -146,6 +150,7 @@ test('ICA connection can send msg with proto3', async t => { const txWithOptions = await EV(account).executeEncodedTx( [delegateMsgSuccess], + // @ts-expect-error XXX TxBody interface { memo: 'TESTING', timeoutHeight: 1_000_000_000n, @@ -157,13 +162,14 @@ test('ICA connection can send msg with proto3', async t => { 'txWithOptions', ); - const delegateMsgFailure = txToBase64( + const delegateMsgFailure = Any.toJSON( MsgDelegate.toProtoMsg({ delegatorAddress: 'cosmos1fail', validatorAddress: 'cosmosvaloper1fail', amount: { denom: 'uatom', amount: '10' }, }), - ); + ) as AnyJson; + await t.throwsAsync(EV(account).executeEncodedTx([delegateMsgFailure]), { message: 'ABCI code: 5: error handling packet: see events for details', }); diff --git a/packages/cosmic-proto/package.json b/packages/cosmic-proto/package.json index d38b04eb45b..187fc6e59c0 100644 --- a/packages/cosmic-proto/package.json +++ b/packages/cosmic-proto/package.json @@ -32,6 +32,10 @@ "types": "./dist/codegen/cosmos/staking/v1beta1/tx.d.ts", "default": "./dist/codegen/cosmos/staking/v1beta1/tx.js" }, + "./google/protobuf/any": { + "types": "./dist/codegen/google/protobuf/any.d.ts", + "default": "./dist/codegen/google/protobuf/any.js" + }, "./swingset/msgs.js": { "types": "./dist/codegen/agoric/swingset/msgs.d.ts", "default": "./dist/codegen/agoric/swingset/msgs.js" diff --git a/packages/cosmic-proto/src/helpers.ts b/packages/cosmic-proto/src/helpers.ts index 8966882c643..237d985d316 100644 --- a/packages/cosmic-proto/src/helpers.ts +++ b/packages/cosmic-proto/src/helpers.ts @@ -2,6 +2,13 @@ import type { QueryAllBalancesRequest } from './codegen/cosmos/bank/v1beta1/quer import type { MsgSend } from './codegen/cosmos/bank/v1beta1/tx.js'; import type { MsgDelegate } from './codegen/cosmos/staking/v1beta1/tx.js'; +/** + * The result of Any.toJSON(). The type in cosms-types says it returns + * `unknown` but it's actually this. The `value` string is a base64 encoding of + * the bytes array. + */ +export type AnyJson = { typeUrl: string; value: string }; + // TODO codegen this by modifying Telescope export type Proto3Shape = { '/cosmos.bank.v1beta1.MsgSend': MsgSend; @@ -34,3 +41,11 @@ export const typedJson = ( ...obj, } as TypedJson; }; + +// TODO make codegen toJSON() return these instead of unknown +/** + * Proto Any with arrays encoded as base64 + */ +export type Base64Any = { + [Prop in keyof T]: T[Prop] extends Uint8Array ? string : T[Prop]; +}; diff --git a/packages/orchestration/package.json b/packages/orchestration/package.json index f8183833395..24c929f676b 100644 --- a/packages/orchestration/package.json +++ b/packages/orchestration/package.json @@ -30,8 +30,8 @@ "homepage": "https://github.com/Agoric/agoric-sdk#readme", "dependencies": { "@agoric/assert": "^0.6.0", - "@agoric/ertp": "^0.16.2", "@agoric/cosmic-proto": "^0.4.0", + "@agoric/ertp": "^0.16.2", "@agoric/internal": "^0.3.2", "@agoric/network": "^0.1.0", "@agoric/notifier": "^0.6.2", @@ -46,11 +46,10 @@ "@endo/patterns": "^1.3.1" }, "devDependencies": { - "@endo/ses-ava": "^1.2.1", "@cosmjs/amino": "^0.32.3", "@cosmjs/proto-signing": "^0.32.3", - "ava": "^5.3.1", - "cosmjs-types": "^0.9.0" + "@endo/ses-ava": "^1.2.1", + "ava": "^5.3.1" }, "ava": { "extensions": { diff --git a/packages/orchestration/src/contracts/stakingAccountHolder.js b/packages/orchestration/src/contracts/stakingAccountHolder.js index a44dd74f1ec..0a61fdbcd59 100644 --- a/packages/orchestration/src/contracts/stakingAccountHolder.js +++ b/packages/orchestration/src/contracts/stakingAccountHolder.js @@ -11,12 +11,13 @@ import { M, prepareExoClassKit } from '@agoric/vat-data'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; import { decodeBase64 } from '@endo/base64'; import { E } from '@endo/far'; -import { txToBase64 } from '../utils/tx.js'; +import { Any } from '@agoric/cosmic-proto/google/protobuf/any'; /** * @import { ChainAccount, ChainAddress } from '../types.js'; * @import { RecorderKit, MakeRecorderKit } from '@agoric/zoe/src/contractSupport/recorder.js'; * @import { Baggage } from '@agoric/swingset-liveslots'; + * @import {AnyJson} from '@agoric/cosmic-proto'; */ const trace = makeTracer('StakingAccountHolder'); @@ -109,12 +110,14 @@ export const prepareStakingAccountHolder = (baggage, makeRecorderKit, zcf) => { const delegatorAddress = this.state.chainAddress; const result = await E(account).executeEncodedTx([ - txToBase64( - MsgDelegate.toProtoMsg({ - delegatorAddress, - validatorAddress, - amount, - }), + /** @type {AnyJson} */ ( + Any.toJSON( + MsgDelegate.toProtoMsg({ + delegatorAddress, + validatorAddress, + amount, + }), + ) ), ]); diff --git a/packages/orchestration/src/orchestration.js b/packages/orchestration/src/orchestration.js index c7dd87f2725..99787a942ed 100644 --- a/packages/orchestration/src/orchestration.js +++ b/packages/orchestration/src/orchestration.js @@ -18,7 +18,7 @@ import '@agoric/network/exported.js'; const { Fail, bare } = assert; const trace = makeTracer('Orchestration'); -/** @import {Proto3Msg} from './utils/tx.js'; */ +/** @import {AnyJson} from '@agoric/cosmic-proto'; */ // TODO improve me /** @typedef {string} ChainAddress */ @@ -124,7 +124,7 @@ const prepareChainAccount = zone => return this.state.port; }, /** - * @param {Proto3Msg[]} msgs + * @param {AnyJson[]} msgs * @param {Omit} [opts] * @returns {Promise} - base64 encoded bytes string. Can be decoded using the corresponding `Msg*Response` object. * @throws {Error} if packet fails to send or an error is returned diff --git a/packages/orchestration/src/utils/tx.js b/packages/orchestration/src/utils/tx.js index bdb98cd04e1..a53e9ee2b04 100644 --- a/packages/orchestration/src/utils/tx.js +++ b/packages/orchestration/src/utils/tx.js @@ -1,24 +1,20 @@ // @ts-check import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; -import { decodeBase64, encodeBase64 } from '@endo/base64'; - -/** @typedef {{ typeUrl: string; value: string; }} Proto3Msg */ +import { encodeBase64 } from '@endo/base64'; +import { Any } from '@agoric/cosmic-proto/google/protobuf/any'; /** * Makes an IBC packet from an array of messages. Expects the `value` of each message * to be base64 encoded bytes. * Skips checks for malformed messages in favor of interface guards. - * @param {Proto3Msg[]} msgs + * @param {import('@agoric/cosmic-proto').AnyJson[]} msgs * // XXX intellisense does not seem to infer well here * @param {Omit} [opts] * @returns {string} - IBC TX packet * @throws {Error} if malformed messages are provided */ export function makeTxPacket(msgs, opts) { - const messages = msgs.map(msg => ({ - typeUrl: msg.typeUrl, - value: decodeBase64(msg.value), - })); + const messages = msgs.map(Any.fromJSON); const bytes = TxBody.encode( TxBody.fromPartial({ messages, @@ -34,19 +30,6 @@ export function makeTxPacket(msgs, opts) { } harden(makeTxPacket); -/** - * base64 encode an EncodeObject for cross-vat communication - * @param {{ typeUrl: string, value: Uint8Array }} tx - * @returns {Proto3Msg} - */ -export function txToBase64(tx) { - return { - typeUrl: tx.typeUrl, - value: encodeBase64(tx.value), - }; -} -harden(txToBase64); - /** * Looks for a result or error key in the response string, and returns * a Base64Bytes string. This string can be decoded using the corresponding diff --git a/packages/orchestration/test/utils/tx.test.js b/packages/orchestration/test/utils/tx.test.js index 43bb867d663..f5b5d201153 100644 --- a/packages/orchestration/test/utils/tx.test.js +++ b/packages/orchestration/test/utils/tx.test.js @@ -1,9 +1,6 @@ import test from '@endo/ses-ava/prepare-endo.js'; -import { - makeTxPacket, - txToBase64, - parsePacketAck, -} from '../../src/utils/tx.js'; +import { Any } from '@agoric/cosmic-proto/google/protobuf/any'; +import { makeTxPacket, parsePacketAck } from '../../src/utils/tx.js'; test('makeTxPacket', t => { const mockMsg = { @@ -21,12 +18,13 @@ test('makeTxPacket', t => { '{"type":1,"data":"ChcKCy9mb28uYmFyLnYxEggKBgoEc2VudBIFaGVsbG8YkE4=","memo":""}', 'accepts options for TxBody', ); - t.throws(() => + t.deepEqual( makeTxPacket([ { typeUrl: mockMsg.typeUrl, }, ]), + '{"type":1,"data":"Cg0KCy9mb28uYmFyLnYx","memo":""}', ); t.throws(() => makeTxPacket([ @@ -40,7 +38,7 @@ test('makeTxPacket', t => { test('txToBase64', t => { t.deepEqual( - txToBase64({ + Any.toJSON({ typeUrl: '/foo.bar.v1', value: new Uint8Array([1, 2, 3]), }), diff --git a/packages/vats/package.json b/packages/vats/package.json index 8e98264c021..f0c6eef27d4 100644 --- a/packages/vats/package.json +++ b/packages/vats/package.json @@ -35,6 +35,7 @@ "@agoric/vat-data": "^0.5.2", "@agoric/zoe": "^0.26.2", "@agoric/zone": "^0.2.2", + "@endo/base64": "^1.0.4", "@endo/far": "^1.1.1", "@endo/import-bundle": "^1.1.1", "@endo/marshal": "^1.4.1", diff --git a/packages/zoe/src/contractFacet/types-ambient.d.ts b/packages/zoe/src/contractFacet/types-ambient.d.ts index baaf6bfd269..858a1c89f3d 100644 --- a/packages/zoe/src/contractFacet/types-ambient.d.ts +++ b/packages/zoe/src/contractFacet/types-ambient.d.ts @@ -207,10 +207,7 @@ type ZcfSeatKit = { zcfSeat: ZCFSeat; userSeat: ERef; }; -type HandleOffer = ( - seat: ZCFSeat, - offerArgs?: OA, -) => OR; +type HandleOffer = (seat: ZCFSeat, offerArgs: OA) => OR; type OfferHandler = | HandleOffer | { diff --git a/yarn.lock b/yarn.lock index 80f63ad474b..81da9ee7358 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1512,23 +1512,11 @@ "@endo/zip" "^1.0.4" ses "^1.4.1" -"@endo/env-options@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@endo/env-options/-/env-options-1.1.2.tgz#38cb86effbdcc2879bb5a4f50650b129000c37c2" - integrity sha512-mhA1kRgZgGxywMAIpUeBI3gtcVUwIYazCOev9mFoZlkxd92aaTuAAJOnvGPeeyfhBpZtQCQ8BnLguBeCh3g6YA== - "@endo/env-options@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@endo/env-options/-/env-options-1.1.3.tgz#fc6969a715c5dba61b42102d4e88c4c5ccb1455d" integrity sha512-Em0Y7ZMc90qwCKzpBaFJQbaErDXyYMnVRtPTC1w0qtgYoNOvPe5lzDmXfiZYPyFIyh+9ZhFwDp8rzy+3erMvCw== -"@endo/errors@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@endo/errors/-/errors-1.2.0.tgz#015dfdf60cc9c12ff27e69d73e1eb41af617318c" - integrity sha512-dY5EeMiKKyx+yDKoOYDrmypcrPyaBbqhK+YW6Ipdq2ctfg9e5drdfLg7UHWNdkk6+nvgdHS4QLUM5H/OHDtU+g== - dependencies: - ses "^1.4.0" - "@endo/errors@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@endo/errors/-/errors-1.2.1.tgz#3f76e443d94c932cbb47371177550df960137bb1" @@ -1557,13 +1545,6 @@ "@babel/traverse" "^7.23.6" source-map "0.7.4" -"@endo/eventual-send@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@endo/eventual-send/-/eventual-send-1.2.0.tgz#e882c0b9807da397100ce9aab6e18941559903e5" - integrity sha512-ixVgadVrBlWRiRAfx2sCVK6ZD2nbjdKSEdNBqIb8DcXkf+D1quZ0/WmA2/w/ljAlK1YbSMTo/UhVMdXYCHcdvA== - dependencies: - "@endo/env-options" "^1.1.2" - "@endo/eventual-send@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@endo/eventual-send/-/eventual-send-1.2.1.tgz#d1b06deeb6c3eb21f7b733ba98e8fa6f050a6db1" @@ -1584,16 +1565,7 @@ "@endo/pass-style" "^1.3.1" "@endo/patterns" "^1.3.1" -"@endo/far@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@endo/far/-/far-1.1.0.tgz#aeb86dc43447d58fd3bc62249f9e7e776c9c93ea" - integrity sha512-phpwBCkoFuzmjWUTXlf8ry7t4H7k/FTdXBTIWJjREmPEEdK+ZPxM3nTAmq3faf17/aSTAaKrdWDC6PNiMCxqNg== - dependencies: - "@endo/errors" "^1.2.0" - "@endo/eventual-send" "^1.2.0" - "@endo/pass-style" "^1.3.0" - -"@endo/far@^1.1.1": +"@endo/far@^1.0.0", "@endo/far@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@endo/far/-/far-1.1.1.tgz#c4df662e1fb3714d50606105bfe485ca01b1f2a5" integrity sha512-ZTEngkTnqQ8Xee1alkuLCsnVNept6a9SwWyBd+BzLHwtDO87cO9dl2t6X1d8ntRzSDFQp7OFFInlXVhidCqqEw== @@ -1657,17 +1629,6 @@ "@endo/stream" "^1.2.1" ses "^1.4.1" -"@endo/pass-style@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@endo/pass-style/-/pass-style-1.3.0.tgz#9d119487df3a53ad7a1b04bd1634f12c25521acb" - integrity sha512-ILbzyARfUMTM7M0o7mlXolyVbcmdjpHxDxIiVkiYK7CMP38a4O9UCjQZaFoDObUFIrIYU5/WZGD+GsCzKJBGsQ== - dependencies: - "@endo/env-options" "^1.1.2" - "@endo/errors" "^1.2.0" - "@endo/eventual-send" "^1.2.0" - "@endo/promise-kit" "^1.1.0" - "@fast-check/ava" "^1.1.5" - "@endo/pass-style@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@endo/pass-style/-/pass-style-1.3.1.tgz#12703d4ff1dd3ef926d397a4f25e132ed89a792d" @@ -1690,13 +1651,6 @@ "@endo/marshal" "^1.4.1" "@endo/promise-kit" "^1.1.1" -"@endo/promise-kit@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@endo/promise-kit/-/promise-kit-1.1.0.tgz#1eaf13830a0af97e2f408eaf518756d65cf7d7d5" - integrity sha512-rYkVbtf4hNYw8L7rNIILX0I18NaM0stuco+hOkX7iDQ9txnKnYyoENXjIqqhap3OQuoLT4W6EXKWjNl4KfGDKA== - dependencies: - ses "^1.4.0" - "@endo/promise-kit@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@endo/promise-kit/-/promise-kit-1.1.1.tgz#1dbb70c7a0c51de788c6b81791885c5044ff33a1" @@ -9921,12 +9875,7 @@ prettier@^2.6.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -prettier@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" - integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== - -prettier@^3.2.5: +prettier@^3.0.0, prettier@^3.2.5: version "3.2.5" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== @@ -10572,13 +10521,6 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" -ses@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ses/-/ses-1.4.0.tgz#ee02d415dc30287b14532efd92f7342af9a295e3" - integrity sha512-uMqWGE3D+FMmlJ5C1jPt6T9Y2XIraZC2yMpoA80/FwnsUDsTZPumdM/YjIemXzpZmCJZOboEFXWRlYeScYMWrQ== - dependencies: - "@endo/env-options" "^1.1.2" - ses@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/ses/-/ses-1.4.1.tgz#ee60fa9f49bb898250aa8eb8bb3738c48fab438f" @@ -11595,12 +11537,7 @@ typescript-eslint@^7.3.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -typescript@^5.5.0-dev.20240327: - version "5.5.0-dev.20240327" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.0-dev.20240327.tgz#bd5aa22496947a32eef83f2339b04c51cccaebaf" - integrity sha512-8nAu1p3cwPDGwuJ2XyLonUMdWYtHIyFRi/3qA0FYNmArajdxYW15fh9szcDlLF1nPz/3kM7sbT1q8K7GLTPvxA== - -typescript@~5.5.0-dev.20240327: +typescript@^5.5.0-dev.20240327, typescript@~5.5.0-dev.20240327: version "5.5.0-dev.20240404" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.0-dev.20240404.tgz#4047a68369753dbec926e3fe2db939ca3bc06168" integrity sha512-Knb9Yx0JJHc0mmqXLEPPKNSwOvPQrtYZEDLQY7Wns7LckkQl82AZ+OGTPG/ofwqx2QeDCHCtKjutZPy1UEiwKA==