diff --git a/packages/daimo-common/package.json b/packages/daimo-common/package.json index 8bdac40eb..524bdee23 100644 --- a/packages/daimo-common/package.json +++ b/packages/daimo-common/package.json @@ -1,9 +1,11 @@ { "name": "@daimo/common", "version": "0.2.10", - "description": "Shared between web and mobile", + "description": "Daimo shared models and utilities", "main": "dist/index.js", "types": "dist/index.d.ts", + "author": "", + "license": "MIT", "scripts": { "build": "tsc", "test": "tape -r ts-node/register/transpile-only test/**/*.test.ts", @@ -11,8 +13,6 @@ "lint:deps": "npx depcheck --ignores @tsconfig/node20,@types/tape,ts-node", "lint:style": "eslint . --max-warnings=0" }, - "author": "", - "license": "GPL-3.0-or-later", "devDependencies": { "@tsconfig/node20": "^20.1.4", "@types/tape": "^5.6.0", diff --git a/packages/daimo-common/src/chainExplorer.ts b/packages/daimo-common/src/chainExplorer.ts new file mode 100644 index 000000000..496f2141b --- /dev/null +++ b/packages/daimo-common/src/chainExplorer.ts @@ -0,0 +1,51 @@ +import { Address, Hex } from "viem"; + +/** + * Get block explorer URL for chain ID + */ +export function getChainExplorerByChainId(chainId: number): string | undefined { + switch (chainId) { + case 1: + return "https://etherscan.io"; + case 8453: + return "https://basescan.org"; + case 42161: + return "https://arbiscan.io"; + case 10: + return "https://optimistic.etherscan.io"; + case 137: + return "https://polygonscan.com"; + case 43114: + return "https://snowtrace.io"; + case 11155111: + return "https://sepolia.etherscan.io"; + case 84532: + return "https://sepolia.basescan.org"; + case 421614: + return "https://sepolia.arbiscan.io"; + case 11155420: + return "https://sepolia-optimism.etherscan.io"; + case 80002: + return "https://amoy.polygonscan.com"; + case 43113: + return "https://testnet.snowtrace.io"; + default: + return undefined; + } +} + +export function getChainExplorerAddressUrl(chainId: number, address: Address) { + const explorer = getChainExplorerByChainId(chainId); + if (!explorer) { + return undefined; + } + return `${explorer}/address/${address}`; +} + +export function getChainExplorerTxUrl(chainId: number, txHash: Hex) { + const explorer = getChainExplorerByChainId(chainId); + if (!explorer) { + return undefined; + } + return `${explorer}/tx/${txHash}`; +} diff --git a/packages/daimo-common/src/daimoPay.ts b/packages/daimo-common/src/daimoPay.ts new file mode 100644 index 000000000..4f3e08f86 --- /dev/null +++ b/packages/daimo-common/src/daimoPay.ts @@ -0,0 +1,122 @@ +import { ForeignToken } from "@daimo/contract"; +import { base58 } from "@scure/base"; +import { Address, bytesToBigInt, Hex, numberToBytes, zeroAddress } from "viem"; + +import { BigIntStr } from "./model"; + +// lifecycle: waiting payment -> processed. +export enum DaimoPayOrderStatusSource { + WAITING_PAYMENT = "waiting_payment", + PENDING_PROCESSING = "pending_processing", + PROCESSED = "processed", +} + +// lifecycle: pending -> fast-finished (optionally) -> claimed +export enum DaimoPayOrderStatusDest { + PENDING = "pending", + FAST_FINISHED = "fast_finished", + CLAIMED = "claimed", +} + +export enum DaimoPayOrderMode { + SALE = "sale", // product or item sale + CHOOSE_AMOUNT = "choose_amount", // let the user specify the amount to pay + HYDRATED = "hydrated", // once hydrated, the order is final and all parameters are known and immutable +} + +export interface DaimoPayOrderItem { + name: string; + description: string; + image: string; +} + +export type DaimoPayDehydratedOrder = { + mode: DaimoPayOrderMode.SALE | DaimoPayOrderMode.CHOOSE_AMOUNT; + id: bigint; + destFinalCallTokenAmount: DaimoPayTokenAmount; + destFinalCall: OnChainCall; + destNonce: bigint; + intent: string; + itemsJson: string | null; + redirectUri: string | null; +}; + +export type DaimoPayHydratedOrder = { + mode: DaimoPayOrderMode.HYDRATED; + id: bigint; + handoffAddr: Address; + destMintTokenAmount: DaimoPayTokenAmount; + destFinalCallTokenAmount: DaimoPayTokenAmount; + destFinalCall: OnChainCall; + destRefundAddr: Address; + destNonce: bigint; + sourceTokenAmount: DaimoPayTokenAmount | null; + sourceInitiateTxHash: Hex | null; + sourceStartTxHash: Hex | null; + sourceStatus: DaimoPayOrderStatusSource; + destStatus: DaimoPayOrderStatusDest; + destFastFinishTxHash: Hex | null; + destClaimTxHash: Hex | null; + intent: string; + itemsJson: string | null; + redirectUri: string | null; +}; + +export type DaimoPayHydratedOrderWithoutHandoffAddr = Omit< + DaimoPayHydratedOrder, + "handoffAddr" +>; + +export type DaimoPayOrder = DaimoPayDehydratedOrder | DaimoPayHydratedOrder; + +export type ExternalPaymentOptionMetadata = { + id: ExternalPaymentOptions; + cta: string; + logoURI: string; + paymentToken: DaimoPayToken; +}; + +export type ExternalPaymentOptionData = { + url: string; + waitingMessage: string; +}; + +export enum ExternalPaymentOptions { + Daimo = "Daimo", + Coinbase = "Coinbase", + RampNetwork = "RampNetwork", +} + +export interface DaimoPayToken extends ForeignToken { + usd: number; // per unit price in dollars, example 2300 (USD) for WETH + quoteTimestamp: number; + quoteBlockNumber: number; + displayDecimals: number; // TODO, human friendly number of decimals for the token + fiatSymbol?: string; // e.g. $ for USDC/USDT/DAI, € for EUROC, etc +} + +export interface DaimoPayTokenAmount { + token: DaimoPayToken; + amount: BigIntStr; + usd: number; // amount in dollars +} + +export type OnChainCall = { + to: Address; + data: Hex; + value: bigint; +}; + +export const emptyOnChainCall: OnChainCall = { + to: zeroAddress, + data: "0x", + value: 0n, +}; + +export function readEncodedDaimoPayID(id: string): bigint { + return bytesToBigInt(base58.decode(id)); +} + +export function writeEncodedDaimoPayID(id: bigint): string { + return base58.encode(numberToBytes(id)); +} diff --git a/packages/daimo-common/src/index.ts b/packages/daimo-common/src/index.ts index 211637758..4b8899c70 100644 --- a/packages/daimo-common/src/index.ts +++ b/packages/daimo-common/src/index.ts @@ -28,3 +28,5 @@ export * from "./sendPair"; export * from "./time"; export * from "./viemClient"; export * from "./moneyEntry"; +export * from "./daimoPay"; +export * from "./chainExplorer"; diff --git a/packages/daimo-contract/src/codegen/contracts.ts b/packages/daimo-contract/src/codegen/contracts.ts index 1793fb754..0119f987a 100644 --- a/packages/daimo-contract/src/codegen/contracts.ts +++ b/packages/daimo-contract/src/codegen/contracts.ts @@ -283,6 +283,1100 @@ export const aggregatorV3InterfaceAbi = [ }, ] as const +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CrepeBotLP +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const crepeBotLpAbi = [ + { + type: 'constructor', + inputs: [{ name: '_owner', internalType: 'address', type: 'address' }], + stateMutability: 'nonpayable', + }, + { type: 'receive', stateMutability: 'payable' }, + { + type: 'function', + inputs: [], + name: 'acceptOwnership', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { + name: 'mintCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'fc', internalType: 'contract CrepeFastCCTP', type: 'address' }, + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'swapCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + ], + name: 'claimAndKeep', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'fc', internalType: 'contract CrepeFastCCTP', type: 'address' }, + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'swapCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + ], + name: 'fastFinish', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'owner', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'pendingOwner', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { + name: 'requiredTokenIn', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { name: 'addr', internalType: 'contract IERC20', type: 'address' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'suppliedTokenInAmount', + internalType: 'uint256', + type: 'uint256', + }, + { + name: 'requiredTokenOut', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { name: 'addr', internalType: 'contract IERC20', type: 'address' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { name: 'maxTip', internalType: 'uint256', type: 'uint256' }, + { + name: 'innerSwap', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + ], + name: 'swapAndTip', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [{ name: 'newOwner', internalType: 'address', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'previousOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'newOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipTransferStarted', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'previousOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'newOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipTransferred', + }, + { + type: 'error', + inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], + name: 'OwnableInvalidOwner', + }, + { + type: 'error', + inputs: [{ name: 'account', internalType: 'address', type: 'address' }], + name: 'OwnableUnauthorizedAccount', + }, + { + type: 'error', + inputs: [{ name: 'token', internalType: 'address', type: 'address' }], + name: 'SafeERC20FailedOperation', + }, +] as const + +export const crepeBotLpAddress = + '0x88f00747336702466091f7Bf26A9613660B8bB44' as const + +export const crepeBotLpConfig = { + address: crepeBotLpAddress, + abi: crepeBotLpAbi, +} as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CrepeFastCCTP +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const crepeFastCctpAbi = [ + { + type: 'constructor', + inputs: [ + { + name: '_tokenMinter', + internalType: 'contract ITokenMinter', + type: 'address', + }, + { + name: '_cctpMessenger', + internalType: 'contract ICCTPTokenMessenger', + type: 'address', + }, + { + name: '_handoffFactory', + internalType: 'contract CrepeHandoffFactory', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + }, + { type: 'receive', stateMutability: 'payable' }, + { + type: 'function', + inputs: [], + name: 'cctpMessenger', + outputs: [ + { + name: '', + internalType: 'contract ICCTPTokenMessenger', + type: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'swapCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + ], + name: 'claimAction', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'swapCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + ], + name: 'fastFinishAction', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + }, + ], + name: 'getCurrentChainCCTPToken', + outputs: [{ name: '', internalType: 'contract IERC20', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'handoffFactory', + outputs: [ + { + name: '', + internalType: 'contract CrepeHandoffFactory', + type: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'handoffAddr', internalType: 'address', type: 'address' }], + name: 'handoffSent', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'handoffAddr', internalType: 'address', type: 'address' }], + name: 'handoffToRecipient', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { + name: 'approvals', + internalType: 'struct TokenAmount[]', + type: 'tuple[]', + components: [ + { name: 'addr', internalType: 'contract IERC20', type: 'address' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'swapCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + }, + ], + name: 'startAction', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'tokenMinter', + outputs: [ + { name: '', internalType: 'contract ITokenMinter', type: 'address' }, + ], + stateMutability: 'view', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'handoffAddr', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'finalRecipient', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + indexed: false, + }, + ], + name: 'Claim', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'handoffAddr', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'newRecipient', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + indexed: false, + }, + ], + name: 'FastFinish', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'handoffAddr', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + indexed: false, + }, + ], + name: 'Start', + }, + { + type: 'error', + inputs: [{ name: 'token', internalType: 'address', type: 'address' }], + name: 'SafeERC20FailedOperation', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CrepeHandoff +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const crepeHandoffAbi = [ + { type: 'constructor', inputs: [], stateMutability: 'nonpayable' }, + { type: 'receive', stateMutability: 'payable' }, + { + type: 'function', + inputs: [ + { name: '_creator', internalType: 'address payable', type: 'address' }, + { + name: '_destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + }, + ], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'receiveAndSelfDestruct', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { + name: 'cctpMessenger', + internalType: 'contract ICCTPTokenMessenger', + type: 'address', + }, + { + name: 'approvals', + internalType: 'struct TokenAmount[]', + type: 'tuple[]', + components: [ + { name: 'addr', internalType: 'contract IERC20', type: 'address' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'swapCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { + name: 'expectedBurnToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { name: 'addr', internalType: 'contract IERC20', type: 'address' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + ], + name: 'sendAndSelfDestruct', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'version', + internalType: 'uint64', + type: 'uint64', + indexed: false, + }, + ], + name: 'Initialized', + }, + { type: 'error', inputs: [], name: 'InvalidInitialization' }, + { type: 'error', inputs: [], name: 'NotInitializing' }, + { + type: 'error', + inputs: [{ name: 'token', internalType: 'address', type: 'address' }], + name: 'SafeERC20FailedOperation', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CrepeHandoffFactory +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const crepeHandoffFactoryAbi = [ + { type: 'constructor', inputs: [], stateMutability: 'nonpayable' }, + { + type: 'function', + inputs: [ + { name: 'creator', internalType: 'address payable', type: 'address' }, + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + }, + ], + name: 'createHandoff', + outputs: [ + { name: 'ret', internalType: 'contract CrepeHandoff', type: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'creator', internalType: 'address payable', type: 'address' }, + { + name: 'destination', + internalType: 'struct Destination', + type: 'tuple', + components: [ + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'domain', internalType: 'uint32', type: 'uint32' }, + { + name: 'mintToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCallToken', + internalType: 'struct TokenAmount', + type: 'tuple', + components: [ + { + name: 'addr', + internalType: 'contract IERC20', + type: 'address', + }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + }, + { + name: 'finalCall', + internalType: 'struct Call', + type: 'tuple', + components: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + }, + { name: 'refundAddress', internalType: 'address', type: 'address' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + ], + }, + ], + name: 'getHandoffAddress', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'handoffImplementation', + outputs: [ + { name: '', internalType: 'contract CrepeHandoff', type: 'address' }, + ], + stateMutability: 'view', + }, +] as const + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // DaimoAccountFactoryV2 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/daimo-contract/src/foreignToken.ts b/packages/daimo-contract/src/foreignToken.ts index a40c7e768..b2b4b051b 100644 --- a/packages/daimo-contract/src/foreignToken.ts +++ b/packages/daimo-contract/src/foreignToken.ts @@ -26,6 +26,7 @@ export type ForeignToken = { export enum TokenLogo { ETH = "https://assets.coingecko.com/coins/images/279/large/ethereum.png", USDC = "https://assets.coingecko.com/coins/images/6319/large/usdc.png", + EURC = "https://assets.coingecko.com/coins/images/26045/large/euro.png", USDT = "https://assets.coingecko.com/coins/images/325/large/tether.png", DAI = "https://assets.coingecko.com/coins/images/9956/large/dai-multi-collateral-mcd.png", MATIC = "https://assets.coingecko.com/coins/images/4713/large/polygon.png", @@ -84,7 +85,16 @@ export const ethereumUSDC: ForeignToken = { logoURI: TokenLogo.USDC, }; -const ethereumTokens = [ethereumETH, ethereumWETH, ethereumUSDC]; +export const ethereumEURC: ForeignToken = { + chainId: 1, + token: getAddress("0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c"), + decimals: 6, + name: "EURC", + symbol: "EURC", + logoURI: TokenLogo.EURC, +}; + +const ethereumTokens = [ethereumETH, ethereumWETH, ethereumUSDC, ethereumEURC]; // // Base Sepolia @@ -132,6 +142,15 @@ export const baseUSDC: ForeignToken = { logoURI: TokenLogo.USDC, }; +export const baseEURC: ForeignToken = { + chainId: 8453, + token: getAddress("0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"), + decimals: 6, + name: "EURC", + symbol: "EURC", + logoURI: TokenLogo.EURC, +}; + export const baseUSDbC: ForeignToken = { chainId: 8453, token: getAddress("0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA"), @@ -156,7 +175,15 @@ export const baseUSDT: ForeignToken = { logoURI: TokenLogo.USDT, }; -const baseTokens = [baseETH, baseWETH, baseUSDC, baseUSDbC, baseDAI, baseUSDT]; +const baseTokens = [ + baseETH, + baseWETH, + baseUSDC, + baseEURC, + baseUSDbC, + baseDAI, + baseUSDT, +]; // // Arbitrum Mainnet diff --git a/packages/daimo-contract/src/index.ts b/packages/daimo-contract/src/index.ts index 73136c9a4..6e87da453 100644 --- a/packages/daimo-contract/src/index.ts +++ b/packages/daimo-contract/src/index.ts @@ -31,6 +31,12 @@ export { entryPointAbi, erc20Abi, swapbotLpAbi, + crepeBotLpAbi, + crepeBotLpAddress, + crepeBotLpConfig, + crepeFastCctpAbi, + crepeHandoffAbi, + crepeHandoffFactoryAbi, } from "./codegen/contracts"; export const nameRegistryProxyConfig = { @@ -56,6 +62,13 @@ export const daimoFastCctpAddrs: Address[] = [ daimoFastCctpV1Address, ]; +/** Daimo Pay FastCCTP address */ +export const crepeFastCctpAddress = + "0x1C19B74cF09BBbb80AdE88F33F053bd09872Ac6F"; +/** Daimo Pay factory address */ +export const crepeHandoffFactoryAddress = + "0x8B7bB875169B6fd583A7AD36f5025Af970818E02"; + export const daimoFlexSwapperAddress = "0x52A7Fb58f1F26fd57B4a3aAE55d6c51a38A73610"; export const daimoCctpBridgerAddress = diff --git a/packages/daimo-contract/wagmi.config.ts b/packages/daimo-contract/wagmi.config.ts index e083929fb..bf938d311 100644 --- a/packages/daimo-contract/wagmi.config.ts +++ b/packages/daimo-contract/wagmi.config.ts @@ -3,6 +3,9 @@ import { foundry } from "@wagmi/cli/plugins"; import latestAccountFactoryV2 from "../contract/broadcast/DeployAccountFactoryV2.s.sol/8453/run-latest.json"; import latestCCTPBridger from "../contract/broadcast/DeployCCTPBridger.s.sol/8453/run-latest.json"; +import latestCrepeFastCCTP from "../contract/broadcast/DeployCrepeFastCCTP.s.sol/8453/run-latest.json"; +import latestCrepeHandoffFactory from "../contract/broadcast/DeployCrepeHandoffFactory.s.sol/8453/run-latest.json"; +import latestCrepeLPBot from "../contract/broadcast/DeployCrepeLPBot.s.sol/8453/run-latest.json"; import latestEphemeralNotes from "../contract/broadcast/DeployEphemeralNotes.s.sol/8453/run-latest.json"; import latestEphemeralNotesV2 from "../contract/broadcast/DeployEphemeralNotesV2.s.sol/8453/run-latest.json"; import latestFastCCTP from "../contract/broadcast/DeployFastCCTP.s.sol/8453/run-latest.json"; @@ -26,6 +29,9 @@ const deployments = Object.fromEntries( ...latestFlexSwapper.transactions, ...latestCCTPBridger.transactions, ...latestFastCCTP.transactions, + ...latestCrepeFastCCTP.transactions, + ...latestCrepeHandoffFactory.transactions, + ...latestCrepeLPBot.transactions, ] .filter((t) => t.transactionType === "CREATE2") .map((r) => [r.contractName, r.contractAddress as `0x${string}`]) @@ -40,6 +46,7 @@ export default defineConfig({ forge: { build: false }, include: [ "Daimo*.sol/*", + "Crepe*.sol/*", "ERC*.sol/*", "EntryPoint.sol/*", "AggregatorV2V3Interface.sol/*",