diff --git a/README.md b/README.md
index 833bf37..9ab973a 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,8 @@ Targets can be defined in the `package.json` or `projects.json`. Learn more [in
### New Build
+- Update the Version and Build numbers in `app.json`. If you're pushing an update, you'll probably just want to increment the build.
+
```shell
nx prebuild mobile-app -- --platform ios
nx cargo-ios ironfish-native-module -- --target='ios'
@@ -61,7 +63,8 @@ open .
- Double Click "mobileapp"
- Signing & Capabilities tab
- Under signing select "IF Labs" for team (must be added to team in App Store Connect, ask Derek)
-- Bundle identifier should be prepopulated but should read "com.ironfish.mobileapp"
+- Bundle identifier should be prepopulated but should read "network.ironfish.mobilewallet"
+- Double-check Version and Build numbers match those in `app.json`.
- In the scheme bar (top center of editor), select Any iOS Device (arm64)
- Mac menu bar - click Product -> Archive, wait for build (might take a minute or two)
- Click Distribute App button in popup
diff --git a/packages/mobile-app/app.json b/packages/mobile-app/app.json
index 98bd8bf..ab3b0eb 100644
--- a/packages/mobile-app/app.json
+++ b/packages/mobile-app/app.json
@@ -16,6 +16,8 @@
"ios": {
"icon": "./assets/icon.png",
"bundleIdentifier": "network.ironfish.mobilewallet",
+ "appleTeamId": "9WR79D873L",
+ "buildNumber": "2",
"infoPlist": {
"NSAppTransportSecurity": {
"NSAllowsArbitraryLoads": false,
diff --git a/packages/mobile-app/app/(drawer)/account/bridge/index.tsx b/packages/mobile-app/app/(drawer)/account/bridge/index.tsx
index 05c2bd2..eecc6f3 100644
--- a/packages/mobile-app/app/(drawer)/account/bridge/index.tsx
+++ b/packages/mobile-app/app/(drawer)/account/bridge/index.tsx
@@ -3,7 +3,7 @@ import { StyleSheet, View } from "react-native";
import { Button, Card, Layout, Text } from "@ui-kitten/components";
import { oreoWallet } from "@/data/wallet/oreowalletWallet";
import { Network } from "@/data/constants";
-import { useCallback, useRef, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import * as Uint8ArrayUtils from "@/utils/uint8Array";
import { useFacade } from "@/data/facades";
import { Output } from "@/data/facades/wallet/types";
@@ -219,15 +219,13 @@ export default function MenuDebugBrowser() {
},
);
- const handlePresentModalPress = useCallback(() => {
- bottomSheetModalRef.current?.present();
- setAccountModalVisible(true);
- }, []);
-
- const handleDismissModalPress = useCallback(() => {
- bottomSheetModalRef.current?.dismiss();
- setAccountModalVisible(false);
- }, []);
+ useEffect(() => {
+ if (accountModalVisible) {
+ bottomSheetModalRef.current?.present();
+ } else {
+ bottomSheetModalRef.current?.dismiss();
+ }
+ }, [accountModalVisible]);
const renderBackdrop = useCallback(
(props: any) => (
@@ -379,7 +377,10 @@ export default function MenuDebugBrowser() {
enablePanDownToClose
backdropComponent={renderBackdrop}
onDismiss={() => {
- messageHandler.current.updateActiveAccount(null);
+ if (messageHandler.current.connectRequest) {
+ messageHandler.current.connectRequest.resolve(null);
+ messageHandler.current.connectRequest = null;
+ }
setAccountModalVisible(false);
}}
backgroundStyle={styles.bottomSheetModal}
@@ -391,7 +392,7 @@ export default function MenuDebugBrowser() {
style={{ width: 48, height: 48 }}
/>
- Iron Fish Bridge
+ Connect Account
{network}
@@ -412,34 +413,36 @@ export default function MenuDebugBrowser() {
>
+
-
{
messageHandler.current.rejectSendTransactionRequest();
@@ -457,7 +460,7 @@ export default function MenuDebugBrowser() {
onMessage={(event) => {
messageHandler.current.handleMessage(
event.nativeEvent.data,
- handlePresentModalPress,
+ () => setAccountModalVisible(true),
(data) => {
setSendTransactionData(data);
},
diff --git a/packages/mobile-app/app/(drawer)/account/transaction/[hash].tsx b/packages/mobile-app/app/(drawer)/account/transaction/[hash].tsx
index 57a3328..4050c5c 100644
--- a/packages/mobile-app/app/(drawer)/account/transaction/[hash].tsx
+++ b/packages/mobile-app/app/(drawer)/account/transaction/[hash].tsx
@@ -65,6 +65,16 @@ export default function TransactionDetails() {
},
);
+ const explorer = facade.getExplorerUrl.useQuery(
+ {
+ type: "transaction",
+ hash: hash,
+ },
+ {
+ enabled: !!hash,
+ },
+ );
+
// Get assets for all balance deltas
const assetQueries = useQueries({
queries:
@@ -76,7 +86,8 @@ export default function TransactionDetails() {
});
const openInExplorer = () => {
- Linking.openURL(`https://explorer.ironfish.network/transaction/${hash}`);
+ if (!explorer.data) return;
+ Linking.openURL(explorer.data);
};
if (transactionQuery.isLoading || assetQueries.some((q) => q.isLoading)) {
diff --git a/packages/mobile-app/components/browser/SendTransactionModal.tsx b/packages/mobile-app/components/browser/SendTransactionModal.tsx
index b080a3f..41b4ed9 100644
--- a/packages/mobile-app/components/browser/SendTransactionModal.tsx
+++ b/packages/mobile-app/components/browser/SendTransactionModal.tsx
@@ -1,9 +1,16 @@
-import { Network } from "@/data/constants";
+import { IRON_ASSET_ID_HEX, Network } from "@/data/constants";
import { Output } from "@/data/facades/wallet/types";
import { oreoWallet } from "@/data/wallet/oreowalletWallet";
-import { Button, Modal, SafeAreaView, Text, View } from "react-native";
-import { useState, useEffect } from "react";
-import { RawTransaction } from "@ironfish/sdk";
+import { StyleSheet, View } from "react-native";
+import { useState, useEffect, useRef } from "react";
+import { CurrencyUtils, RawTransaction } from "@ironfish/sdk";
+import { BottomSheetModal, BottomSheetScrollView } from "@gorhom/bottom-sheet";
+import { Button, Card, Layout, Spinner, Text } from "@ui-kitten/components";
+import { useFacade } from "@/data/facades";
+import { useQueries } from "@tanstack/react-query";
+import { Image } from "expo-image";
+import { Asset } from "@/data/facades/chain/types";
+import { setStringAsync } from "expo-clipboard";
type Mint = {
value: string;
@@ -31,19 +38,80 @@ type DisplayMessage =
| { type: "success"; hash: string; message: string };
export default function SendTransactionModal({
+ network,
sendTransactionData,
+ renderBackdrop,
cancel,
success,
}: {
+ network: string;
sendTransactionData: GeneralTransactionData | null;
+ renderBackdrop: (props: any) => React.JSX.Element;
cancel: () => void;
success: (hash: string) => void;
}) {
+ const facade = useFacade();
+ const bottomSheetModalRef = useRef(null);
+
const [displayMessage, setDisplayMessage] = useState(
null,
);
const [rawTxn, setRawTxn] = useState(null);
+ const assetIds = [
+ ...new Set(
+ rawTxn?.outputs.map((o) => o.note.assetId().toString("hex")) ?? [],
+ ),
+ ];
+
+ const assetQueries = useQueries({
+ queries:
+ assetIds.map((assetId) => ({
+ queryKey: facade.getAsset.buildQueryKey({ assetId }),
+ queryFn: () => facade.getAsset.resolver({ assetId }),
+ })) ?? [],
+ });
+
+ const assetMap = new Map<
+ string,
+ { name: string; image?: string; asset: Asset }
+ >();
+ for (const a of assetQueries) {
+ if (a.data) {
+ assetMap.set(a.data.id, {
+ name:
+ a.data?.verification.status === "verified"
+ ? a.data.verification.symbol
+ : a.data.name,
+ image:
+ a.data?.verification.status === "verified"
+ ? a.data.verification.logoURI
+ : undefined,
+ asset: a.data,
+ });
+ }
+ }
+
+ const assetBalanceDeltas = new Map();
+ if (rawTxn) {
+ assetBalanceDeltas.set(IRON_ASSET_ID_HEX, rawTxn?.fee);
+ for (const o of rawTxn.outputs) {
+ if (o.note.owner() !== o.note.sender()) {
+ const assetId = o.note.assetId().toString("hex");
+ const currentBalance = assetBalanceDeltas.get(assetId) ?? 0n;
+ assetBalanceDeltas.set(assetId, currentBalance + o.note.value());
+ }
+ }
+ }
+
+ useEffect(() => {
+ if (sendTransactionData) {
+ bottomSheetModalRef.current?.present();
+ } else {
+ bottomSheetModalRef.current?.dismiss();
+ }
+ }, [sendTransactionData]);
+
useEffect(() => {
let cancel = false;
@@ -89,24 +157,68 @@ export default function SendTransactionModal({
const renderBody = () => {
if (displayMessage) {
if (displayMessage.type === "loading") {
- return {displayMessage.message};
+ return (
+
+
+ {displayMessage.message}
+
+
+
+ );
} else if (displayMessage.type === "error") {
return (
- <>
- {displayMessage.message}
-
- >
+
+
+ Error
+ {displayMessage.message}
+
+
+
);
} else if (displayMessage.type === "success") {
return (
- <>
- {displayMessage.message}
- {`Transaction hash: ${displayMessage.hash}`}
-
);
}
+
+const styles = StyleSheet.create({
+ assetBadge: {
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ backgroundColor: "#E1E1E1",
+ elevation: 4,
+ shadowColor: "#000",
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.25,
+ shadowRadius: 4,
+ },
+ bottomSheetModal: {
+ shadowColor: "#000",
+ shadowOffset: {
+ width: 0,
+ height: 6,
+ },
+ shadowOpacity: 0.37,
+ shadowRadius: 7.49,
+
+ elevation: 12,
+ },
+ bottomSheetContent: {
+ padding: 16,
+ gap: 16,
+ },
+ modalSubtitle: {
+ textAlign: "center",
+ },
+});
diff --git a/packages/mobile-app/data/wallet/oreowalletWallet.ts b/packages/mobile-app/data/wallet/oreowalletWallet.ts
index 98fc62a..fe2263b 100644
--- a/packages/mobile-app/data/wallet/oreowalletWallet.ts
+++ b/packages/mobile-app/data/wallet/oreowalletWallet.ts
@@ -393,14 +393,12 @@ export class Wallet {
memo = memo.padEnd(64, "0");
const key = `${output.publicAddress}:${output.amount}:${output.assetId ?? IRON_ASSET_ID_HEX}:${memo}`;
- console.log(`Output key: ${key}`);
map.set(key, (map.get(key) ?? 0) + 1);
}
for (const output of rawTransaction.outputs) {
const key = `${output.note.owner()}:${output.note.value()}:${output.note.assetId().toString("hex")}:${output.note.memo().toString("hex")}`;
- console.log(`rawTxn output key: ${key}`);
const count = map.get(key) ?? 0;