Skip to content

Commit

Permalink
feat: add transaction insight for FilForwarder transfers (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
agostbiro committed Aug 1, 2023
1 parent 6fb5e4d commit 863376c
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 19 deletions.
2 changes: 1 addition & 1 deletion packages/adapter/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { FilsnapAdapter } from './snap'
export { filForwarderMetadata } from './filforwarder-metadata'

export { filForwarderMetadata } from 'filsnap'
export type { SnapConfig, AccountInfo } from 'filsnap'
3 changes: 2 additions & 1 deletion packages/snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@
"@metamask/key-tree": "^9.0.0",
"@metamask/snaps-ui": "^0.32.2",
"iso-base": "^1.1.2",
"iso-filecoin": "^2.0.2",
"iso-filecoin": "^2.1.0",
"merge-options": "^3.0.4",
"viem": "^1.4.2",
"zod": "^3.21.4"
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/filecoin-project/filsnap.git"
},
"source": {
"shasum": "PrxFxKiusGgxcttw9R6UenDIU4vxcM2CFCfrX70fMWM=",
"shasum": "OfcugyA6dShGuJZbZAmwbtMOnhKbAuYrPnvX+Al5xf4=",
"location": {
"npm": {
"filePath": "dist/snap.js",
Expand All @@ -23,6 +23,7 @@
"dapps": true,
"snaps": true
},
"endowment:transaction-insight": {},
"snap_dialog": {},
"snap_getBip44Entropy": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ const contractAddress: `0x${string}` =
'0x2B3ef6906429b580b7b2080de5CA893BC282c225'

const chainIds = {
filecoinMainnet: 314,
filecoinCalibrationTestnet: 314_159,
filecoinMainnet: 'eip155:13a',
filecoinCalibrationTestnet: 'eip155:4cb2f',
}

// FEVM FilForwarder contract metadata
Expand All @@ -92,6 +92,6 @@ export const filForwarderMetadata = {
abi,
// The contract address is the same on all chains where the contract is deployed
contractAddress,
// The Ethereum chain ids where the contract is deployed
// The CAIP-2 chain ids where the contract is deployed
chainIds,
}
4 changes: 4 additions & 0 deletions packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export type {
AccountInfo,
} from './types'

export { filForwarderMetadata } from './filforwarder-metadata'

export { onTransaction } from './transaction-insight'

export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
try {
const config = await configFromSnap(snap)
Expand Down
121 changes: 121 additions & 0 deletions packages/snap/src/transaction-insight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type {
OnTransactionHandler,
OnTransactionResponse,
} from '@metamask/snaps-types'
import { heading, panel, text } from '@metamask/snaps-ui'
import { fromHex, type Hex } from 'viem'
import * as Address from 'iso-filecoin/address'
import { Token } from 'iso-filecoin/token'
import { filForwarderMetadata } from './filforwarder-metadata'
import { decodeFunctionData } from 'viem'

/**
*
* @param message
*/
function invalidTransferMessage(message: string): OnTransactionResponse {
return {
content: panel([heading('Invalid FIL Transfer'), text(message)]),
}
}

/**
*
* @param chainId
*/
function humanReadableNetwork(chainId: string): string {
if (chainId === filForwarderMetadata.chainIds.filecoinMainnet) {
return 'Filecoin Mainnet'
} else if (
chainId === filForwarderMetadata.chainIds.filecoinCalibrationTestnet
) {
return 'Filecoin Calibration Testnet'
} else {
throw new Error(`Unknown chain ID: ${chainId}`)
}
}

/**
*
* @param chainId
*/
function chainMatches(chainId: string): boolean {
return Object.values(filForwarderMetadata.chainIds).includes(chainId)
}

/**
*
* @param transactionTo
*/
function contractAddressMatches(transactionTo: string | undefined): boolean {
return (
transactionTo?.toLowerCase() ===
filForwarderMetadata.contractAddress.toLowerCase()
)
}

// Note: currently MetaMask Flask shows the transaction insight tab by default even if we don't display any information
// in it. This is a bug that the MetaMask team is addressing in this PR:
// https://github.com/MetaMask/metamask-extension/pull/20267
export const onTransaction: OnTransactionHandler = async ({
transaction,
chainId,
}) => {
if (
!chainMatches(chainId) ||
!contractAddressMatches(transaction.to as string | undefined)
) {
// Don't show any insights if the transaction is not a FIL transfer.
return {
content: null,
}
}

let transferAmount
try {
transferAmount = new Token(fromHex(transaction.value as Hex, 'bigint'))
} catch (error) {
// eslint-disable-next-line no-console
console.error(error)
return invalidTransferMessage(
`Transfer amount is missing from the transaction.`
)
}

let recipient
try {
const callData = decodeFunctionData({
abi: filForwarderMetadata.abi,
data: transaction.data as Hex,
})
if (callData.functionName !== 'forward') {
return invalidTransferMessage(`Transaction tries to call wrong method.`)
}
if (callData.args === undefined || callData.args.length !== 1) {
return invalidTransferMessage(`Missing recipient in transaction.`)
}

const isMainNet = chainId === filForwarderMetadata.chainIds.filecoinMainnet
recipient = Address.fromContractDestination(
callData.args[0] as Hex,
isMainNet ? 'mainnet' : 'testnet'
)
} catch (error) {
// eslint-disable-next-line no-console
console.error(error)
return invalidTransferMessage(`Transaction recipient is invalid.`)
}

return {
content: panel([
heading('Transfer FIL'),
text(
`You are transferring ${transferAmount
.toFIL()
.toString()} FIL to ${recipient.toString()} on ${humanReadableNetwork(
chainId
)}`
),
]),
}
}
43 changes: 30 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 863376c

Please sign in to comment.