@@ -3,22 +3,33 @@ import {
33 BaseCoin ,
44 BitGoBase ,
55 KeyPair ,
6- ParseTransactionOptions ,
76 ParsedTransaction ,
87 SignTransactionOptions ,
98 SignedTransaction ,
109 VerifyTransactionOptions ,
1110 MultisigType ,
1211 multisigTypes ,
1312 MPCAlgorithm ,
13+ InvalidAddressError ,
14+ EDDSAMethods ,
1415 TssVerifyAddressOptions ,
1516 MPCType ,
16- verifyEddsaTssWalletAddress ,
17+ PopulatedIntent ,
18+ PrebuildTransactionWithIntentOptions ,
1719} from '@bitgo/sdk-core' ;
18- import { BaseCoin as StaticsBaseCoin , CoinFamily } from '@bitgo/statics' ;
20+ import { BaseCoin as StaticsBaseCoin , CoinFamily , coins } from '@bitgo/statics' ;
1921import utils from './lib/utils' ;
20- import { KeyPair as IotaKeyPair } from './lib' ;
22+ import { KeyPair as IotaKeyPair , Transaction , TransactionBuilderFactory } from './lib' ;
2123import { auditEddsaPrivateKey } from '@bitgo/sdk-lib-mpc' ;
24+ import BigNumber from 'bignumber.js' ;
25+ import * as _ from 'lodash' ;
26+ import {
27+ ExplainTransactionOptions ,
28+ IotaParseTransactionOptions ,
29+ TransactionExplanation ,
30+ TransferTxData ,
31+ } from './lib/iface' ;
32+ import { TransferTransaction } from './lib/transferTransaction' ;
2233
2334export class Iota extends BaseCoin {
2435 protected readonly _staticsCoin : Readonly < StaticsBaseCoin > ;
@@ -77,12 +88,44 @@ export class Iota extends BaseCoin {
7788 return utils . isValidAddress ( address ) ;
7889 }
7990
91+ /**
92+ * @inheritDoc
93+ */
94+ async explainTransaction ( params : ExplainTransactionOptions ) : Promise < TransactionExplanation > {
95+ const rawTx = params . txBase64 ;
96+ if ( ! rawTx ) {
97+ throw new Error ( 'missing required tx prebuild property txBase64' ) ;
98+ }
99+ const transaction = await this . rebuildTransaction ( rawTx ) ;
100+ if ( ! transaction ) {
101+ throw new Error ( 'failed to explain transaction' ) ;
102+ }
103+ return transaction . explainTransaction ( ) ;
104+ }
105+
80106 /**
81107 * Verifies that a transaction prebuild complies with the original intention
82108 * @param params
83109 */
84110 async verifyTransaction ( params : VerifyTransactionOptions ) : Promise < boolean > {
85- // TODO: Add IOTA-specific transaction verification logic
111+ const { txPrebuild : txPrebuild , txParams : txParams } = params ;
112+ const rawTx = txPrebuild . txBase64 ;
113+ if ( ! rawTx ) {
114+ throw new Error ( 'missing required tx prebuild property txBase64' ) ;
115+ }
116+ const transaction = await this . rebuildTransaction ( rawTx ) ;
117+ if ( ! transaction ) {
118+ throw new Error ( 'failed to verify transaction' ) ;
119+ }
120+ if ( txParams . recipients !== undefined ) {
121+ if ( ! ( transaction instanceof TransferTransaction ) ) {
122+ throw new Error ( 'Tx not a transfer transaction' ) ;
123+ }
124+ const txData = transaction . toJson ( ) as TransferTxData ;
125+ if ( ! txData . recipients || ! _ . isEqual ( txParams . recipients , txData . recipients ) ) {
126+ throw new Error ( 'Tx recipients does not match with expected txParams recipients' ) ;
127+ }
128+ }
86129 return true ;
87130 }
88131
@@ -91,20 +134,80 @@ export class Iota extends BaseCoin {
91134 * @param params
92135 */
93136 async isWalletAddress ( params : TssVerifyAddressOptions ) : Promise < boolean > {
94- return verifyEddsaTssWalletAddress (
95- params ,
96- ( address ) => this . isValidAddress ( address ) ,
97- ( publicKey ) => utils . getAddressFromPublicKey ( publicKey )
98- ) ;
137+ const { keychains, address, index } = params ;
138+
139+ if ( ! this . isValidAddress ( address ) ) {
140+ throw new InvalidAddressError ( `invalid address: ${ address } ` ) ;
141+ }
142+
143+ if ( ! keychains ) {
144+ throw new Error ( 'missing required param keychains' ) ;
145+ }
146+
147+ for ( const keychain of keychains ) {
148+ const MPC = await EDDSAMethods . getInitializedMpcInstance ( ) ;
149+ const commonKeychain = keychain . commonKeychain as string ;
150+
151+ const derivationPath = 'm/' + index ;
152+ const derivedPublicKey = MPC . deriveUnhardened ( commonKeychain , derivationPath ) . slice ( 0 , 64 ) ;
153+ const expectedAddress = utils . getAddressFromPublicKey ( derivedPublicKey ) ;
154+
155+ if ( address !== expectedAddress ) {
156+ return false ;
157+ }
158+ }
159+ return true ;
99160 }
100161
101162 /**
102163 * Parse a transaction
103164 * @param params
104165 */
105- async parseTransaction ( params : ParseTransactionOptions ) : Promise < ParsedTransaction > {
106- // TODO: Add IOTA-specific transaction parsing logic
107- return { } ;
166+ async parseTransaction ( params : IotaParseTransactionOptions ) : Promise < ParsedTransaction > {
167+ const transactionExplanation = await this . explainTransaction ( { txBase64 : params . txBase64 } ) ;
168+
169+ if ( ! transactionExplanation ) {
170+ throw new Error ( 'Invalid transaction' ) ;
171+ }
172+
173+ let fee = new BigNumber ( 0 ) ;
174+
175+ if ( transactionExplanation . outputs . length <= 0 ) {
176+ return {
177+ inputs : [ ] ,
178+ outputs : [ ] ,
179+ fee,
180+ } ;
181+ }
182+
183+ const senderAddress = transactionExplanation . outputs [ 0 ] . address ;
184+ if ( transactionExplanation . fee . fee !== '' ) {
185+ fee = new BigNumber ( transactionExplanation . fee . fee ) ;
186+ }
187+
188+ // assume 1 sender, who is also the fee payer
189+ const inputs = [
190+ {
191+ address : senderAddress ,
192+ amount : new BigNumber ( transactionExplanation . outputAmount ) . plus ( fee ) . toFixed ( ) ,
193+ } ,
194+ ] ;
195+
196+ const outputs : {
197+ address : string ;
198+ amount : string ;
199+ } [ ] = transactionExplanation . outputs . map ( ( output ) => {
200+ return {
201+ address : output . address ,
202+ amount : new BigNumber ( output . amount ) . toFixed ( ) ,
203+ } ;
204+ } ) ;
205+
206+ return {
207+ inputs,
208+ outputs,
209+ fee,
210+ } ;
108211 }
109212
110213 /**
@@ -149,4 +252,30 @@ export class Iota extends BaseCoin {
149252 }
150253 auditEddsaPrivateKey ( prv , publicKey ?? '' ) ;
151254 }
255+
256+ /** @inheritDoc */
257+ async getSignablePayload ( serializedTx : string ) : Promise < Buffer > {
258+ const rebuiltTransaction = await this . rebuildTransaction ( serializedTx ) ;
259+ return rebuiltTransaction . signablePayload ;
260+ }
261+
262+ /** inherited doc */
263+ setCoinSpecificFieldsInIntent ( intent : PopulatedIntent , params : PrebuildTransactionWithIntentOptions ) : void {
264+ intent . unspents = params . unspents ;
265+ }
266+
267+ private getTxBuilderFactory ( ) : TransactionBuilderFactory {
268+ return new TransactionBuilderFactory ( coins . get ( this . getChain ( ) ) ) ;
269+ }
270+
271+ private async rebuildTransaction ( txHex : string ) : Promise < Transaction > {
272+ const txBuilderFactory = this . getTxBuilderFactory ( ) ;
273+ try {
274+ const txBuilder = txBuilderFactory . from ( txHex ) ;
275+ txBuilder . transaction . isSimulateTx = false ;
276+ return ( await txBuilder . build ( ) ) as Transaction ;
277+ } catch {
278+ throw new Error ( 'Failed to rebuild transaction' ) ;
279+ }
280+ }
152281}
0 commit comments