Skip to content

Commit

Permalink
feat: add generate ibc shielding memo to the sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
emccorson committed Oct 16, 2024
1 parent 59c7ce3 commit 95a7e54
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 32 deletions.
35 changes: 27 additions & 8 deletions apps/namadillo/src/App/Ibc/IbcTransfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const IbcTransfer: React.FC = () => {
const [shielded, setShielded] = useState<boolean>(true);
const [selectedAsset, setSelectedAsset] = useState<Asset>();
const [sourceChannelId, setSourceChannelId] = useState<string>("");
const [destinationChannelId, setDestinationChannelId] = useState<string>("");
const [assetsBalances, setAssetsBalances] = useState<
Record<string, AssetWithBalance>
>({});
Expand Down Expand Up @@ -161,7 +162,15 @@ export const IbcTransfer: React.FC = () => {
destinationAddress,
amount,
token: selectedAsset.base,
channelId: sourceChannelId,
sourceChannelId,
...(shielded ?
{
isShielded: true,
destinationChannelId,
}
: {
isShielded: false,
}),
},
});
};
Expand All @@ -172,13 +181,23 @@ export const IbcTransfer: React.FC = () => {
<h2>IBC Transfer to Namada</h2>
</header>

<input
className="text-black"
type="text"
placeholder="source channel id"
value={sourceChannelId}
onChange={(e) => setSourceChannelId(e.target.value)}
/>
<div className="flex flex-col gap-2">
<input
className="text-black"
type="text"
placeholder="source channel id"
value={sourceChannelId}
onChange={(e) => setSourceChannelId(e.target.value)}
/>

<input
className="text-black"
type="text"
placeholder="destination channel id"
value={destinationChannelId}
onChange={(e) => setDestinationChannelId(e.target.value)}
/>
</div>

<TransferModule
source={{
Expand Down
4 changes: 2 additions & 2 deletions apps/namadillo/src/atoms/integrations/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { ExtensionKey } from "@namada/types";
import { queryAndStoreRpc } from "atoms/registry";
import { atomWithMutation } from "jotai-tanstack-query";
import { atomWithStorage } from "jotai/utils";
import { IBCTransferParams, submitIbcTransfer } from "./services";
import { IbcTransferParams, submitIbcTransfer } from "./services";

type IBCTransferAtomParams = {
transferParams: IBCTransferParams;
transferParams: IbcTransferParams;
chain: Chain;
};

Expand Down
85 changes: 69 additions & 16 deletions apps/namadillo/src/atoms/integrations/services.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,61 @@
import { OfflineSigner } from "@cosmjs/launchpad";
import { coin, coins } from "@cosmjs/proto-signing";
import { SigningStargateClient } from "@cosmjs/stargate";
import {
MsgTransferEncodeObject,
SigningStargateClient,
} from "@cosmjs/stargate";
import BigNumber from "bignumber.js";
import { getSdkInstance } from "hooks";

export type IBCTransferParams = {
type CommonParams = {
signer: OfflineSigner;
sourceAddress: string;
destinationAddress: string;
amount: BigNumber;
token: string;
channelId: string;
memo?: string;
sourceChannelId: string;
};

type TransparentParams = CommonParams & { isShielded: false };
type ShieldedParams = CommonParams & {
isShielded: true;
destinationChannelId: string;
};

export type IbcTransferParams = TransparentParams | ShieldedParams;

const getShieldedArgs = async (
target: string,
token: string,
amount: BigNumber,
destinationChannelId: string
): Promise<{ receiver: string; memo: string }> => {
const sdk = await getSdkInstance();

const memo = await sdk.tx.generateIbcShieldingMemo(
target,
token,
amount,
destinationChannelId
);

return {
receiver: sdk.masp.maspAddress(),
memo,
};
};

export const submitIbcTransfer =
(transferParams: IBCTransferParams) =>
(transferParams: IbcTransferParams) =>
async (rpc: string): Promise<void> => {
const {
signer,
sourceAddress,
destinationAddress,
amount,
token,
channelId,
memo,
sourceChannelId,
isShielded,
} = transferParams;

const client = await SigningStargateClient.connectWithSigner(rpc, signer, {
Expand All @@ -36,16 +68,37 @@ export const submitIbcTransfer =
gas: "222000",
};

const response = await client.sendIbcTokens(
const timeoutTimestampNanoseconds =
BigInt(Math.floor(Date.now() / 1000) + 60) * BigInt(1_000_000_000);

const { receiver, memo }: { receiver: string; memo?: string } =
isShielded ?
await getShieldedArgs(
destinationAddress,
token,
amount,
transferParams.destinationChannelId
)
: { receiver: destinationAddress };

const transferMsg: MsgTransferEncodeObject = {
typeUrl: "/ibc.applications.transfer.v1.MsgTransfer",
value: {
sourcePort: "transfer",
sourceChannel: sourceChannelId,
sender: sourceAddress,
receiver,
token: coin(amount.toString(), token),
timeoutHeight: undefined,
timeoutTimestamp: timeoutTimestampNanoseconds,
memo,
},
};

const response = await client.signAndBroadcast(
sourceAddress,
destinationAddress,
coin(amount.toString(), token),
"transfer",
channelId,
undefined, // timeout height
Math.floor(Date.now() / 1000) + 60, // timeout timestamp
fee,
memo
[transferMsg],
fee
);

if (response.code !== 0) {
Expand Down
9 changes: 9 additions & 0 deletions packages/sdk/src/masp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,13 @@ export class Masp {
async addDefaultPaymentAddress(xvk: string, alias: string): Promise<void> {
return await this.sdk.add_default_payment_address(xvk, alias);
}

/**
* Returns the MASP address used as the receiving address in IBC transfers to
* shielded accounts
* @returns the MASP address
*/
maspAddress(): string {
return this.sdk.masp_address();
}
}
25 changes: 25 additions & 0 deletions packages/sdk/src/tx/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
WrapperTxProps,
} from "@namada/types";
import { ResponseSign } from "@zondax/ledger-namada";
import BigNumber from "bignumber.js";
import { WasmHash } from "../rpc";

/**
Expand Down Expand Up @@ -472,6 +473,30 @@ export class Tx {
};
}

/**
* Generate the memo needed for peforming an IBC transfer to a Namada shielded
* address.
* @async
* @param target - the Namada shielded address to send tokens to
* @param token - the token to transfer
* @param amount - the amount to transfer
* @param channelId - the IBC channel ID on the Namada side
* @returns promise that resolves to the shielding memo
*/
generateIbcShieldingMemo(
target: string,
token: string,
amount: BigNumber,
channelId: string
): Promise<string> {
return this.sdk.generate_ibc_shielding_memo(
target,
token,
amount.toString(),
channelId
);
}

/**
* Return the inner tx hashes from the provided tx bytes
* @param bytes - Uint8Array
Expand Down
61 changes: 55 additions & 6 deletions packages/shared/lib/src/sdk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,38 @@ use crate::utils::set_panic_hook;
use crate::utils::to_bytes;
use crate::utils::to_js_result;
use gloo_utils::format::JsValueSerdeExt;
use namada_sdk::address::Address;
use namada_sdk::address::{Address, MASP};
use namada_sdk::borsh::{self, BorshDeserialize};
use namada_sdk::eth_bridge::bridge_pool::build_bridge_pool_tx;
use namada_sdk::hash::Hash;
use namada_sdk::io::NamadaIo;
use namada_sdk::key::{common, ed25519, SigScheme};
use namada_sdk::masp::ShieldedContext;
use namada_sdk::masp::{ShieldedContext};
use namada_sdk::rpc::{query_epoch, InnerTxResult};
use namada_sdk::signing::SigningTxData;
use namada_sdk::string_encoding::Format;
use namada_sdk::tx::{
build_batch, build_bond, build_claim_rewards, build_ibc_transfer, build_redelegation,
build_reveal_pk, build_shielded_transfer, build_shielding_transfer, build_transparent_transfer,
build_unbond, build_unshielding_transfer, build_vote_proposal, build_withdraw,
data::compute_inner_tx_hash, either::Either, process_tx, ProcessTxResponse, Tx,
data::compute_inner_tx_hash, either::Either, process_tx, ProcessTxResponse,
Tx, gen_ibc_shielding_transfer
};
use namada_sdk::wallet::{Store, Wallet};
use namada_sdk::{Namada, NamadaImpl};
use namada_sdk::{Namada, NamadaImpl, PaymentAddress, TransferTarget};
use namada_sdk::args::{InputAmount, GenIbcShieldingTransfer, Query, TxExpiration};
use namada_sdk::ibc::core::host::types::identifiers::{ChannelId, PortId};
use namada_sdk::ibc::convert_masp_tx_to_ibc_memo;
use namada_sdk::token::DenominatedAmount;
use namada_sdk::tendermint_rpc::Url;
use std::str::FromStr;
use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue};

/// Represents the Sdk public API.
#[wasm_bindgen]
pub struct Sdk {
namada: NamadaImpl<HttpClient, wallet::JSWalletUtils, masp::JSShieldedUtils, WebIo>,
rpc_url: String
}

#[wasm_bindgen]
Expand All @@ -47,7 +54,7 @@ impl Sdk {
#[wasm_bindgen(constructor)]
pub fn new(url: String, native_token: String, path_or_db_name: String) -> Self {
set_panic_hook();
let client: HttpClient = HttpClient::new(url);
let client: HttpClient = HttpClient::new(url.clone());
let wallet: Wallet<wallet::JSWalletUtils> = Wallet::new(
wallet::JSWalletUtils::new_utils(&path_or_db_name),
Store::default(),
Expand All @@ -63,7 +70,10 @@ impl Sdk {
Address::from_str(&native_token).unwrap(),
);

Sdk { namada }
Sdk {
namada,
rpc_url: url
}
}

pub async fn has_masp_params() -> Result<JsValue, JsValue> {
Expand Down Expand Up @@ -475,6 +485,45 @@ impl Sdk {
common::SigScheme::verify_signature(&public_key, &signed_hash, &sig).map_err(JsError::from)
}

pub async fn generate_ibc_shielding_memo(
&self,
target: &str,
token: String,
amount: &str,
channel_id: &str
) -> Result<JsValue, JsError> {
let ledger_address = Url::from_str(&self.rpc_url).expect("RPC URL is a valid URL");
let target = TransferTarget::PaymentAddress(
PaymentAddress::from_str(target).expect("target is a valid shielded address")
);
let amount = InputAmount::Unvalidated(
DenominatedAmount::from_str(amount).expect("amount is valid")
);
let channel_id = ChannelId::from_str(channel_id).expect("channel ID is valid");

let args = GenIbcShieldingTransfer {
query: Query { ledger_address },
output_folder: None,
target,
token,
amount,
port_id: PortId::transfer(),
channel_id,
expiration: TxExpiration::Default
};

if let Some(masp_tx) = gen_ibc_shielding_transfer(&self.namada, args).await? {
let memo = convert_masp_tx_to_ibc_memo(&masp_tx);
to_js_result(memo)
} else {
Err(JsError::new("Generating ibc shielding transfer generated nothing"))
}
}

pub fn masp_address(&self) -> String {
MASP.to_string()
}

fn serialize_tx_result(
&self,
tx: Tx,
Expand Down

0 comments on commit 95a7e54

Please sign in to comment.