Skip to content

Commit

Permalink
Deposit from Landline (#1289)
Browse files Browse the repository at this point in the history
* WIP

* make RPC call to landline for deposits

* update landline api interface

* deposit button WIP

* landline deposit button

* fix stored landlineAccount type

* mobile & api: landline feature flag, logo, copy fixes

* mobile: create hook for landline deposit

* generate landline url server-side

* more i18n

* landline offchain action

* small spanish fix

* parse dollarStr to zDollarStr

* make landlineSessionURL optional
  • Loading branch information
andrewliu08 authored Aug 21, 2024
1 parent a1b2be5 commit 0fbcc4c
Show file tree
Hide file tree
Showing 28 changed files with 566 additions and 134 deletions.
Binary file added apps/daimo-mobile/assets/logos/landline-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions apps/daimo-mobile/src/action/useLandlineDeposit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { OffchainAction, now, zDollarStr } from "@daimo/common";
import { daimoChainFromId } from "@daimo/contract";
import * as Haptics from "expo-haptics";
import { useCallback } from "react";
import { stringToBytes } from "viem";

import { signAsync } from "./sign";
import { ActHandle, useActStatus } from "../action/actStatus";
import { i18n } from "../i18n";
import { getRpcFunc } from "../logic/trpc";
import { Account } from "../storage/account";

const i18 = i18n.landlineDepositButton;

interface UseLandlineDepositArgs {
account: Account;
recipient: { landlineAccountUuid: string };
dollarsStr: string;
memo?: string;
}

export function useLandlineDeposit({
account,
recipient,
dollarsStr,
memo,
}: UseLandlineDepositArgs): ActHandle & { exec: () => Promise<void> } {
const [as, setAS] = useActStatus("useLandlineDeposit");

const exec = useCallback(async () => {
console.log(
`[LANDLINE] Creating deposit for ${account.name} to ${recipient.landlineAccountUuid} for $${dollarsStr}`
);
setAS("loading", i18.depositStatus.creating());

// Make the user sign an offchain action to authenticate the deposit
const action: OffchainAction = {
type: "landlineDeposit",
time: now(),
landlineAccountUuid: recipient.landlineAccountUuid,
amount: zDollarStr.parse(dollarsStr),
memo: memo ?? "",
};
const actionJSON = JSON.stringify(action);
const messageBytes = stringToBytes(actionJSON);
const signature = await signAsync({ account, messageBytes });

try {
const rpcFunc = getRpcFunc(daimoChainFromId(account.homeChainId));
console.log("[LANDLINE] Making RPC call to depositFromLandline");
const response = await rpcFunc.depositFromLandline.mutate({
daimoAddress: account.address,
actionJSON,
signature,
});

if (response.status === "success") {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
setAS("success", i18.depositStatus.success());
} else {
console.error("[LANDLINE] Landline deposit error:", response.error);
setAS("error", i18.depositStatus.failed());
}
} catch (error) {
console.error("[LANDLINE] Landline deposit error:", error);
setAS("error", i18.depositStatus.failed());
}
}, [account, recipient, dollarsStr, memo, setAS]);

return { ...as, exec };
}
6 changes: 4 additions & 2 deletions apps/daimo-mobile/src/common/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import { Platform } from "react-native";
import { Hex } from "viem";

import { Dispatcher } from "../action/dispatch";
import { BankTransferOptions } from "../logic/bankTransferOptions";
import {
BridgeBankAccountContact,
LandlineBankAccountContact,
DaimoContact,
EAccountContact,
MsgContact,
Expand Down Expand Up @@ -136,9 +137,10 @@ export interface SendNavProp {
}

export interface LandlineTransferNavProp {
recipient: BridgeBankAccountContact;
recipient: LandlineBankAccountContact;
money?: MoneyEntry;
memo?: string;
bankTransferOption?: BankTransferOptions;
}

export type ParamListTab = {
Expand Down
19 changes: 17 additions & 2 deletions apps/daimo-mobile/src/i18n/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,15 @@ export const en = {
contactDisplay: {
requestedBy: () => `Requested by`,
},
// LandlineDepositButton.tsx
landlineDepositButton: {
holdButton: () => "HOLD TO DEPOSIT",
depositStatus: {
creating: () => "Creating deposit",
success: () => "Deposit successful!",
failed: () => "Deposit failed",
},
},
// ------------ MISC SCREENS ------------
// DepositScreen.tsx
deposit: {
Expand Down Expand Up @@ -532,9 +541,15 @@ export const en = {
},
// LandlineBankTransfer.tsx
landlineBankTransfer: {
title: {
deposit: () => `Deposit from`,
withdraw: () => `Withdraw to`,
},
warning: {
title: () => `Withdrawals are public`,
minimum: () => `Minimum withdrawal of 1 USDC`,
titleDeposit: () => `Deposits are public`,
titleWithdraw: () => `Withdrawals are public`,
minimumDeposit: () => `Minimum deposit of 1 USD`,
minimumWithdraw: () => `Minimum withdrawal of 1 USDC`,
},
},
// ProfileScreen.tsx
Expand Down
19 changes: 17 additions & 2 deletions apps/daimo-mobile/src/i18n/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,15 @@ export const es: LanguageDefinition = {
contactDisplay: {
requestedBy: () => `Solicitado por`,
},
// LandlineDepositButton.tsx
landlineDepositButton: {
holdButton: () => "MANTENGA PARA DEPOSITAR",
depositStatus: {
creating: () => "Creando depósito",
success: () => "Depósito creado",
failed: () => "Depósito fallido",
},
},
// ------------ MISC SCREENS ------------
// DepositScreen.tsx
deposit: {
Expand Down Expand Up @@ -537,9 +546,15 @@ export const es: LanguageDefinition = {
},
// LandlineBankTransfer.tsx
landlineBankTransfer: {
title: {
deposit: () => `Depositar desde`,
withdraw: () => `Retirar a`,
},
warning: {
title: () => `Los retiros son públicos`,
minimum: () => `La cantidad mínima para retirar es 1 USDC`,
titleDeposit: () => `Los depósitos son públicos`,
titleWithdraw: () => `Los retiros son públicos`,
minimumDeposit: () => `La cantidad mínima para depositar es 1 USD`,
minimumWithdraw: () => `La cantidad mínima para retirar es 1 USDC`,
},
},
// ProfileScreen.tsx
Expand Down
4 changes: 4 additions & 0 deletions apps/daimo-mobile/src/logic/bankTransferOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum BankTransferOptions {
Deposit = "Deposit",
Withdraw = "Withdraw",
}
33 changes: 20 additions & 13 deletions apps/daimo-mobile/src/logic/daimoContacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Address } from "viem";
import { getCachedEAccount } from "./addr";
import { useSystemContactsSearch } from "./systemContacts";
import { getRpcHook } from "./trpc";
import IconDepositWallet from "../../assets/icon-deposit-wallet.png";
import { Account } from "../storage/account";

interface BaseDaimoContact {
Expand All @@ -38,11 +39,12 @@ export interface PhoneNumberContact extends BaseDaimoContact {
name?: string;
}

export interface BridgeBankAccountContact extends EAccount, BaseDaimoContact {
type: "bridgeBankAccount";
export interface LandlineBankAccountContact extends EAccount, BaseDaimoContact {
type: "landlineBankAccount";
landlineAccountUuid: string;
bankName: string;
lastFour: string;
bankLogo: string | undefined;
bankLogo: string | null;
accountNumberLastFour: string;
}

// A DaimoContact is a "contact" of the user in the app.
Expand All @@ -52,7 +54,7 @@ export type DaimoContact =
| EAccountContact
| EmailContact
| PhoneNumberContact
| BridgeBankAccountContact;
| LandlineBankAccountContact;

// A MsgContact is a contact that is not a EAccount. (i.e. not an
// on-chain account)
Expand All @@ -69,7 +71,7 @@ export function getDaimoContactKey(contact: DaimoContact): string {
return contact.email;
case "phoneNumber":
return contact.phoneNumber;
case "bridgeBankAccount":
case "landlineBankAccount":
return contact.addr;
}
}
Expand All @@ -93,8 +95,8 @@ export function getContactName(r: DaimoContact) {
if (r.type === "eAcc") return getAccountName(r);
else if (r.type === "email") return r.name ? r.name : r.email;
else if (r.type === "phoneNumber") return r.name ? r.name : r.phoneNumber;
else if (r.type === "bridgeBankAccount")
return `${r.bankName} ****${r.lastFour}`;
else if (r.type === "landlineBankAccount")
return `${r.bankName} ****${r.accountNumberLastFour}`;
else throw new Error(`Unknown recipient type ${r}`);
}

Expand All @@ -103,9 +105,13 @@ export function getContactProfilePicture(
): string | { uri: string } | undefined {
if (r.type === "eAcc") {
return r.profilePicture;
} else if (r.type === "bridgeBankAccount") {
} else if (r.type === "landlineBankAccount") {
const defaultLogo = IconDepositWallet;
// The bank logo is fetched as a base64 string for a png
return { uri: `data:image/png;base64,${r.bankLogo}` };
const logo = r.bankLogo
? { uri: `data:image/png;base64,${r.bankLogo}` }
: defaultLogo;
return logo;
} else {
return undefined;
}
Expand Down Expand Up @@ -216,12 +222,13 @@ export function useContactSearch(

export function landlineAccountToContact(
landlineAccount: LandlineAccount
): BridgeBankAccountContact {
): LandlineBankAccountContact {
return {
type: "bridgeBankAccount",
type: "landlineBankAccount",
landlineAccountUuid: landlineAccount.landlineAccountUuid,
addr: landlineAccount.liquidationAddress,
bankName: landlineAccount.bankName,
lastFour: landlineAccount.lastFour,
accountNumberLastFour: landlineAccount.accountNumberLastFour,
bankLogo: landlineAccount.bankLogo,
};
}
10 changes: 5 additions & 5 deletions apps/daimo-mobile/src/storage/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ export type Account = {
/** Payment links sent, but not yet claimed */
sentPaymentLinks: DaimoLinkNoteV2[];

/** Session key used to authenticate to the Landline onramp/offramp app **/
landlineSessionKey: string;
/** Session URL used to authenticate to the Landline onramp/offramp app **/
landlineSessionURL: string;
/** Bank accounts connected to the Landline onramp/offramp app **/
landlineAccounts: LandlineAccount[];
};
Expand Down Expand Up @@ -191,7 +191,7 @@ export function parseAccount(accountJSON?: string): Account | null {
exchangeRates: a.exchangeRates,
sentPaymentLinks: a.sentPaymentLinks,

landlineSessionKey: a.landlineSessionKey,
landlineSessionURL: a.landlineSessionURL ?? "",
landlineAccounts: a.landlineAccounts,
};
}
Expand Down Expand Up @@ -239,7 +239,7 @@ export function serializeAccount(account: Account | null): string {
exchangeRates: account.exchangeRates,
sentPaymentLinks: account.sentPaymentLinks,

landlineSessionKey: account.landlineSessionKey,
landlineSessionURL: account.landlineSessionURL,
landlineAccounts: account.landlineAccounts,
};

Expand Down Expand Up @@ -302,7 +302,7 @@ export function createEmptyAccount(
exchangeRates: [],
sentPaymentLinks: [],

landlineSessionKey: "",
landlineSessionURL: "",
landlineAccounts: [],
};
}
2 changes: 1 addition & 1 deletion apps/daimo-mobile/src/storage/storedAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ export interface StoredV16Account extends StoredModel {
exchangeRates: StoredV15CurrencyExchangeRate[];
sentPaymentLinks: StoredV15DaimoLinkNoteV2[];

landlineSessionKey: string;
landlineSessionURL?: string;
landlineAccounts: StoredV15LandlineAccount[];
}
18 changes: 9 additions & 9 deletions apps/daimo-mobile/src/storage/storedAccountMigrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ interface StoredV15Account extends StoredModel {
exchangeRates: StoredV15CurrencyExchangeRate[];
sentPaymentLinks: StoredV15DaimoLinkNoteV2[];

landlineSessionKey: string;
landlineSessionURL?: string;
landlineAccounts: StoredV15LandlineAccount[];
}

Expand Down Expand Up @@ -343,7 +343,7 @@ export function migrateOldAccount(model: StoredModel): Account {
exchangeRates: [],
sentPaymentLinks: [],

landlineSessionKey: "",
landlineSessionURL: "",
landlineAccounts: [],
};
} else if (model.storageVersion === 9) {
Expand Down Expand Up @@ -387,7 +387,7 @@ export function migrateOldAccount(model: StoredModel): Account {
exchangeRates: [],
sentPaymentLinks: [],

landlineSessionKey: "",
landlineSessionURL: "",
landlineAccounts: [],
};
} else if (model.storageVersion === 10) {
Expand Down Expand Up @@ -431,7 +431,7 @@ export function migrateOldAccount(model: StoredModel): Account {
exchangeRates: [],
sentPaymentLinks: [],

landlineSessionKey: "",
landlineSessionURL: "",
landlineAccounts: [],
};
} else if (model.storageVersion === 11) {
Expand Down Expand Up @@ -476,7 +476,7 @@ export function migrateOldAccount(model: StoredModel): Account {
exchangeRates: [],
sentPaymentLinks: [],

landlineSessionKey: "",
landlineSessionURL: "",
landlineAccounts: [],
};
} else if (model.storageVersion === 12) {
Expand Down Expand Up @@ -520,7 +520,7 @@ export function migrateOldAccount(model: StoredModel): Account {
exchangeRates: [],
sentPaymentLinks: [],

landlineSessionKey: "",
landlineSessionURL: "",
landlineAccounts: [],
};
} else if (model.storageVersion === 13) {
Expand Down Expand Up @@ -565,7 +565,7 @@ export function migrateOldAccount(model: StoredModel): Account {
exchangeRates: [],
sentPaymentLinks: [],

landlineSessionKey: "",
landlineSessionURL: "",
landlineAccounts: [],
};
} else if (model.storageVersion === 14) {
Expand Down Expand Up @@ -610,7 +610,7 @@ export function migrateOldAccount(model: StoredModel): Account {
exchangeRates: [],
sentPaymentLinks: [],

landlineSessionKey: "",
landlineSessionURL: "",
landlineAccounts: [],
};
} else if (model.storageVersion === 15) {
Expand Down Expand Up @@ -654,7 +654,7 @@ export function migrateOldAccount(model: StoredModel): Account {
exchangeRates: a.exchangeRates || [],
sentPaymentLinks: a.sentPaymentLinks || [],

landlineSessionKey: a.landlineSessionKey || "",
landlineSessionURL: a.landlineSessionURL || "",
landlineAccounts: a.landlineAccounts || [],
};
} else {
Expand Down
Loading

0 comments on commit 0fbcc4c

Please sign in to comment.