-
Notifications
You must be signed in to change notification settings - Fork 7
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: ibc transfers #63
Conversation
@turadg @michaelfig @dckc Please have a read of the PR description and let me know your thoughts. I'm also willing to discuss in real-time if helpful. |
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 to see this taking shape!
I haven't managed to really try it out.
I think the assertIsDeliverTxSuccess
call is critical.
The rest are just suggestions to think about.
import { SigningStargateClient } from '@cosmjs/stargate'; | ||
import { CircularProgress, Link, Snackbar, Typography } from '@mui/material'; | ||
|
||
export enum IbcDirection { |
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.
I'd rather we stuck to the erase-to-js subset of ts and avoid enum
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.
That does go for agoric-sdk because we want to avoid a build step. Browser apps will always have a build step so for these repos we (frontend developers) decided to allow enum.
@@ -0,0 +1,3 @@ | |||
export const wellKnownPetnames = { | |||
IbcATOM: 'ATOM', |
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.
I expect the vbank name in production to be ATOM.
We can/should change that in our main configs.
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.
Just changed everything to reference 'ATOM' and do the 'IbcATOM' -> 'ATOM' conversion in the beginning when the purse is loaded.
denom: | ||
'ibc/BA313C4A19DFBF943586C0387E6B11286F9E416B4DD27574E6909CABE0E342FA', |
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.
the denom should be available via agoricNames.vbankAssets
. I suppose what's deployed doesn't line up, so I can see why you would hard-code 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.
Yea, maybe we can remove this in favor of that later, but for now this is needed for testing
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.
please an XXX
comment to that effect
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.
Done
chainInfo: { | ||
chainName: 'Cosmos Hub', | ||
chainId: 'cosmoshub-4', | ||
rpc: 'https://cosmoshub-rpc.stakely.io/', |
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.
I wonder about fail-over. I suppose stakely's service does some of that internally, but it's still a single point of failure.
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.
Yes good point, it would be good to use an RPC we have some sort of relationship with, ideally an SLA.
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.
agree but not a blocker. please file a ticket for future work (e.g.: fall back if Stakely LB is down)
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 need autodiscovery of rpc nodes, and client qualification and selection from the chain registry. Any other approach is not ideal. If we're going to hardcode something, perhaps polkachu's node
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.
not entirely sure I understand autodiscovery, but since we need it...
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.
Seems like polkachu is locked down:
Access to fetch at 'https://cosmos-rpc.polkachu.com/' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
Do you have any pointers to how autodiscovery of rpc nodes could work?
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.
Since this has it's own ticket I'm not considering it to be a blocker for "feat: ibc transfers"
wallet/src/util/Icons.ts
Outdated
@@ -2,6 +2,7 @@ export const icons = { | |||
IST: 'tokens/IST.png', | |||
BLD: 'tokens/BLD.svg', | |||
ATOM: 'tokens/cosmos.svg', | |||
IbcATOM: 'tokens/cosmos.svg', |
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.
might as well kludge whatever code reads from agoricNames.vbankAssets to you can avoid this sort of thing
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.
Done
close(); | ||
showSnackbar( | ||
<> | ||
Successfully executed transaction{' '} |
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.
Looking at sendIbcTokens, it doesn't seem to do assertIsDeliverTxSuccess(tx)
so I think we need to do that before declaring success.
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.
Done
setInProgress(false); | ||
} | ||
} else { | ||
/* Is Withdrawal */ |
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.
comments like this suggest breaking out a named function
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.
Done, but kept the comment
try { | ||
const { prefix } = fromBech32(remoteChainAddress); | ||
return prefix !== ibcAsset?.chainInfo.addressPrefix; | ||
} catch (e) { |
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.
lint rules allow unused vars without a special name like _e
?
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.
Apparently, just fixed this instance though
try { | ||
const res = await signer.sendIbcTokens( | ||
remoteChainAddress, | ||
keplrConnection.address, |
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.
Are we reacting to the user changing to a different account?
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.
This always uses the agoric address that the wallet UI config is currently using. If the user switches keplr accounts after initially loading the wallet UI, I don't know if we handle that well currently, they'd probably have to refresh the page. Otherwise any transactions would just fail to sign.
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.
they'd probably have to refresh the page
IOU an issue.
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.
Successfully executed transaction{' '} | ||
<Link | ||
color="rgb(0, 176, 255)" | ||
href={`https://bigdipper.live/agoric/transactions/${res.transactionHash}`} |
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.
The url seems a little buried; hoist it up near const agoricRpc = ...
?
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.
Moved this url into a single util function so it's not repeated. It's a bit tricky to use a const because of the interpolation.
+1 to this choice and rationale. Long lived PRs tend to rot. |
feature flag works for me. |
}; | ||
|
||
const Purse = purse => { | ||
const shouldShowIbcTransferButtons = | ||
(keplrConnection?.chainId === agoricChainId || previewEnabled) && |
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 can type setPreviewEnabled(true)
in the console to enable ibc transfer, otherwise it only shows for mainnet.
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.
consider putting that comment into the code. for non-mainnet, what are we previewing? Is there a ticket to support IBC transfer on other nets?
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.
I guess you could say we're previewing a dangerous misleading version of the component that only makes transactions on mainnet. Just made the comment to that effect.
The closest we have to a ticket is probably Agoric/agoric-sdk#7104. But that doesn't seem to capture everything involved with spinning up two chains, making sure the UI has all the references it needs, etc.
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're previewing a dangerous misleading version of the component that only makes transactions on mainnet
Why would we "preview" that? I think we'd need to "preview" some end user feature that will be production-ready eventually and right now isn't. I haven't seen a ticket for the functionality that will be in production at some point so this sounds more like a "development" or "testing" mode flag.
Will leave this to your judgement.
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.
Yea that is a good point. I just don't know if it's worth making a new flag for this case, maybe just rename it to debug or something...
I'm able to test this locally because the address for an agoric local chain and mainnet is the same for a given keplr account. So, in real life (prod), I sent tokens to an agoric address. Then, with that keplr account, I created a smart wallet on my local agoric network so that it has the same address. The only caveat is that, in the component, the agoric balance reflects what's on my local chain, not prod. But, the cosmos hub balance reflects cosmos mainnet, and signing the transactions works (but signs on mainnet not local net). |
import Petname from './Petname'; | ||
import type { PurseInfo } from '../service/Offers'; | ||
import type { KeplrUtils } from '../contexts/Provider'; | ||
import type { Petname as PetnameType } from '@agoric/smart-wallet/src/types'; |
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.
since Petname
is type that spans repos, consider letting it win this naming collision. Petname.tsx
could be PetnameSpan
.
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.
Done
ibcAsset.chainInfo.chainId, | ||
); | ||
const accounts = await offlineSigner.getAccounts(); | ||
setRemoteChainAddress(accounts[0].address); |
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.
why is it safe to pick the first? does this code assume the array has only one element? if so best to assert.
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.
Yea keplr only returns one address. I think an assert might be too harsh but added a warning so we can be aware if some behavior changes, it shouldn't break anything though.
setRemoteChainSigner(offlineSigner); | ||
}; | ||
|
||
const isRemoteChainAddressInvalid = useMemo(() => { |
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.
it's more common to check for validity. consider flipping 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.
Done
explorerPath: string, | ||
transactionHash: string, | ||
) => { | ||
setSnackbarMessage( |
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.
no need to put this into state. isSnackbarOpen
is a signal for whether to render.
setSnackbarMessage( | |
snackbarMessage = <> |
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.
I'm sorry I don't follow. Are you saying we can use an empty message (maybe null
) as a signal rather than tracking isSnackbarOpen
separately?
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.
No, the opposite. Since we have the Boolean state for whether to present a message we don't also need the message itself to be in state. Just make it a let
binding in scope.
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.
Ah I see, because we only change the content when we change isSnackbarOpen
, so it will re-render from that. It just seems a bit hazardous to me in case we ever want the content to change while open, simpler to just keep state in useState
.
Also, using null
content to hide the snackbar I realized will make the hide animation look choppy.
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.
content to change while open
Yeah if that's a requirement then the content should be in state.
using null content to hide the snackbar I realized will make the hide animation look choppy
Oh, I didn't realize that. Why?
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.
Since when you set isSnackbarOpen
to false it doesn't disappear immediately, it fades out over a short duration. So, while it's fading, the content will disappear, making the layout shift.
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.
ah thanks for explaining. the current code makes sense. maybe worth some comments though with this context.
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.
Just put all the snackbar stuff in a hook, also added a close button instead of relying on clickaway. Also I combined the state into one useState
so that we only call setState
once, since it's from an async event this was causing react to render twice unnecessarily.
} else { | ||
// Is withdrawal. |
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.
shouldn't need a comment. consider else if (direction === IbcDirection.Withdrawal)
and an else
for unknown values. alternately switch(direction)
and call helper functions
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.
Added an else if
<CircularProgress size={36} sx={{ p: 1, mx: 1 }} /> | ||
) : ( | ||
<> | ||
{/* @ts-expect-error 'cancel' is part of our theme */} |
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.
time to put it into the types?
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.
I'm not sure, we'd have to wrap the component and override the MUI types somewhere down the inheritance tree. It's a big pain that MUI doesn't allow custom color names.
}; | ||
|
||
const Purse = purse => { | ||
const shouldShowIbcTransferButtons = | ||
(keplrConnection?.chainId === agoricChainId || previewEnabled) && |
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.
consider putting that comment into the code. for non-mainnet, what are we previewing? Is there a ticket to support IBC transfer on other nets?
chainInfo: { | ||
chainName: 'Cosmos Hub', | ||
chainId: 'cosmoshub-4', | ||
rpc: 'https://cosmoshub-rpc.stakely.io/', |
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.
agree but not a blocker. please file a ticket for future work (e.g.: fall back if Stakely LB is down)
denom: | ||
'ibc/BA313C4A19DFBF943586C0387E6B11286F9E416B4DD27574E6909CABE0E342FA', |
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.
please an XXX
comment to that effect
|
||
close(); | ||
try { | ||
assertIsDeliverTxSuccess(res); |
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.
may be more straightforward to use...
export declare function isDeliverTxSuccess(result: DeliverTxResponse): boolean;
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.
Done, thanks!
|
||
interface Props { | ||
purses: PurseInfo[] | null; | ||
previewEnabled: boolean; |
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.
surprised to see this passed along since it's a global state.
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.
Passing it as a prop from context allows components to reactively update when you type setPreviewEnabled(true)
in the console.
}; | ||
|
||
const Purse = purse => { | ||
const shouldShowIbcTransferButtons = | ||
(keplrConnection?.chainId === agoricChainId || previewEnabled) && |
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're previewing a dangerous misleading version of the component that only makes transactions on mainnet
Why would we "preview" that? I think we'd need to "preview" some end user feature that will be production-ready eventually and right now isn't. I haven't seen a ticket for the functionality that will be in production at some point so this sounds more like a "development" or "testing" mode flag.
Will leave this to your judgement.
chainInfo: { | ||
chainName: 'Cosmos Hub', | ||
chainId: 'cosmoshub-4', | ||
rpc: 'https://cosmoshub-rpc.stakely.io/', |
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.
Since this has it's own ticket I'm not considering it to be a blocker for "feat: ibc transfers"
Fixes: #50
WARNING
This component always attempts to make transactions on mainnet, regardless of if the UI config is set to a local/test network.
Testing
Connected to local chain with same keplr account that has tokens on mainnet. Typed
setPreviewEnabled(true)
in the console. Sent real tokens over IBC and saw that my balances were updated in Keplr.TODO (Done)
Since this isn't testable outside of production, we can do one of the following:
Keep this as a draft until completion of allowagoric run add-collateral-core.js
to work without a solo agoric-sdk#7104 (which at this time is not estimated). We'll then likely have to change the implementation to not rely on wallet/src/util/ibc-assets.ts so arbitrary networks and denoms can be tested.I'm leaning towards choice 1 because it's much less work (we could call this basically done from here) and we'll have to verify that it works in mainnet anyway.