-
Notifications
You must be signed in to change notification settings - Fork 3
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
}; | ||
}; |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth exporting There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||
|
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, worth exporting |
||
|
||
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; | ||
}, | ||
}); | ||
}; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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