Skip to content

Commit

Permalink
Add modern memo handling to all plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
swansontec committed Sep 12, 2023
1 parent b2996ee commit a7d8561
Show file tree
Hide file tree
Showing 52 changed files with 398 additions and 110 deletions.
20 changes: 10 additions & 10 deletions src/algorand/AlgorandEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { base16, base64 } from 'rfc4648'

import { CurrencyEngine } from '../common/CurrencyEngine'
import { PluginEnvironment } from '../common/innerPlugin'
import { upgradeMemos } from '../common/upgradeMemos'
import { utf8 } from '../common/utf8'
import {
asyncWaterfall,
cleanTxLogs,
Expand Down Expand Up @@ -461,6 +463,7 @@ export class AlgorandEngine extends CurrencyEngine<
}

async getMaxSpendable(spendInfo: EdgeSpendInfo): Promise<string> {
spendInfo = upgradeMemos(spendInfo, this.currencyInfo)
let balance = this.getBalance({
currencyCode: spendInfo.currencyCode
})
Expand Down Expand Up @@ -509,8 +512,10 @@ export class AlgorandEngine extends CurrencyEngine<
}

async makeSpend(edgeSpendInfoIn: EdgeSpendInfo): Promise<EdgeTransaction> {
edgeSpendInfoIn = upgradeMemos(edgeSpendInfoIn, this.currencyInfo)
const { edgeSpendInfo, currencyCode, nativeBalance } =
this.makeSpendCheck(edgeSpendInfoIn)
const { memos = [] } = edgeSpendInfo

const spendableAlgoBalance = sub(
this.getBalance({
Expand All @@ -523,11 +528,8 @@ export class AlgorandEngine extends CurrencyEngine<
throw new Error('Error: only one output allowed')
}

const {
nativeAmount: amount,
memo,
publicAddress
} = edgeSpendInfo.spendTargets[0]
const { nativeAmount: amount, publicAddress } =
edgeSpendInfo.spendTargets[0]

if (publicAddress == null)
throw new Error('makeSpend Missing publicAddress')
Expand All @@ -539,10 +541,8 @@ export class AlgorandEngine extends CurrencyEngine<
type: currencyCode === this.currencyInfo.currencyCode ? 'pay' : 'axfer'
}

let note: Uint8Array | undefined
if (memo != null) {
note = Uint8Array.from(Buffer.from(memo, 'ascii'))
}
const note =
memos[0]?.type === 'text' ? utf8.parse(memos[0].value) : undefined

const { customNetworkFee } = edgeSpendInfo
const customFee = asMaybeCustomFee(customNetworkFee).fee
Expand Down Expand Up @@ -628,7 +628,7 @@ export class AlgorandEngine extends CurrencyEngine<
currencyCode,
date: 0,
isSend: true,
memos: [],
memos,
nativeAmount,
networkFee,
otherParams,
Expand Down
3 changes: 3 additions & 0 deletions src/algorand/algorandInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const currencyInfo: EdgeCurrencyInfo = {
}
],

// https://developer.algorand.org/docs/get-details/transactions/transactions/
memoOptions: [{ type: 'text', memoName: 'note', maxLength: 1000 }],

// Deprecated:
defaultSettings: { customFeeSettings: ['fee'] },
memoType: 'text',
Expand Down
3 changes: 3 additions & 0 deletions src/algorand/algorandTestnetInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const currencyInfo: EdgeCurrencyInfo = {
}
],

// https://developer.algorand.org/docs/get-details/transactions/transactions/
memoOptions: [{ type: 'text', memoName: 'note', maxLength: 1000 }],

// Deprecated:
defaultSettings: { customFeeSettings: ['fee'] },
memoType: 'text',
Expand Down
10 changes: 5 additions & 5 deletions src/binance/BinanceEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { CurrencyEngine } from '../common/CurrencyEngine'
import { PluginEnvironment } from '../common/innerPlugin'
import { asErrorMessage } from '../common/types'
import { upgradeMemos } from '../common/upgradeMemos'
import {
asyncWaterfall,
cleanTxLogs,
Expand Down Expand Up @@ -383,7 +384,9 @@ export class BinanceEngine extends CurrencyEngine<
}

async makeSpend(edgeSpendInfoIn: EdgeSpendInfo): Promise<EdgeTransaction> {
edgeSpendInfoIn = upgradeMemos(edgeSpendInfoIn, this.currencyInfo)
const { edgeSpendInfo, currencyCode } = this.makeSpendCheck(edgeSpendInfoIn)
const { memos = [] } = edgeSpendInfo

const spendTarget = edgeSpendInfo.spendTargets[0]
const { publicAddress } = spendTarget
Expand All @@ -408,10 +411,7 @@ export class BinanceEngine extends CurrencyEngine<
throw new Error('Binance Beacon Chain token transfers not supported')
}

if (edgeSpendInfo.spendTargets[0].otherParams?.uniqueIdentifier != null) {
otherParams.memo =
edgeSpendInfo.spendTargets[0].otherParams.uniqueIdentifier
}
otherParams.memo = memos[0]?.type === 'text' ? memos[0].value : undefined

const nativeNetworkFee = NETWORK_FEE_NATIVE_AMOUNT
const ErrorInsufficientFundsMoreBnb = new Error(
Expand All @@ -437,7 +437,7 @@ export class BinanceEngine extends CurrencyEngine<
currencyCode, // currencyCode
date: 0, // date
isSend: nativeAmount.startsWith('-'),
memos: [],
memos,
nativeAmount, // nativeAmount
networkFee: nativeNetworkFee, // networkFee, supposedly fixed
otherParams, // otherParams
Expand Down
4 changes: 4 additions & 0 deletions src/binance/binanceInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ const currencyInfo: EdgeCurrencyInfo = {
}
],

// https://github.com/bnb-chain/javascript-sdk/blob/master/docs/api-docs/classes/bncclient.md#transfer
memoOptions: [{ type: 'text', memoName: 'memo', maxLength: 128 }],

// Deprecated:
defaultSettings: {},
memoMaxLength: 128,
memoType: 'text',
metaTokens: []
}

Expand Down
10 changes: 10 additions & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
asArray,
asCodec,
asEither,
asMaybe,
asNumber,
Expand All @@ -11,6 +12,7 @@ import {
Cleaner
} from 'cleaners'
import { EdgeToken, EdgeTokenInfo, EdgeTransaction } from 'edge-core-js/types'
import { base16 } from 'rfc4648'

export const DATA_STORE_FILE = 'txEngineFolder/walletLocalData.json'
export const TXID_MAP_FILE = 'txEngineFolder/txidMap.json'
Expand Down Expand Up @@ -75,6 +77,14 @@ export const asSafeCommonWalletInfo = asWalletInfo(
asObject({ publicKey: asString })
)

/**
* A string of hex-encoded binary data.
*/
export const asBase16: Cleaner<Uint8Array> = asCodec(
raw => base16.parse(asString(raw)),
clean => base16.stringify(clean).toLowerCase()
)

export function asIntegerString(raw: unknown): string {
const clean = asString(raw)
if (!/^\d+$/.test(clean)) {
Expand Down
47 changes: 47 additions & 0 deletions src/common/upgradeMemos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { EdgeCurrencyInfo, EdgeMemo, EdgeSpendInfo } from 'edge-core-js/types'

import { validateMemos } from './validateMemos'

/**
* Upgrades the memo fields inside an EdgeSpendTarget,
* since we need to be runtime-compatible with legacy core versions.
*/
export function upgradeMemos(
spendInfo: EdgeSpendInfo,
currencyInfo: EdgeCurrencyInfo
): EdgeSpendInfo {
const { memoType } = currencyInfo

const legacyMemos: EdgeMemo[] = []

// If this chain supports legacy memos, grab those:
if (memoType === 'hex' || memoType === 'number' || memoType === 'text') {
for (const target of spendInfo.spendTargets) {
if (target.memo != null) {
legacyMemos.push({
type: memoType,
value: target.memo
})
} else if (target.uniqueIdentifier != null) {
legacyMemos.push({
type: memoType,
value: target.uniqueIdentifier
})
} else if (typeof target.otherParams?.uniqueIdentifier === 'string') {
legacyMemos.push({
type: memoType,
value: target.otherParams.uniqueIdentifier
})
}
}
}

// If we don't have modern memos, use the legacy ones:
const out: EdgeSpendInfo = {
...spendInfo,
memos: spendInfo.memos ?? legacyMemos
}

validateMemos(out, currencyInfo)
return out
}
107 changes: 107 additions & 0 deletions src/common/validateMemos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { gt } from 'biggystring'
import { asMaybe } from 'cleaners'
import {
EdgeCurrencyInfo,
EdgeMemo,
EdgeMemoOption,
EdgeSpendInfo
} from 'edge-core-js/types'

import { asBase16, asIntegerString } from './types'

/**
* Validates the memos on an spend request.
* Throws an error if any of the memos are wrong.
*/
export function validateMemos(
spendInfo: EdgeSpendInfo,
currencyInfo: EdgeCurrencyInfo
): void {
const { memos = [] } = spendInfo
const {
displayName,
memoMaxLength,
memoMaxValue,
memoOptions = [],
multipleMemos = false,
memoType
} = currencyInfo

// Not all coins keep the legacy memo type in the modern list,
// but we still need to validate the legacy type if present:
const allOptions = [...memoOptions]
if (memoType === 'text') {
allOptions.push({ type: 'text', maxLength: memoMaxLength })
}
if (memoType === 'number') {
allOptions.push({ type: 'number', maxValue: memoMaxValue })
}
if (memoType === 'hex') {
allOptions.push({
type: 'hex',
maxBytes: memoMaxLength == null ? undefined : memoMaxLength / 2
})
}

// What we should call a "memo" in our error messages:
const { memoName = 'memo' } = memoOptions[0] ?? {}

// Now validate our memos:
for (const memo of memos) {
const options = allOptions.filter(option => memo.type === option.type)
if (options.length < 1) {
throw new Error(`${displayName} ${memoName}: cannot be type ${memo.type}`)
}
const problem = options
.map(option => getMemoError(memo, option))
.find(problem => problem != null)
if (problem != null) {
throw new Error(`${displayName} {memoName}: ${problem}`)
}
}

// Validate the number of memos:
if (!multipleMemos && memos.length > 1) {
throw new Error(`${displayName} only supports one ${memoName}`)
}
}

/**
* Checks a memo against a memo option.
* Returns `undefined` if valid, or an error string if invalid.
*/
function getMemoError(
memo: EdgeMemo,
option: EdgeMemoOption
): string | undefined {
if (
option.type === 'text' &&
option.maxLength != null &&
memo.value.length > option.maxLength
) {
return `cannot be longer than ${option.maxLength}`
}

if (option.type === 'number') {
const value = asMaybe(asIntegerString)(memo.value)
if (value == null) {
return `is not a valid number`
}
if (option.maxValue != null && gt(value, option.maxValue)) {
return `cannot be greater than ${option.maxValue}`
}
}

if (option.type === 'hex') {
const value = asMaybe(asBase16)(memo.value)
if (value == null) {
return `is not valid hexadecimal`
}
if (option.maxBytes != null && value.length > option.maxBytes) {
return `cannot be longer than ${option.maxBytes} bytes`
}
if (option.minBytes != null && value.length < option.minBytes) {
return `cannot be shorter than ${option.minBytes} bytes`
}
}
}
15 changes: 6 additions & 9 deletions src/eos/EosEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import parse from 'url-parse'

import { CurrencyEngine } from '../common/CurrencyEngine'
import { PluginEnvironment } from '../common/innerPlugin'
import { upgradeMemos } from '../common/upgradeMemos'
import {
asyncWaterfall,
cleanTxLogs,
Expand Down Expand Up @@ -952,8 +953,11 @@ export class EosEngine extends CurrencyEngine<EosTools, SafeEosWalletInfo> {
}

async makeSpend(edgeSpendInfoIn: EdgeSpendInfo): Promise<EdgeTransaction> {
edgeSpendInfoIn = upgradeMemos(edgeSpendInfoIn, this.currencyInfo)
const { edgeSpendInfo, currencyCode, nativeBalance, denom } =
this.makeSpendCheck(edgeSpendInfoIn)
const { memos = [] } = edgeSpendInfo

const tokenInfo = this.getTokenInfo(currencyCode)
if (tokenInfo == null) throw new Error('Unable to find token info')
const { contractAddress = 'eosio.token' } = tokenInfo
Expand Down Expand Up @@ -1013,14 +1017,7 @@ export class EosEngine extends CurrencyEngine<EosTools, SafeEosWalletInfo> {

const quantity =
toFixed(exchangeAmount, nativePrecision) + ` ${currencyCode}`
let memo = ''
if (
edgeSpendInfo.spendTargets[0].otherParams != null &&
typeof edgeSpendInfo.spendTargets[0].otherParams.uniqueIdentifier ===
'string'
) {
memo = edgeSpendInfo.spendTargets[0].otherParams.uniqueIdentifier
}
const memo = memos[0]?.type === 'text' ? memos[0].value : undefined

const transferActions: EosTransfer[] = [
{
Expand Down Expand Up @@ -1048,7 +1045,7 @@ export class EosEngine extends CurrencyEngine<EosTools, SafeEosWalletInfo> {
currencyCode, // currencyCode
date: 0, // date
isSend: nativeAmount.startsWith('-'),
memos: [],
memos,
nativeAmount, // nativeAmount
networkFee, // networkFee
otherParams: {
Expand Down
10 changes: 10 additions & 0 deletions src/eos/info/eosCommonInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { EdgeMemoOption } from 'edge-core-js/types'

// https://developers.eos.io/manuals/eos/v2.1/cleos/command-reference/transfer
export const eosMemoOptions: EdgeMemoOption[] = [
{
type: 'text',
memoName: 'memo',
maxLength: 256
}
]
2 changes: 2 additions & 0 deletions src/eos/info/eosInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { makeOuterPlugin } from '../../common/innerPlugin'
import type { EosTools } from '../EosTools'
import type { EosNetworkInfo } from '../eosTypes'
import { eosOtherMethodNames } from '../eosTypes'
import { eosMemoOptions } from './eosCommonInfo'

// ----EOSIO MAIN NET----
export const eosNetworkInfo: EosNetworkInfo = {
Expand All @@ -30,6 +31,7 @@ export const eosNetworkInfo: EosNetworkInfo = {
export const eosCurrencyInfo: EdgeCurrencyInfo = {
currencyCode: 'EOS',
displayName: 'EOS',
memoOptions: eosMemoOptions,
pluginId: 'eos',
unsafeBroadcastTx: true,
walletType: 'wallet:eos',
Expand Down
Loading

0 comments on commit a7d8561

Please sign in to comment.