Skip to content

Settlement

SIP-37 in the Achernar release introduced Fee Reclamation into Synthetix. This means, following all exchanges into a synth, a waiting period must expire before subsequent exchanges out of that synth can be processed.

Once the waiting period expires, settlement is performed automatically during subsequent exchanges. However, subsequent transfer attempts will always fail if there is not sufficient balance after settlement, hence transferAndSettle must be used.

Transfer and settle

It was decided during SIP-37 to not automatically settle within transfers. The reason being is that to settle within a transfer may break ERC20 conventions because the amount provided as a parameter might not be the amount emitted via Transfer due to fees owing or owed.

Settlement API

There are a number of different ways to settle synths explicitly:

  1. via Synthetix.settle(bytes32 synth) where the user is msg.sender
  2. via Exchanger.settle(address user, bytes32 synth) where the user is provided (and msg.sender pays the gas)
  3. via Synth<key>.transferAndSettle(address to, uint value) where the user msg.sender
  4. via Synth<key>.transferFromAndSettle() where the user is provided (and msg.sender has been ERC20 approved to transferFrom)

1. User settles for themselves

Destination contract (address): ProxyERC20

Target contract (ABI): Synthetix

Note: Synthetix uses a proxy system. The ABI of the underlying Synthetix ProxyERC20 contract you need is Synthetix. Learn more about how proxies work by visiting the overview page.

Methods

Examples on Mainnet

  • Exchanger.settle(iBTC)

2. Anyone can settle on behalf of a user

Contract (address & ABI): Exchanger (this address is subject to change in subsequent releases)

Methods

Examples on Mainnet

  • Exchanger.settle(sUSD)

3. User transfers and settles

Destination contract (address): Proxy<key> where key is the synth key (e.g. sUSD, iETH, etc)

Target contract (ABI): Synth<key

Methods

Examples on Mainnet

  • ProxyERC20sUSD.transferAndSettle(0x1cd919, 1e18)

4. Approved contract transfers and settles for a user

Destination contract (address): Proxy<key> where key is the synth key (e.g. sUSD, iETH, etc)

Target contract (ABI): Synth<key

Methods

Events Emitted

If fees owing on src (fee reclamation)

name emitted on address from address to uint value
Transfer Proxy<src> msg.sender (or user) 0x0 feesOwing
name emitted on address account uint value
Burned Proxy<src> msg.sender (or user) feesOwing

Else if fees owed on src (fee rebate)

name emitted on address from address to uint value
Transfer Proxy<src> 0x0 msg.sender (or user) feesOwed
name emitted on address account uint value
Issued Proxy<src> msg.sender (or user) feesOwed

And following those

name emitted on uint cachedDebt
DebtCacheUpdated DebtCache New cachedDebt in the system (see SIP-91)
name emitted on address account bytes32 src uint amount bytes32 dest uint reclaim uint rebate uint srcRoundIdAtPeriodEnd uint destRoundIdAtPeriodEnd uint exchangeTimestamp
ExchangeEntrySettled Exchanger msg.sender
(or user)
src fromAmount dest reclaimedAmount if any rebateAmount if any the roundId for rate of src synth at waiting period expiry the roundId for rate of dest synth at waiting period expiry the timestamp of the exchange

Code Snippets

Settlement

 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
const { SynthetixJs } = require('synthetix-js');
const privateKey = '0x' + '1'.repeat(64); // don't actually put a private key in code obviously

// parameters: default provider, default networkId, private key as a string
const networkId = 11155111; // sepolia, (use 1 for mainnet)
const signer = new SynthetixJs.signers.PrivateKey(null, networkId, privateKey);
const snxjs = new SynthetixJs({ signer, networkId });

const { toUtf8Bytes32, parseEther } = snxjs.utils;

(async () => {
  try {
    // send transaction
    const txn = await snxjs.Synthetix.settle(toUtf8Bytes32('iETH'));

    console.log('hash is mining', txn.hash);

    // wait for mining
    await txn.wait();

    // fetch logs of transaction
    const { logs } = await signer.provider.getTransactionReceipt(txn.hash);

    // show them
    console.log(JSON.stringify(logs, null, '\t'));
  } catch (err) {
    console.log('Error', err);
  }
})();
 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
const synthetix = require('synthetix'); // nodejs
const ethers = require('ethers'); // nodejs
// or using ES modules:
// import synthetix from 'synthetix';
// import ethers from 'ethers';

const network = 'sepolia';
const provider = ethers.getDefaultProvider(network === 'mainnet' ? 'homestead' : network);

const { address } = synthetix.getTarget({ network, contract: 'ProxyERC20' });
const { abi } = synthetix.getSource({ network, contract: 'Synthetix' });

const privateKey = '0x' + '1'.repeat(64); // don't actually put a private key in code obviously
const signer = new ethers.Wallet(privateKey).connect(provider);

// see https://docs.ethers.io/ethers.js/html/api-contract.html#connecting-to-existing-contracts
const Synthetix = new ethers.Contract(address, abi, signer);
const { toBytes32 } = synthetix;

(async () => {
  try {
    // send transaction
    const txn = await Synthetix.settle(toBytes32('iETH'));

    // wait for mining
    await txn.wait();
    // fetch logs of transaction
    const { logs } = await provider.getTransactionReceipt(txn.hash);
    // display
    console.log(JSON.stringify(logs, null, '\t'));
  } catch (err) {
    console.log('Error', err);
  }
})();
 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
48
49
50
51
52
53
54
55
56
57
58
59
pragma solidity 0.5.16;

import "synthetix/contracts/interfaces/IAddressResolver.sol";
import "synthetix/contracts/interfaces/ISynthetix.sol";
import "synthetix/contracts/interfaces/IExchanger.sol";


contract MyContract {

    // This should be instantiated with our ReadProxyAddressResolver
    // it's a ReadProxy that won't change, so safe to code it here without a setter
    // see https://docs.synthetix.io/addresses for addresses in mainnet and testnets
    IAddressResolver public synthetixResolver;

    constructor(IAddressResolver _snxResolver) public {
        synthetixResolver = _snxResolver;
    }

    function synthetixSettle(bytes32 synthKey) external {)
      ISynthetix synthetix = synthetixResolver.getAddress("Synthetix");
      require(synthetix != address(0), "Synthetix is missing from Synthetix resolver");

      // This check is what synthetix.exchange() will perform, added here for explicitness
      require(!synthetix.isWaitingPeriod(synthKey), "Cannot settle during the waiting period");

      // Settle for msg.sender = address(MyContract)
      synthetix.settle(synthKey);

    }

    function synthetixSettleOnBehalf(address user, bytes32 synthKey) external {
        IExchanger exchanger = synthetixResolver.getAddress("Exchanger");
        require(exchanger != address(0), "Exchanger is missing from Synthetix resolver");

        // This check is what exchanger.settle() will perform, added here for explicitness
        require(exchanger.maxSecsLeftInWaitingPeriod(user, synthKey) == 0, "Cannot settle during the waiting period");

        // This function has no msg.sender restriction - any address can call it (they'll just have to pay the gas on behalf of the user)
        exchanger.settle(user, synthKey)
    }

    function synthetixTransferAndSettle(bytes32 synthKey, address to, uint value) extenrnal {
        // Note ⚠️: IAddressResolver.getSynth will not work until the Altair release (v2.22) of Synthetix
        ISynth synth = synthetixResolver.getSynth(synthKey);
        require(synth != address(0), "Synth is missing from Synthetix");

        synth.transferAndSettle(to, value);
    }


    function synthetixTransferFromAndSettle(bytes32 synthKey, address from, address to, uint value) extenrnal {
        // Note ⚠️: IAddressResolver.getSynth will not work until the Altair release (v2.22) of Synthetix
        ISynth synth = synthetixResolver.getSynth(synthKey);
        require(synth != address(0), "Synth is missing from Synthetix");

        // Note: only works if user has invoked ERC20.approve(address(MyContract)) on the given synth
        synth.transferFromAndSettle(from, to, value);
    }
}