Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web-components): allow custom signer instead of keplr #81

Merged
merged 1 commit into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/web-components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

export { makeAgoricKeplrConnection } from './src/keplr-connection/KeplrConnection.js';
export { makeAgoricWalletConnection } from './src/wallet-connection/walletConnection.js';
export {
agoricRegistryTypes,
agoricConverters,
} from './src/wallet-connection/signerOptions.js';
Comment on lines +6 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, glad to see to see these exported. Should we also update the README to include this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should but I think I'd like to land the react-components stuff first so we can have a more cohesive view/example

export { suggestChain } from './src/wallet-connection/suggestChain.js';
export { Errors as AgoricKeplrConnectionErrors } from './src/errors.js';
export {
Expand Down
1 change: 1 addition & 0 deletions packages/web-components/src/wallet-connection/chainInfo.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
/** @typedef {import('@keplr-wallet/types').Bech32Config} Bech32Config */
/** @typedef {import('@keplr-wallet/types').FeeCurrency} FeeCurrency */

Expand Down
61 changes: 61 additions & 0 deletions packages/web-components/src/wallet-connection/connectKeplr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// @ts-check
import { Registry } from '@cosmjs/proto-signing';
import {
SigningStargateClient,
AminoTypes,
defaultRegistryTypes,
createBankAminoConverters,
createAuthzAminoConverters,
} from '@cosmjs/stargate';
import { Errors } from '../errors.js';
import { agoricConverters, agoricRegistryTypes } from './signerOptions.js';

/** @typedef {import('@keplr-wallet/types').Keplr} Keplr */

/**
*
* @param {string} chainId
* @param {string} rpc
*/
export const connectKeplr = async (chainId, rpc) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add aminoTypes and registry as arguments with default values? accountParser is another thing a developer may want exposed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't exported in the package level, it's just a backward-compatible default in case clientConfig isn't provided. My intention is if someone's using a signer for non-agoric stuff also, they should provide their own signing client with the help of agoricRegistryTypes/agoricConverters. If there's any accountParser stuff specific to agoric that we're missing we should provide that here though, is there?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct nothing specific from agoric wrt to account parser. But if someone is using this signer implementation to connect to other chains, like Agoric/wallet-app#128, they might need one.

More in general, if I'm a library author and wrapping something that takes options, I like to expose those options to the consumer to give more them flexibility and the code a longer shelf life.

if (!('keplr' in window)) {
throw Error(Errors.noKeplr);
}
/** @type {import('@keplr-wallet/types').Keplr} */
// @ts-expect-error cast (checked above)
const keplr = window.keplr;

await null;
try {
await keplr.enable(chainId);
} catch {
throw Error(Errors.enableKeplr);
}

// Until we have SIGN_MODE_TEXTUAL,
// Use Amino because Direct results in ugly protobuf in the keplr UI.
const offlineSigner = await keplr.getOfflineSignerOnlyAmino(chainId);
console.debug('InteractiveSigner', { offlineSigner });

// Currently, Keplr extension manages only one address/public key pair.
const [account] = await offlineSigner.getAccounts();
const { address } = account;

const signingClient = await SigningStargateClient.connectWithSigner(
rpc,
offlineSigner,
{
aminoTypes: new AminoTypes({
...agoricConverters,
...createBankAminoConverters(),
...createAuthzAminoConverters(),
}),
registry: new Registry([...defaultRegistryTypes, ...agoricRegistryTypes]),
},
);

return {
address,
client: signingClient,
};
};
123 changes: 123 additions & 0 deletions packages/web-components/src/wallet-connection/makeAgoricSigner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// @ts-check
import { fromBech32, toBase64 } from '@cosmjs/encoding';
import { assertIsDeliverTxSuccess } from '@cosmjs/stargate';
import { stableCurrency } from './chainInfo.js';
import { AgoricMsgs } from './signerOptions.js';

/** @typedef {import("@cosmjs/proto-signing").EncodeObject} EncodeObject */
/** @typedef {import("@cosmjs/stargate").AminoConverters} AminoConverters */
/** @typedef {import("@cosmjs/stargate").StdFee} StdFee */
/** @typedef {import('@keplr-wallet/types').ChainInfo} ChainInfo */
/** @typedef {import('@keplr-wallet/types').Keplr} Keplr */
/** @typedef {import('@cosmjs/stargate').SigningStargateClient} SigningStargateClient */

/**
* @param {string} address
* @returns {Uint8Array}
*/
const toAccAddress = address => {
return fromBech32(address).data;
};

// XXX domain of @agoric/cosmic-proto
/**
* non-exhaustive list of powerFlags
*
* See also MsgProvision in golang/cosmos/proto/agoric/swingset/msgs.proto
*/
const PowerFlags = {
SMART_WALLET: 'SMART_WALLET',
};

/** @typedef {{owner: string, spendAction: string}} WalletSpendAction */

/**
* @returns {StdFee}
*/
const zeroFee = () => {
const { coinMinimalDenom: denom } = stableCurrency;
const fee = {
amount: [{ amount: '0', denom }],
gas: '300000', // TODO: estimate gas?
};
return fee;
};

/**
* Use a signing client to
* @param {SigningStargateClient} signingClient
* @param {string} address
* Ref: https://docs.keplr.app/api/
*/
export const makeAgoricSigner = (signingClient, address) => {
const fee = zeroFee();

return harden({
/**
* Sign and broadcast Provision for a new smart wallet
*
* @throws if account does not exist on chain, user cancels,
* RPC connection fails, RPC service fails to broadcast (
* for example, if signature verification fails)
*/
provisionSmartWallet: async () => {
const { accountNumber, sequence } = await signingClient.getSequence(
address,
);
console.log({ accountNumber, sequence });

const b64address = toBase64(toAccAddress(address));

const act1 = {
typeUrl: AgoricMsgs.MsgProvision.typeUrl,
value: {
address: b64address,
nickname: 'my wallet',
powerFlags: [PowerFlags.SMART_WALLET],
submitter: b64address,
},
};
Comment on lines +69 to +79
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth exporting makeProvisionSmartWalletMsg(address) along with the types and converters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why someone would need this exported if they can just use the wallet connection object. I could be convinced otherwise, but wanted to keep these changes as minimal as possible for the goal of the wallet connection thing. Ditto for makeWalletSpendMsg

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking here was to have exports for folks in this camp, but don't feel strongly about it. Maybe these folks can use @agoric/cosmic-proto which is being updated to use telescope.

My intention is if someone's using a signer for non-agoric stuff also, they should provide their own signing client with the help of agoricRegistryTypes/agoricConverters.


const msgs = [act1];
console.log('sign provision', { address, msgs, fee });

const tx = await signingClient.signAndBroadcast(address, msgs, fee);
console.log('spend action result tx', tx);
assertIsDeliverTxSuccess(tx);

return tx;
},

/**
* Sign and broadcast WalletSpendAction
*
* @param {string} spendAction marshaled offer
* @throws if account does not exist on chain, user cancels,
* RPC connection fails, RPC service fails to broadcast (
* for example, if signature verification fails)
*/
submitSpendAction: async spendAction => {
const { accountNumber, sequence } = await signingClient.getSequence(
address,
);
console.debug({ accountNumber, sequence });

const act1 = {
typeUrl: AgoricMsgs.MsgWalletSpendAction.typeUrl,
value: {
owner: toBase64(toAccAddress(address)),
spendAction,
},
};
Comment on lines +105 to +111
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, worth exporting makeWalletSpendMsg(address, spendAction)?


const msgs = [act1];
console.debug('sign spend action', { address, msgs, fee });

const tx = await signingClient.signAndBroadcast(address, msgs, fee);
console.debug('spend action result tx', tx);
assertIsDeliverTxSuccess(tx);

return tx;
},
});
};
Loading
Loading