How to configure predicate call's inputs and outputs in ts-sdk?

Hi there
In our CLOB on predicates in a few cases like fulfillment or cancelation of order, we should have the possibility to configure ScriptCallHandler with inputs and outputs

Here is the cancel_order func on Rust language using Rust SDK

 pub async fn cancel_order(
        predicate: &Predicate,
        wallet: &WalletUnlocked,
        asset0: AssetId,
        amount0: u64,
    ) -> Result<FuelCallResponse<()>, fuels::prelude::Error> {
        let provider = wallet.provider().unwrap();

        let inputs = predicate
            .clone()
            .set_provider(provider.clone())
            .get_asset_inputs_for_amount(asset0, amount0, None)
            .await
            .unwrap();

        let outputs = wallet.get_asset_outputs_for_amount(wallet.address(), asset0, 0);
        println!("inputs = {:?}", inputs);
        println!("outputs = {:?}", outputs);
        let script_call = ScriptCallHandler::new(
            vec![],
            UnresolvedBytes::default(),
            wallet.clone(),
            provider.clone(),
            Default::default(),
        )
        .with_inputs(inputs)
        .with_outputs(outputs)
        .tx_params(TxParameters::default().set_gas_price(1));

        script_call.call().await
    }

logs will be like that

alice_address = 0xccc4f9249536cf89ab0f31bb52fa06c00ea0387dcac5830924ecf56f2dded3a5
Predicate root = Bech32Address { hrp: "fuel", hash: c04e498ac26cbff0b93d9c218cf8bdc18e1190953ef5f3e5ace8146deded519c }
USDC AssetId (asset0) = 0x56fb8789a590ea9c12af6fe6dc2b43f347700b049d4f823fd4476c6f366af201
UNI AssetId (asset1) = 0x5381bbd1cff41519062c8531ec30e8ea1a2d752e59e4ac884068d3821e9f0093
    

    inputs = [
        ResourcePredicate { 
            resource: Coin(Coin {
                 amount: 1000000000, 
                 block_created: 766024, 
                 asset_id: 56fb8789a590ea9c12af6fe6dc2b43f347700b049d4f823fd4476c6f366af201, 
                 utxo_id: UtxoId { tx_id: 2744628915b0d89ca8e79133a4598f72b32be9e884a723dad67a56c882da48ff, output_index: 2 }, 
                 maturity: 0, 
                 owner: Bech32Address { hrp: "fuel", hash: c04e498ac26cbff0b93d9c218cf8bdc18e1190953ef5f3e5ace8146deded519c }, 
                 status: Unspent 
                }), 
            code: [...], 
            data: UnresolvedBytes { data: [] } 
        }
    ]

    outputs = [
        Coin { 
            to: ccc4f9249536cf89ab0f31bb52fa06c00ea0387dcac5830924ecf56f2dded3a5, 
            amount: 0, 
            asset_id: 56fb8789a590ea9c12af6fe6dc2b43f347700b049d4f823fd4476c6f366af201 
        }, 
        Change { 
            to: ccc4f9249536cf89ab0f31bb52fa06c00ea0387dcac5830924ecf56f2dded3a5, 
            amount: 0, 
            asset_id: 56fb8789a590ea9c12af6fe6dc2b43f347700b049d4f823fd4476c6f366af201 
        }
    ]

In the predicate, this code is in charge of cancelation

    let mut i = 0u8;
    let inputs: u8 = input_count();
    while i < inputs  {
        if input_owner(i).unwrap() == Address::from(MAKER) {
            return true;
        }
        i += 1u8;
    }

So, ts-sdk has func `` that can help us to make cancelation:

const cancelTx = await wallet.transfer(predicate.address, 0, token0.assetId, {
    gasPrice: 1,
  });
await cancelTx.waitForResult();

We did this script to test that

import BN from "../src/utils/BN";
import { TOKENS_BY_SYMBOL } from "../src/constants";
import { LimitOrderPredicateAbi__factory } from "../src/predicates";
import { Predicate, Provider, Wallet } from "fuels";
import { nodeUrl, privateKey } from "../src/config";

(async () => {
  const token0 = TOKENS_BY_SYMBOL.USDC;
  const amount0 = BN.parseUnits(20, token0.decimals);
  const token1 = TOKENS_BY_SYMBOL.BTC;
  const amount1 = BN.parseUnits(0.001, token1.decimals);
  const exp = BN.parseUnits(1, 9 + token0.decimals - token1.decimals);
  let price = amount1.times(exp).div(amount0);

  const wallet = Wallet.fromPrivateKey(privateKey, nodeUrl);

  // console.log(price.toString());
  const configurableConstants = {
    ASSET0: token0.assetId,
    ASSET1: token1.assetId,
    MAKER: wallet.address.toB256(),
    PRICE: price.toFixed(0),
    ASSET0_DECINALS: token0.decimals,
    ASSET1_DECINALS: token1.decimals,
  };
  console.log(configurableConstants);
  const predicate = new Predicate(
    LimitOrderPredicateAbi__factory.bin,
    LimitOrderPredicateAbi__factory.abi,
    new Provider(nodeUrl),
    configurableConstants
  );

  console.log(predicate.address.toB256());

  const initialPredicateBalance = await predicate.getBalance(token0.assetId);
  console.log("initialPredicateBalance", initialPredicateBalance.toString());

  const depositTx = await wallet
    .transfer(predicate.address, amount0.toFixed(0), token0.assetId, { gasPrice: 1 })
    .catch((e) => console.error(`depositTx ${e}`));
  await depositTx?.waitForResult();
  //
  const feeTx = await wallet
    .transfer(predicate.address, 1, TOKENS_BY_SYMBOL.ETH.assetId, { gasPrice: 1 })
    .catch((e) => console.error(`feeTx ${e}`));
  await feeTx?.waitForResult();

  const predicateBalances = await predicate.getBalances();
  console.log(
    "predicateBalances",
    predicateBalances.map((v) => ({
      amount: v.amount.toString(),
      assetId: v.assetId.toString(),
    }))
  );
  //---------------------
  const cancelTx = await wallet.transfer(predicate.address, 0, token0.assetId, {
    gasPrice: 1,
  });
  await cancelTx.waitForResult();

  const finalPredicateBalance = await predicate.getBalances();
  console.log(
    "finalPredicateBalance",
    finalPredicateBalance.map((v) => v.amount.toString())
  );
})();

But this test fails with this error

/Users/alexey/projects/spark-ts-sdk/node_modules/graphql-request/src/index.ts:441
    throw new ClientError(
          ^
ClientError: TransactionOutputCoinAssetIdNotFound(56fb8789a590ea9c12af6fe6dc2b43f347700b049d4f823fd4476c6f366af201): {"response":{"data":null,"errors":[{"message":"TransactionOutputCoinAssetIdNotFound(56fb8789a590ea9c12af6fe6dc2b43f347700b049d4f823fd4476c6f366af201)","locations":[{"line":2,"column":3}],"path":["dryRun"]}],"status":200,"headers":{}},"request":{"query":"mutation dryRun($encodedTransaction: HexString!, $utxoValidation: Boolean) {\n  dryRun(tx: $encodedTransaction, utxoValidation: $utxoValidation) {\n    ...receiptFragment\n  }\n}\n\nfragment receiptFragment on Receipt {\n  data\n  rawPayload\n}","variables":{"encodedTransaction":"0x000000000000000000000000000000010000000005f5e1000000000000000000000000000000000400000000000000000000000000000001000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000009835cda5c81ed1492fe4dc3696a7b39b6e94ac2c3ba4f739bafc69562061b31700000000000000042ce05bde9ada2d2c58b5eb9f08be34df19375d618626c324f75dfdbd226c8d8800000000000004300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057fe0ee890797038951426b07a7c0f16d135635e0bc27cae1461aae80ca6b7e6000000000000000056fb8789a590ea9c12af6fe6dc2b43f347700b049d4f823fd4476c6f366af20100000000000000032ce05bde9ada2d2c58b5eb9f08be34df19375d618626c324f75dfdbd226c8d88000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","utxoValidation":false}}}
    at makeRequest (/Users/alexey/projects/spark-ts-sdk/node_modules/graphql-request/src/index.ts:441:11)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async H.estimateTxDependencies (/Users/alexey/projects/spark-ts-sdk/node_modules/@fuel-ts/providers/src/provider.ts:387:39)
    at async n.sendTransaction (/Users/alexey/projects/spark-ts-sdk/node_modules/@fuel-ts/wallet/src/base-unlocked-wallet.ts:85:5) {
  response: {
    data: null,
    errors: [ [Object] ],
    status: 200,
    headers: Headers { [Symbol(map)]: [Object: null prototype] }
  },
  request: {
    query: 'mutation dryRun($encodedTransaction: HexString!, $utxoValidation: Boolean) {\n' +
      '  dryRun(tx: $encodedTransaction, utxoValidation: $utxoValidation) {\n' +
      '    ...receiptFragment\n' +
      '  }\n' +
      '}\n' +
      '\n' +
      'fragment receiptFragment on Receipt {\n' +
      '  data\n' +
      '  rawPayload\n' +
      '}',
    variables: {
      encodedTransaction: '0x000000000000000000000000000000010000000005f5e1000000000000000000000000000000000400000000000000000000000000000001000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000009835cda5c81ed1492fe4dc3696a7b39b6e94ac2c3ba4f739bafc69562061b31700000000000000042ce05bde9ada2d2c58b5eb9f08be34df19375d618626c324f75dfdbd226c8d8800000000000004300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057fe0ee890797038951426b07a7c0f16d135635e0bc27cae1461aae80ca6b7e6000000000000000056fb8789a590ea9c12af6fe6dc2b43f347700b049d4f823fd4476c6f366af20100000000000000032ce05bde9ada2d2c58b5eb9f08be34df19375d618626c324f75dfdbd226c8d88000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
      utxoValidation: false
    }
  }
}

3 Likes

It’s also an unresolved issue on our end even though the AssetId is in the list of outputs and has the correct type. Would love to see some feedback on this!

Thankyou for the input both and for raising an issue @fuel , let’s continue the discussion there and I can post outcomes here also :slight_smile:

1 Like