Skip to content

Polkadot

Introduction

Polkadot unites and secures a growing ecosystem of specialized blockchains called parachains. Apps and services on Polkadot can securely communicate across chains, forming the basis for a truly interoperable decentralized web.

Scanning Blocks

The block scanner monitors fortuna addresses and looks for incoming transactions to those addresses. When it sees, it performs validations and witnesses to hermes chain.

    func (c *Client) convertToTxIn(dextrinsic *DecodedExtrinsic, height int64) (types.TxInItem, error) {
      e := dextrinsic.Extrinsic
      call, ok := e.Metadata.CallIndex[e.CallIndex]
      if !ok {
        return types.TxInItem{}, fmt.Errorf("failed to get call index from metadata")
      }
      // if extrinsic module is utility and function is batch all or batch with signed tx, gas class is normal
      // https://docs.substrate.io/build/tx-weights-fees/
      if e.Module == "Utility" && (call.Call.Name == "batch_all" || call.Call.Name == "batch") && e.ContainsTransaction && len(e.Signature) > 0 && dextrinsic.PaymentInfo.Class == "normal" {
        dest := " "
        dotQty := cosmos.ZeroUint()
        memo := ""
        calls := &[]UtilityBatchCall{}
        err := unmarshalAny(calls, e.Params[0].Value)
        gas := dextrinsic.PaymentInfo.PartialFee.BigInt().Uint64()
        comsosGas := cosmos.NewUint(gas)
        c.updateDOTGasCache(comsosGas)
        if err != nil {
          return types.TxInItem{}, err
        }

        for _, c := range *calls {
          for _, a := range c.Params {
            switch a.Name {
            // decode destination address to ss58
            case "dest":
              dest = EncodeToSS58(a.Value.(map[string]interface{})["Id"].(string))
              break
              // get transacted value to dest address
            case "value":
              value := a.Value.(string)
              dotQty, err = cosmos.ParseUint(value)
              if err != nil {
                return types.TxInItem{}, err
              }
              break
              // get memo field
            case "remark":
              memoBeforeConv := a.Value.(string)
              parts := strings.Split(memoBeforeConv, ":")
              if len(parts) < 2 {
                return types.TxInItem{}, fmt.Errorf("invalid memo of dot transaction %s, tx hash %s", memo, e.ExtrinsicHash)
              }
              if strings.EqualFold(parts[0], "memo") {
                memo = strings.Join(parts[1:], ":")
              } else {
                return types.TxInItem{}, fmt.Errorf("invalid memo of dot transaction %s, tx hash %s", memo, e.ExtrinsicHash)
              }
              break
            }
          }
        }

        return types.TxInItem{
          BlockHeight: height,
          Tx:          e.ExtrinsicHash,
          Sender:      EncodeToSS58(e.Address.(string)),
          To:          dest,
          Memo:        memo,
          Coins: common.Coins{
            common.NewCoin(common.DOTAsset, dotQty).WithDecimals(common.DOTCHAIN.GetGasAssetDecimal()),
          },
          Gas: common.Gas{
            common.NewCoin(common.DOTAsset, comsosGas).WithDecimals(common.DOTCHAIN.GetGasAssetDecimal()),
          },
        }, nil
      }

      return types.TxInItem{}, nil
    }

Polkadot Signer

Signer works on signing the outbound transactions received txout from hermes chain that needs to sent on arweave chain. Signing transactions are done in two ways: using ed25519/sr25519 tss signing algo and single wallet signing.

    func (c *Client) processOutboundTx(tx stypes.TxOutItem, hermeschainHeight int64) (gsrpcTypes.SignatureOptions, gsrpcTypes.Extrinsic, error) {
      _, err := tx.VaultPubKey.GetAddress(c.GetChain())

      if err != nil {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, fmt.Errorf("failed to convert polkadot address (%s): %w", tx.VaultPubKey.String(), err)
      }

      // initialise to collect all received txout substractFee coins
      var coins = cosmos.NewUint(0)
      for _, coin := range tx.Coins {
        // handle sors return, leave enough coin to pay for gas.
        if strings.HasPrefix(tx.Memo, hermes.TxToStringMap[hermes.TxSorsReturn]) {
          if coin.Asset == c.cfg.ChainID.GetGasAsset() {
            substractFee := c.averageFee().Mul(cosmos.NewUint(3)).Quo(cosmos.NewUint(2))
            if coin.Amount.LT(substractFee) {
              return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, fmt.Errorf("not enough to pay for transaction, Origal amount %d, Fee %d", coin.Amount.Uint64(), substractFee.Uint64())
            }

            coin.Amount = coin.Amount.Sub(substractFee)
          }
        }
        coins = coins.Add(coin.Amount)
      }

      // fetch the latest metadata from chain
      metadata, err := c.GetLatestMetadata()

      if err != nil {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, err
      }

      // convert from ss58 to hexadecimal address
      hexAddr := DecodeFromSS58(tx.ToAddress.String())
      appendX := strings.Join([]string{"0x", hexAddr}, "")
      dest, err := gsrpcTypes.NewMultiAddressFromHexAccountID(appendX)

      if err != nil {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, err
      }

      // append memo string to tx.memo and join with ":"
      memoArray := strings.Join([]string{"memo", tx.Memo}, ":")
      memo := []byte(memoArray)

      // prepare system::remark call
      remarkCall, err := gsrpcTypes.NewCall(metadata, "System.remark", memo)

      if err != nil {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, err
      }

      // dest amount
      valUint64 := coins.Uint64()
      amount := gsrpcTypes.NewUCompactFromUInt(valUint64)

      // prepare balances::transfer call
      transferCall, err := gsrpcTypes.NewCall(metadata, "Balances.transfer", dest, amount)

      if err != nil {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, err
      }

      // prepare utility::batch_all
      utilityBatchCall, err := gsrpcTypes.NewCall(metadata, "Utility.batch_all", []gsrpcTypes.Call{transferCall, remarkCall})

      if err != nil {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, err
      }

      // fetch genesis hash for immortal era.
      genesisHash, err := c.gsrpc.RPC.Chain.GetBlockHash(0)

      if err != nil {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, err
      }

      // create storage key
      storageKey, err := gsrpcTypes.CreateStorageKey(metadata, "System", "Account", c.dotKeysignWrapper.dotKP.PublicKey)

      if err != nil {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, err
      }

      // fetch account info for nonce value
      var accountInfo gsrpcTypes.AccountInfo
      ok, err := c.gsrpc.RPC.State.GetStorageLatest(storageKey, &accountInfo)
      nonce := accountInfo.Nonce

      if err != nil || !ok {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, err
      }

      // fetch runtime version data
      runtimeVersion, err := c.GetRuntimeVersion()

      if err != nil {
        return gsrpcTypes.SignatureOptions{}, gsrpcTypes.Extrinsic{}, err
      }

      // prepare extrinsic for signing
      extrinsic := gsrpcTypes.NewExtrinsic(utilityBatchCall)

      return gsrpcTypes.SignatureOptions{
        BlockHash:          genesisHash,
        Era:                gsrpcTypes.ExtrinsicEra{IsMortalEra: false},
        GenesisHash:        genesisHash,
        Nonce:              gsrpcTypes.NewUCompactFromUInt(uint64(nonce)),
        SpecVersion:        runtimeVersion.SpecVersion,
        Tip:                gsrpcTypes.NewUCompactFromUInt(0),
        TransactionVersion: runtimeVersion.TransactionVersion,
      }, extrinsic, nil
    }