Individual API endpoints versus single endpoint

Discussion of the pros and cons of creating individual endpoints for fio transactions that update state.

Calling signed transaction endpoints in Typescript SDK

Our Typescript SDK currently breaks out all of the individual endpoints and you use genericAction to call the signed transaction. You refer to the documentation for the name of the SDK genericAction calls:

1 2 3 4 5 const result = await user1sdk.genericAction('registerFioDomain', { fioDomain: user1Domain, maxFee: config.api.register_fio_domain.fee , walletFioAddress: '' })

Our SDKs also support pushTransaction. It takes three params: action, account, and data and works for any action:

1 2 3 4 5 6 7 8 9 10 const result = await user1sdk.genericAction('pushTransaction', { action: 'regdomain', account: 'fio.address', data: { fio_domain: user1Domain2, owner_fio_public_key: user1.publicKey, max_fee: config.api.register_fio_domain.fee, tpid: '' } })

On a separate note, the code for creating a signed transaction is fairly simple using our fiojs library:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 const callFioApiSigned = async (endPoint, txn) => { info = await (await fetch(fiourl + 'get_info')).json(); blockInfo = await (await fetch(fiourl + 'get_block', {body: `{"block_num_or_id": ${info.last_irreversible_block_num}}`, method: 'POST'})).json() chainId = info.chain_id; currentDate = new Date(); timePlusTen = currentDate.getTime() + 10000; timeInISOString = (new Date(timePlusTen)).toISOString(); expiration = timeInISOString.substr(0, timeInISOString.length - 1); transaction = { expiration, ref_block_num: blockInfo.block_num & 0xffff, ref_block_prefix: blockInfo.ref_block_prefix, actions: [{ account: txn.account, name: txn.action, authorization: [{ actor: txn.actor, permission: 'active', }], data: txn.data, }] }; abiMap = new Map() tokenRawAbi = await (await fetch(fiourl + 'get_raw_abi', {body: '{"account_name": "' + txn.account + '"}', method: 'POST'})).json() abiMap.set(txn.account, tokenRawAbi) var privateKeys = [txn.privKey]; const tx = await Fio.prepareTransaction({ transaction, chainId, privateKeys, abiMap, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() }); pushResult = await fetch(fiourl + endPoint, { body: JSON.stringify(tx), method: 'POST', }); json = await pushResult.json() return json; };

You can then call push_transaction directly:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const result = await callFioApiSigned('push_transaction', { action: 'addaddress', account: 'fio.address', actor: userA1.account, privKey: userA1.privateKey, data: { "fio_address": "purse@alice", "public_addresses": [ { "chain_code": "BTC", "token_code": "BTC", "public_address": "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs" } ], "max_fee": 1000000000, "tpid": "rewards@wallet", "actor": "aftyershcu22" } })

Calling getters in the SDK

The typescript sdk delineates all of the getter endpoints in the main SDK. For example

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 case 'getFioNames': return this.getFioNames(params.fioPublicKey); case 'getFioDomains': return this.getFioDomains(params.fioPublicKey, params.limit, params.offset); case 'getFioAddresses': return this.getFioAddresses(params.fioPublicKey, params.limit, params.offset); case 'getPendingFioRequests': return this.getPendingFioRequests(params.limit, params.offset); case 'getCancelledFioRequests': return this.getCancelledFioRequests(params.limit, params.offset); case 'getSentFioRequests': return this.getSentFioRequests(params.limit, params.offset); case 'getPublicAddress': return this.getPublicAddress(params.fioAddress, params.chainCode, params.tokenCode); etc...

Examples of new endpoints

  • FIP-1: transfer_fio_domain, transfer_fio_address

  • FIP-3: cancel_funds_request

  • FIP-4: remove_pub_address, remove_all_pub_addresses

  • etc.

Pros of using individual endpoints

  • It is helpful to have the individual endpoints clearly labeled and documented in our Dev Hub.

  • We are able to perform error and parameter checking at the plugin level, increasing performance.

Cons of using individual endpoints

  • Release of a new endpoint requires that all API nodes upgrade chain code in order to support the new endpoint. The upgrade process is difficult for several reasons:

    • We do not know who is running a node.

    • We do not track API node versions.

    • Communication with partners running nodes and tracking upgrades is time consuming.

  • Versioning can get complex with respect to the SDKs.

    • For example a wallet migrating from v1.1 to v1.2 of the SDK needs to make sure the API node they are using has also upgraded to support the relevant API endpoints.

  • New SDKs have to be released for every new API endpoint.

Pros of single endpoint

  • Wallets do not have to upgrade to support new contract actions

  • Once a new action is supported in a fio contract, it is immediately available to partners.

  • Reduced number of SDK and chain code releases.

Supporting both

It has been argued that we currently support both push_transaction (single endpoint) and FIO endpoints, so this is not an issue. The problem is that wallets are integrating and they are using individual endpoints. Thus, if we stop supporting multiple endpoints, or are slow to release them, it impacts the wallets ability to upgrade.

Thoughts on future direction

Eric:

  • I recommend we update our Dev Hub to focus on push_transaction as the single endpoint and deprecate individual endpoints. The sooner the better so our partners do not get locked into the ever expanding list of endpoints.

  • I recommend we propose a generic callFioApi method for calling “getter” endpoints instead of explicitly labeling all get endpoints. This might look something like:

1 2 3 4 5 6 const json = { "fio_public_key": userC1.publicKey, "limit": 1, "offset": -1 } result = await callFioApi("get_cancelled_fio_requests", json);