这周主要做了 ETH钱包:(1)钱包列表展示钱包价值(2)在钱包内发起一个Transation交易 (3)获取交易详情
当前热钱包部分, 对于以上三个功能最大的需求功能,最大阻碍是精度问题和超大数的的基本运算
一.钱包价值展示
参考imToken, imToken主做ETH钱包三年多,相当专业,有太多的地方值得我们学习。
首页部分头部是钱包内主币和代币换算成实体货币数量的总价值,列表部分是当前钱包内主币和选中代币集合,展示了币的图标、名称、数量、兑换成实体货币价值。
关键是数量这里:
ETH 有很多单位,基本使用统计如下:
单位Unit | 以最小单位Wei为基准的进制(Wei 数量) | 使用频率 |
Wei | 1 wei | 经常 |
KWei | 10 * 3 wei | |
MWei | 10 * 6 wei | |
GWei | 10 * 9 wei | 经常 |
microether | 10 * 12 wei | |
milliether | 10 * 15 wei | |
ether | 10 * 18 wei | 经常 |
二 需要运算的地方:
发起一笔转账时的进制转换,和gas 费用。
Gas limit 是用户愿意为执行某个操作或确认交易支付的最大Gas量(最少21,000) 单位个(即对应的是ETH的个数)
计算 花费应该 gas fee = Gas Limit * Gas Price
这个是给矿工的佣金
第一步就是把gas price 从Gwei 转为 ETH 再 * limit
Gas Limit*Gas Price
eg: 1Gwei≈0.00000002 ETH,所以佣金最少为0.00000002*21000=0.00042ETH
(1)用户输入转账ETH个数 单位:ether
和服务端约定上传一律用最小单位,这里需要做一次进制转换 即:1 ether = 10 * 18 wei
如果交易稍大就会超过 iOS 中精度了,更别提运算了。因为有Web3 ,提供 BigDecimal方案处理超大数的精度处理,iOS 这边,完全可以自己写超大数的加减乘除,也可以选择一些成熟的第三 ,这里我使用了一个框架web3swift
web3swift,完全支持在iOS 客户端上实施冷钱包,并发起交易的整个过程 通过 Infura 节点(测试/主网)。具体参考(1)上介绍
所以,上一篇文章,我使用trust keystore 能完成创建钱包,导入钱包,校验钱包的操作。今天换成web3swift,主要是符合我的需求和持续化的迭代设计。
至此,超大数,精度浮点数运算 进制转换使用swift 框架 web3swift来解决
使用举例:
// // FIREnterUnitManager.swift // AkeyWallet // // Created by HF on 2018/7/14. // Copyright © 2018年 Fir.im. All rights reserved. // import Foundation import BigInt import web3swift @objc public class FIREnterUnitManager:NSObject { //从ETH 转换为 Wei public static func getWeiBigStringFrom(ethString:String) throws -> String { let eth = Web3.Utils.parseToBigUInt(ethString, units: .eth);//当前是eth if (nil == eth) { return "" } else { let balString = Web3.Utils.formatToEthereumUnits(eth!, toUnits: .wei, decimals: 0) return balString! } } //从wei 到 eth public static func getETHFrom(weiString:String) throws -> String { let weiBig = BigUInt(weiString); let balString = Web3.Utils.formatToEthereumUnits(weiBig!, toUnits: .eth, decimals: 8) return balString!; }//从当前进制到wei public static func getWeiFromUnit(unitString:String,decimal:Int) throws -> String { let balance = Web3.Utils.parseToBigUInt(unitString, decimals: decimal) let balString = Web3.Utils.formatToEthereumUnits(balance!, toUnits: .wei, decimals: 8) return balString!; }//wei乘法 public static func getMultiWei(wei1:String,wei2:String) throws -> String { let weiBig1 = BigUInt(wei1); let weiBig2 = BigUInt(wei2); let wei = weiBig1! * weiBig2!; let balString = Web3.Utils.formatToEthereumUnits(wei, toUnits: .wei, decimals: 8) return balString!; } //wei加法 public func getSumWei(wei1:String,wei2:String) throws -> String { let weiBig1 = BigUInt(wei1); let weiBig2 = BigUInt(wei2); let wei = weiBig1! + weiBig2!; let balString = Web3.Utils.formatToEthereumUnits(wei, toUnits: .wei, decimals: 8) return balString!; }}
(2)有一些非超大数,比如一些显示,虚拟币、实体币,求和,汇率计算什么的,也可以选择上面的方法算,但是我的风格是按需处理,不是超大数,就是正常的金融数据运算,自己写了一个工具类。
正常的浮点经度进行金融数据运算,会丢精度。苹果针对浮点型计算时存在精度计算误差的问题,提供NSDecimalNumber的计算类。
参考(2),自己完善一下边界处理
NSDecimalNumber 有 NSRoundingMode 类型,我使用四舍五入类型 NSRoundPlain ,请参考者按需处理。
// // NSDecimalNumber+FIRDeimalTool.h // AkeyWallet // // Created by HF on 2018/7/15. // Copyright © 2018年 Fir.im. All rights reserved. // #import <Foundation/Foundation.h> typedef NS_ENUM(NSInteger, calculationType) { Add, Subtract, Multiply, Divide }; @interface NSDecimalNumber (FIRDeimalTool) //自定义加减乘除 +(NSDecimalNumber *)aDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber1 type:(calculationType)type anotherDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber2 andDecimalNumberHandler:(NSDecimalNumberHandler *)handler; //小数比较 +(NSComparisonResult)aDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber1 compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber2; /** 小数位数保留 @param str1 小数 @param scale 保留位数 @return 结果 */ +(NSString *)stringWithDecimalNumber:(NSDecimalNumber *)str1 scale:(NSInteger)scale; extern NSComparisonResult StrNumCompare(id str1,id str2); extern NSDecimalNumber *handlerDecimalNumber(id strOrNum,NSRoundingMode mode,int scale); extern NSComparisonResult FIRCompare(id strOrNum1,id strOrNum2); //加减乘除 extern NSDecimalNumber *FIRAdd(id strOrNum1,id strOrNum2); extern NSDecimalNumber *FIRSub(id strOrNum1,id strOrNum2); extern NSDecimalNumber *FIRMul(id strOrNum1,id strOrNum2); extern NSDecimalNumber *FIRDiv(id strOrNum1,id strOrNum2); //比大小 extern NSDecimalNumber *FIRMin(id strOrNum1,id strOrNum2); extern NSDecimalNumber *FIRMax(id strOrNum1,id strOrNum2); //定义运算结果小数取舍模态 extern NSDecimalNumber *FIRAdd_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale); extern NSDecimalNumber *FIRSub_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale); extern NSDecimalNumber *FIRMul_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale); extern NSDecimalNumber *FIRDiv_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale); extern NSDecimalNumber *FIRMin_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale); extern NSDecimalNumber *FIRMax_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale); /** 字符串小数舍去末尾0 @param stringOrNumber 字符串小数/Number/decimalNumer @return 舍去末尾0 */ +(NSString *)getStringDecimalWithoutEndZero:(id)stringOrNumber; @end
// // NSDecimalNumber+FIRDeimalTool.m // AkeyWallet // // Created by HF on 2018/7/15. // Copyright © 2018年 Fir.im. All rights reserved. // #import "NSDecimalNumber+FIRDeimalTool.h" @implementation NSDecimalNumber (FIRDeimalTool) +(NSDecimalNumber *)aDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber1 type:(calculationType)type anotherDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber2 andDecimalNumberHandler:(NSDecimalNumberHandler *)handler{ if (!stringOrNumber2 || !stringOrNumber1) { NSAssert(NO, @"输入正确类型"); return nil; } NSDecimalNumber *one; NSDecimalNumber *another; NSDecimalNumber *returnNum; if ([stringOrNumber1 isKindOfClass:[NSString class]]) { one = [NSDecimalNumber decimalNumberWithString:stringOrNumber1]; }else if([stringOrNumber1 isKindOfClass:[NSDecimalNumber class]]){ one = stringOrNumber1; }else if ([stringOrNumber1 isKindOfClass:[NSNumber class]]){ one = [NSDecimalNumber decimalNumberWithDecimal:[stringOrNumber1 decimalValue]]; }else{ NSAssert(NO, @"输入正确类型"); return nil; } if ([stringOrNumber2 isKindOfClass:[NSString class]]) { another = [NSDecimalNumber decimalNumberWithString:stringOrNumber2]; }else if([stringOrNumber2 isKindOfClass:[NSDecimalNumber class]]){ another = stringOrNumber2; }else if ([stringOrNumber2 isKindOfClass:[NSNumber class]]){ another = [NSDecimalNumber decimalNumberWithDecimal:[stringOrNumber2 decimalValue]]; }else{ NSAssert(NO, @"输入正确类型"); return nil; } //排除NaN if (isnan(one.doubleValue) || isinf(one.doubleValue)) one = [NSDecimalNumber decimalNumberWithString:@"0"]; if (isnan(another.doubleValue) || isinf(another.doubleValue)) another = [NSDecimalNumber decimalNumberWithString:@"0"]; // if (type == Add) { returnNum = [one decimalNumberByAdding:another]; }else if (type == Subtract){ returnNum = [one decimalNumberBySubtracting:another]; }else if (type == Multiply){ returnNum = [one decimalNumberByMultiplyingBy:another]; }else if (type == Divide){ if ([NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:another compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:@(0)] == 0) { returnNum = nil; }else returnNum = [one decimalNumberByDividingBy:another]; }else{ returnNum = nil; } if (returnNum) { if (handler) { return [returnNum decimalNumberByRoundingAccordingToBehavior:handler]; }else{ return returnNum; } }else{ NSAssert(NO, @"输入正确类型"); return nil; } } +(NSComparisonResult)aDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber1 compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber2{ if (!stringOrNumber2 || !stringOrNumber1) { NSAssert(NO, @"输入正确类型"); return -404; } NSDecimalNumber *one; NSDecimalNumber *another; if ([stringOrNumber1 isKindOfClass:[NSString class]]) { one = [NSDecimalNumber decimalNumberWithString:stringOrNumber1]; }else if([stringOrNumber1 isKindOfClass:[NSDecimalNumber class]]){ one = stringOrNumber1; }else if ([stringOrNumber1 isKindOfClass:[NSNumber class]]){ one = [NSDecimalNumber decimalNumberWithDecimal:[stringOrNumber1 decimalValue]]; }else{ NSAssert(NO, @"输入正确类型"); return -404; } if ([stringOrNumber2 isKindOfClass:[NSString class]]) { another = [NSDecimalNumber decimalNumberWithString:stringOrNumber2]; }else if([stringOrNumber2 isKindOfClass:[NSDecimalNumber class]]){ another = stringOrNumber2; }else if ([stringOrNumber2 isKindOfClass:[NSNumber class]]){ another = [NSDecimalNumber decimalNumberWithDecimal:[stringOrNumber2 decimalValue]]; }else{ NSAssert(NO, @"输入正确类型"); return -404; } //排除NaN if (isnan(one.doubleValue) || isinf(one.doubleValue)) one = [NSDecimalNumber decimalNumberWithString:@"0"]; if (isnan(another.doubleValue) || isinf(another.doubleValue)) another = [NSDecimalNumber decimalNumberWithString:@"0"]; // return [one compare:another]; } +(NSString *)stringWithDecimalNumber:(NSDecimalNumber *)str1 scale:(NSInteger)scale{ if (!str1) { return @""; } NSString *str = [NSString stringWithFormat:@"%@",str1]; if (str && str.length) { if ([str rangeOfString:@"."].length == 1) {//有小数点 NSArray *arr = [str componentsSeparatedByString:@"."]; if (scale > 0) { NSInteger count = [arr[1] length]; for (NSInteger i = count; i<scale; i++) { str = [str stringByAppendingString:@"0"]; } return str; }else{ return arr[0]; } }else{//没有小数点 if ([str rangeOfString:@"."].length) { return @""; } if (scale > 0) { str = [str stringByAppendingString:@"."]; for (int i = 0; i<scale; i++) { str = [str stringByAppendingString:@"0"]; } return str; }else{ return str; } } }else{ return @""; } } NSComparisonResult StrNumCompare(id str1,id str2){ return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:str1 compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:str2]; } NSDecimalNumber *handlerDecimalNumber(id strOrNum,NSRoundingMode mode,int scale){ if (!strOrNum || strOrNum == nil) { NSLog(@"输入正确类型"); return nil; }else{ NSDecimalNumber *one; if ([strOrNum isKindOfClass:[NSString class]]) { one = [NSDecimalNumber decimalNumberWithString:strOrNum]; }else if([strOrNum isKindOfClass:[NSDecimalNumber class]]){ one = strOrNum; }else if ([strOrNum isKindOfClass:[NSNumber class]]){ one = [NSDecimalNumber decimalNumberWithDecimal:[strOrNum decimalValue]]; }else{ NSLog(@"输入正确的类型"); return nil; } //排除NaN if (isnan(one.doubleValue) || isinf(one.doubleValue)) one = [NSDecimalNumber decimalNumberWithString:@"0"]; // NSDecimalNumberHandler *handler = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:mode scale:scale raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:NO]; return [one decimalNumberByRoundingAccordingToBehavior:handler]; } } NSDecimalNumber *FIRAdd(id strOrNum1,id strOrNum2){ return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 type:Add anotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2 andDecimalNumberHandler:nil]; } NSDecimalNumber *FIRSub(id strOrNum1,id strOrNum2){ return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 type:Subtract anotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2 andDecimalNumberHandler:nil]; } NSDecimalNumber *FIRMul(id strOrNum1,id strOrNum2){ return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 type:Multiply anotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2 andDecimalNumberHandler:nil]; } NSDecimalNumber *FIRDiv(id strOrNum1,id strOrNum2){ return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 type:Divide anotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2 andDecimalNumberHandler:nil]; } NSComparisonResult FIRCompare(id strOrNum1,id strOrNum2){ return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2]; } NSDecimalNumber *FIRMin(id strOrNum1,id strOrNum2){ return FIRCompare(strOrNum1, strOrNum2) > 0 ? strOrNum2 : strOrNum1; } NSDecimalNumber *FIRMax(id strOrNum1,id strOrNum2){ return FIRCompare(strOrNum1, strOrNum2) > 0 ? strOrNum1 : strOrNum2; } NSDecimalNumber *FIRAdd_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){ return handlerDecimalNumber(FIRAdd(strOrNum1, strOrNum2), mode, scale); } NSDecimalNumber *FIRSub_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){ return handlerDecimalNumber(FIRSub(strOrNum1, strOrNum2), mode, scale); } NSDecimalNumber *FIRMul_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){ return handlerDecimalNumber(FIRMul(strOrNum1, strOrNum2), mode, scale); } NSDecimalNumber *FIRDiv_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){ return handlerDecimalNumber(FIRDiv(strOrNum1, strOrNum2), mode, scale); } NSDecimalNumber *FIRMin_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){ return handlerDecimalNumber(FIRMin(strOrNum1, strOrNum2), mode, scale); } NSDecimalNumber *FIRMax_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){ return handlerDecimalNumber(FIRMax(strOrNum1, strOrNum2), mode, scale); } +(NSString *)getStringDecimalWithoutEndZero:(id)stringOrNumber { NSDecimalNumber *num = FIRAdd(stringOrNumber, @"0"); return [NSString stringWithFormat:@"%@",num]; } @end
参考:
(1) https://github.com/BANKEX/web3swift
(2)https://github.com/CodeJCSON/NSDecimalNumber
待续