> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canton.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Transfer Types

> Two-step vs one-step transfers / pre-approvals, accepting/rejecting/withdrawing two-step transfers, and transfer pre-approvals.

## Two-step transfer vs one-step transfer

The default behavior for all tokens are a two-step transfer, this matches how funds are usually transferred in TradFi, however this is counter-intuitive in the blockchain world. Canton Coin supports setting up a "Transfer Pre-approval", this allows a party to designate that he wants to auto-accept all incoming transfer, giving a similar behavior of the blockchain world.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { SDK, localNetStaticConfig } from '@canton-network/wallet-sdk'

export default async function () {
    // it is important to configure the SDK correctly else you might run into connectivity or authentication issues
    const sdk = await SDK.create({
        auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT,
        ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
        amulet: global.AMULET_NAMESPACE_CONFIG,
    })

    const myParty = global.EXISTING_PARTY_1
    const myPrivateKey = global.EXISTING_PARTY_1_KEYS.privateKey

    const createPreapprovalCommand =
        await sdk.amulet.preapproval.command.create({
            parties: {
                receiver: myParty,
            },
        })

    await sdk.ledger
        .prepare({
            partyId: myParty,
            commands: createPreapprovalCommand,
        })
        .sign(myPrivateKey)
        .execute({
            partyId: myParty,
        })
}
```

## Accepting or rejecting a two-step transfer

If no Transfer pre-approval have been set up, then it is required to fetch incoming transfer instructions and consume either the `Accept` or `Reject` choice, this can be done easily using the Wallet SDK.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { SDK, localNetStaticConfig } from '@canton-network/wallet-sdk'

export default async function () {
    const sdk = await SDK.create({
        auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT,
        ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
        token: global.TOKEN_NAMESPACE_CONFIG,
    })
    const myParty = global.EXISTING_PARTY_1

    //this returns a list of all transfer instructions, you can then accept or reject them
    await sdk.token.transfer.pending(myParty)
}
```

the above give a list of pending transfer instructions, you can then exercise the accept or reject choice on them:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { SDK, localNetStaticConfig } from '@canton-network/wallet-sdk'

export default async function () {
    const sdk = await SDK.create({
        auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT,
        ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
        token: global.TOKEN_NAMESPACE_CONFIG,
    })
    const myParty = global.EXISTING_PARTY_2
    const myPrivateKey = global.EXISTING_PARTY_2_KEYS.privateKey
    const Reject = true

    const myPendingTransaction = await sdk.token.transfer.pending(myParty)

    const myPendingTransactionCid = myPendingTransaction[0].contractId
    if (Reject) {
        //reject the transaction
        const [rejectTransferCommand, disclosedContracts] =
            await sdk.token.transfer.reject({
                transferInstructionCid: myPendingTransactionCid,
                registryUrl: localNetStaticConfig.LOCALNET_REGISTRY_API_URL,
            })

        await sdk.ledger
            .prepare({
                partyId: myParty,
                commands: rejectTransferCommand,
                disclosedContracts: disclosedContracts,
            })
            .sign(myPrivateKey)
            .execute({ partyId: myParty })
    } else {
        //accept the transaction
        const [acceptTransferCommand, disclosedContracts] =
            await sdk.token.transfer.accept({
                transferInstructionCid: myPendingTransactionCid,
                registryUrl: localNetStaticConfig.LOCALNET_REGISTRY_API_URL,
            })

        await sdk.ledger
            .prepare({
                partyId: myParty,
                commands: acceptTransferCommand,
                disclosedContracts: disclosedContracts,
            })
            .sign(myPrivateKey)
            .execute({ partyId: myParty })
    }
}
```

## Withdrawing a two-step transfer before it gets accepted

Apart from accepting or rejecting a transfer instruction, it is also possible for the sender to `withdraw` the offer, thereby retrieving the locked funds.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { SDK, localNetStaticConfig } from '@canton-network/wallet-sdk'

export default async function () {
    const sdk = await SDK.create({
        auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT,
        ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
        token: global.TOKEN_NAMESPACE_CONFIG,
    })
    const myParty = global.EXISTING_PARTY_1
    const myPrivateKey = global.EXISTING_PARTY_1_KEYS.privateKey

    const myPendingTransaction = await sdk.token.transfer.pending(myParty)

    const myPendingTransactionCid = myPendingTransaction[0].contractId

    const [withdrawTransferCommand, disclosedContracts] =
        await sdk.token.transfer.withdraw({
            transferInstructionCid: myPendingTransactionCid,
            registryUrl: localNetStaticConfig.LOCALNET_REGISTRY_API_URL,
        })

    await sdk.ledger
        .prepare({
            partyId: myParty,
            commands: withdrawTransferCommand,
            disclosedContracts: disclosedContracts,
        })
        .sign(myPrivateKey)
        .execute({ partyId: myParty })
}
```

## How do I quickly setup transfer preapproval?

It is worth nothing that using the validator operator party as the providing party causes the transfer pre-approval to auto-renew. The below script setup transfer preapproval for Bob and performs a one-step transfer from Alice to Bob:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Holding, PrettyContract } from '@canton-network/core-tx-parser'
import { localNetStaticConfig, SDK } from '@canton-network/wallet-sdk'
import { pino } from 'pino'
import {
    TOKEN_NAMESPACE_CONFIG,
    TOKEN_PROVIDER_CONFIG_DEFAULT,
    AMULET_NAMESPACE_CONFIG,
} from './utils/index.js'

const logger = pino({ name: 'v1-05-preapproval', level: 'info' })

const sdk = await SDK.create({
    auth: TOKEN_PROVIDER_CONFIG_DEFAULT,
    ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
    token: TOKEN_NAMESPACE_CONFIG,
    amulet: AMULET_NAMESPACE_CONFIG,
})

await sdk.amulet.tapInternal('1000')

const aliceKeys = sdk.keys.generate()

const alice = await sdk.party.external
    .create(aliceKeys.publicKey, {
        partyHint: 'v1-05-alice',
    })
    .sign(aliceKeys.privateKey)
    .execute()

const [amuletTapCommand, amuletTapDisclosedContracts] = await sdk.amulet.tap(
    alice.partyId,
    '10000'
)

await sdk.ledger
    .prepare({
        partyId: alice.partyId,
        commands: amuletTapCommand,
        disclosedContracts: amuletTapDisclosedContracts,
    })
    .sign(aliceKeys.privateKey)
    .execute({ partyId: alice.partyId })

const bobKeys = sdk.keys.generate()

const bob = await sdk.party.external
    .create(bobKeys.publicKey, {
        partyHint: 'v1-05-bob',
    })
    .sign(bobKeys.privateKey)
    .execute()

// --- TEST CREATE COMMAND

const createPreapprovalCommand = await sdk.amulet.preapproval.command.create({
    parties: {
        receiver: bob.partyId,
    },
})

logger.info(
    { createPreapprovalCommand },
    'Successfully created a preapproval command'
)

await sdk.ledger
    .prepare({
        partyId: bob.partyId,
        commands: createPreapprovalCommand,
    })
    .sign(bobKeys.privateKey)
    .execute({
        partyId: bob.partyId,
    })

logger.info('Successfully registered the preapproval.')

// --- TEST FETCH

const start = performance.now()
const fetchOnceStatus = await sdk.amulet.preapproval.fetchQuick(bob.partyId)
const end = performance.now()

const duration = end - start
if (duration < 1000) {
    logger.info(
        `Success! The operation was fast (${duration.toFixed(2)} ms) and fetchOnce status is ${fetchOnceStatus}.`
    )
} else {
    logger.warn(
        `Warning: Operation took longer than 1 second (${(duration / 1000).toFixed(2)} s).`
    )
}

logger.info('Fetching for preapproval status with retry')

const fetchedPreapprovalStatus = await sdk.amulet.preapproval.fetchStatus(
    bob.partyId
)

logger.info({ fetchedPreapprovalStatus }, 'Fetched preapproval status')

const sentValue = 2000

const [transferCommand, transferDisclosedContracts] =
    await sdk.token.transfer.create({
        sender: alice.partyId,
        recipient: bob.partyId,
        amount: sentValue.toString(),
        instrumentId: 'Amulet',
        registryUrl: localNetStaticConfig.LOCALNET_REGISTRY_API_URL,
    })

await sdk.ledger
    .prepare({
        partyId: alice.partyId,
        commands: transferCommand,
        disclosedContracts: transferDisclosedContracts,
    })
    .sign(aliceKeys.privateKey)
    .execute({ partyId: alice.partyId })

logger.info({ sentValue }, 'Executed transfer from Alice to Bob with value:')

const aliceUtxos = await sdk.token.utxos.list({ partyId: alice.partyId })
const bobUtxos = await sdk.token.utxos.list({ partyId: bob.partyId })

const partyAmuletValue = (utxos: PrettyContract<Holding>[]) =>
    utxos.reduce(
        (acc, utxo) => acc + parseFloat(utxo.interfaceViewValue.amount),
        0
    )
const aliceAmuletValue = partyAmuletValue(aliceUtxos)
const bobAmuletValue = partyAmuletValue(bobUtxos)

if (aliceAmuletValue !== 8000 || bobAmuletValue !== 2000)
    throw Error(
        `Wrong end results for utxos: ${JSON.stringify({ aliceAmuletValue, bobAmuletValue })}`
    )

logger.info({ aliceAmuletValue, bobAmuletValue }, 'Result:')

// --- TEST RENEW COMMAND

logger.info('Renewing preapproval...')

const start2 = performance.now()
const fetchOnceStatusWithPreapproval = await sdk.amulet.preapproval.fetchQuick(
    bob.partyId
)
const end2 = performance.now()

const duration2 = end2 - start2
if (duration < 1000) {
    logger.info(
        `Success! The operation was fast (${duration2.toFixed(2)} ms) and fetchOnce status is ${fetchOnceStatusWithPreapproval}.`
    )
} else {
    logger.warn(
        `Warning: Operation took longer than 1 second (${duration2.toFixed(2)} s).`
    )
}

const newExpiresAt = new Date(fetchedPreapprovalStatus!.expiresAt)
newExpiresAt.setDate(newExpiresAt.getDate() + 2)

await sdk.amulet.preapproval.renew({
    parties: {
        receiver: bob.partyId,
    },
    expiresAt: newExpiresAt,
})

const fetchedStatusAfterRenew = await sdk.amulet.preapproval.fetchStatus(
    bob.partyId,
    {
        oldCid: fetchedPreapprovalStatus!.contractId,
    }
)

const before = fetchedPreapprovalStatus!.expiresAt
const after = fetchedStatusAfterRenew!.expiresAt

if (!(after.getTime() > before.getTime())) {
    throw new Error(
        `Expected expiresAt to increase after renewal. before=${fetchedPreapprovalStatus!.expiresAt.toISOString()} after=${fetchedStatusAfterRenew!.expiresAt.toISOString()}`
    )
}

logger.info(
    {
        before: before.toISOString(),
        after: after.toISOString(),
        extendedSeconds: Math.round(
            (after.getTime() - before.getTime()) / 1000
        ),
    },
    'TransferPreapproval expiry extended, managed to renew preapproval'
)

// --- TEST CANCEL COMMAND
logger.info('Testing out cancel command')

if (!fetchedStatusAfterRenew?.templateId) {
    throw new Error('No preapproval found - fetchedPreapprovalStatus is null')
}
const [cancelPreapprovalCommand, cancelDisclosedContracts] =
    await sdk.amulet.preapproval.command.cancel({
        parties: {
            receiver: bob.partyId,
        },
    })

if (!cancelPreapprovalCommand) {
    throw Error(
        'Cancel preapproval command is null even though one has been created before'
    )
}

await sdk.ledger
    .prepare({
        partyId: bob.partyId,
        commands: cancelPreapprovalCommand,
        disclosedContracts: cancelDisclosedContracts,
    })
    .sign(bobKeys.privateKey)
    .execute({
        partyId: bob.partyId,
    })

logger.info('Submitted cancel command; now polling')
const cancelled = await sdk.amulet.preapproval.fetchStatus(bob.partyId, {
    cancelled: true,
})

const preapprovalACS = await sdk.ledger.acsReader.readJsContracts({
    parties: [bob.partyId],
    filterByParty: true,
})

const renewedPreapprovalStillActive = preapprovalACS.some(
    (contract) => contract.contractId === fetchedStatusAfterRenew?.contractId
)

if (cancelled === null && !renewedPreapprovalStillActive) {
    logger.info(`Successfully cancelled`)
}
```

## How to renew or cancel a transfer preapproval

If you have used the validator operator party as the provider, then it will automatically renew the transfer preapproval approximately 20 days before expiry, however there are cases where you would like to perform the preapproval renewal manually:

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
await amulet.preapproval.renew({
    parties: {
        receiver: myPartyId,
    },
    expiresAt: newExpiresAt,
})
```

You can also deploy a secondary transfer preapproval, however this means that there are simply two preapprovals instead of it replacing the existing.

If you have accidentally created a transfer preapproval that you dont want to keep you can perform a cancel instead:

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const [cancelPreapprovalCommand, cancelDisclosedContracts] =
await amulet.preapproval.command.cancel({
    parties: {
        receiver: myPartyId,
    },
})
await sdk.ledger
    .prepare({
        partyId: myPartyId,
        commands: cancelPreapprovalCommand,
        disclosedContracts: cancelDisclosedContracts,
    })
    .sign(myPrivateKey)
    .execute({
        partyId: myPartyId,
    })
```
