본문으로 건너뛰기

대체 가능한 토큰 전송

이 튜토리얼에서는 스마트 컨트랙트에 핵심 표준을 구현하는 방법을 배웁니다. 여기서는 토큰을 전송하고 수신할 수 있는 로직을 구현합니다. 처음 해보는 경우 이 레퍼지토리를 복제하고 4.storage 브랜치를 확인하세요.

If you wish to see the finished code for this Core tutorial, you can find it in the 5.transfers folder.


소개

지금까지 소유자가 총 토큰 공급량을 발행하고, 대체 가능한 토큰(FT) 자체에 대한 정보를 볼 수 있는 간단한 FT 스마트 컨트랙트를 만들었습니다. 또한 계정을 등록하고 이벤트를 내보내는 기능을 추가했습니다. 오늘은 사용자가 대체 가능한 토큰을 전송하고 받을 수 있도록 스마트 컨트랙트를 확장할 것입니다.

단순 전송을 수행하는 로직은 이해하기 매우 쉽습니다. Benji가 Mike에게 100 개의 대체 가능한 토큰을 전송하려고 한다고 가정해 보겠습니다. 컨트랙트는 다음과 같은 몇 가지 작업을 수행해야 합니다.

  • Benji가 최소 100개의 토큰을 소유하고 있는지 확인합니다.
  • Benji가 함수를 호출하는지 확인합니다.
  • Mike가 컨트랙트에 등록되어 있는지 확인합니다.
  • Benji의 계정에서 토큰 100개를 가져옵니다.
  • Mike의 계정에 100개의 토큰을 넣습니다.

이 시점에서, 스마트 컨트랙트에 필요한 수정을 할 준비가 되었습니다.


컨트랙트 수정

src/ft_core.rs 파일에서 시작하겠습니다.

전송 함수

ft_transfer 로직을 구현하는 것으로 시작합니다. 이 함수는 "Happy Birthday Mike!"와 같은 memo를 사용하여 지정된 amountreceiver_id로 전송합니다.

There are a couple things to notice here.

  1. We've introduced a new function called assert_one_yocto(). 이 메서드는 사용자가 호출에 정확히 하나의 yoctoNEAR를 연결했는지 확인합니다. 함수에 입금이 필요한 경우, 해당 트랜잭션에 서명하려면 전체 액세스 키가 필요합니다. 이는 하나의 yoctoNEAR 보증금 요구 사항을 추가함으로써, 본질적으로 사용자가 전체 액세스 키로 트랜잭션에 서명하도록 강제합니다. 전송 함수는 잠재적으로 매우 귀중한 자산을 전송하므로, 함수를 호출하는 사람이 전체 액세스 키를 가지고 있는지 확인해야 합니다.

  2. internal_transfer 메서드를 도입했습니다. 이것은 NFT를 전송하는 데 필요한 모든 로직을 수행합니다.


내부 헬퍼 함수

Let's quickly move over to the ft-contract/src/internal.rs file so that you can implement the internal_transfer method which is the core of this tutorial. This function will take the following parameters:

  • sender_id: 토큰 전송을 시도하는 계정입니다.
  • receiver_id: 토큰을 받는 계정입니다.
  • amount: 전송되는 FT 개수입니다.
  • memo: 선택적으로 포함할 수 있는 메모입니다.

The first thing you'll want to do is make sure the sender isn't sending tokens to themselves and that they're sending a positive number. After that, you'll want to withdraw the tokens from the sender's balance and deposit them into the receiver's balance. You've already written the logic to deposit FTs by using the internal_deposit function.

Let's use similar logic to implement internal_withdraw:

In this case, the contract will get the account's balance and ensure they are registered by calling the internal_unwrap_balance_of function. It will then subtract the amount from the user's balance and re-insert them into the map.

Using the internal_deposit and internal_withdraw functions together, the core of the internal_transfer function is complete.

There's only one more thing you need to do. When transferring the tokens, you need to remember to emit a log as per the events standard:

Now that this is finished, the simple transfer case is done! You can now transfer FTs between registered users!


전송 호출 함수

In this section, you'll learn about a new function ft_transfer_call. This will transfer FTs to a receiver and also call a method on the receiver's contract all in the same transaction.

Let's consider the following scenario. An account wants to transfer FTs to a smart contract for performing a service. The traditional approach would be to perform the service and then ask for the tokens in two separate transactions. If we introduce a “transfer and call” workflow baked into a single transaction, the process can be greatly improved:

This function will do several things:

  1. 호출자가 보안을 위해 정확히 1 yocto를 첨부했는지 확인합니다.
  2. 방금 작성한 internal_transfer 토큰을 사용하여 토큰을 전송합니다.
  3. receiver_id의 컨트랙트에서 ft_on_transfer 메서드를 호출하는 Promise를 만듭니다.
  4. Promise 실행이 끝나면 ft_resolve_transfer 함수가 호출됩니다.
정보

This is a very common workflow when dealing with cross contract calls. You first initiate the call and wait for it to finish executing. Then, you invoke a function that resolves the result of the promise and act accordingly. Learn more here.

When calling ft_on_transfer, it will return how many tokens the contract should refund the original sender.

This is important for a couple of reasons:

  1. 수신자에게 너무 많은 FT를 보내서, 컨트랙트에서 초과분을 환불하려는 경우입니다.
  2. 로직 패닉이 발생하면, 모든 토큰을 보낸 사람에게 다시 환불해야 합니다.

This logic will all happen in the ft_resolve_transfer function:

The first thing you'll do is check the status of the promise. If anything failed, you'll refund the sender for the full amount of tokens. If the promise succeeded, you'll extract the amount of tokens to refund the sender based on the value returned from ft_on_transfer. Once you have the amount needed to be refunded, you'll perform the actual refund logic by using the internal_transfer function you wrote previously.

You'll then return the net amount of tokens that were transferred to the receiver. If the sender transferred 100 tokens but 20 were refunded, this function should return 80.

With that finished, you've now successfully added the necessary logic to allow users to transfer FTs. It's now time to deploy and do some testing.


컨트랙트 배포

Let's create a new sub-account to deploy the contract to. Since these changes are only additive and non state breaking, you could have deployed a patch fix to the contract you deployed in the storage section as well. To learn more about upgrading contracts, see the upgrading a contract section in the NFT Zero to Hero tutorial.


하위 계정(sub-account) 생성

Run the following command to create a sub-account transfer of your main account with an initial balance of 3 NEAR which will be transferred from the original to your new account.

near account create-account fund-myself transfer.$FT_CONTRACT_ID '3 NEAR' autogenerate-new-keypair save-to-legacy-keychain sign-as $FT_CONTRACT_ID network-config testnet sign-with-legacy-keychain send

Next, you'll want to export an environment variable for ease of development:

export TRANSFER_FT_CONTRACT_ID=transfer.$FT_CONTRACT_ID

Build the contract as you did in the previous tutorials:

cd 4.storage
cargo near build

Deployment and Initialization

It's time to deploy the contract, initialize it and mint the total supply. 다시 한 번 초기 공급량을 1000gtNEAR로 만들어 봅시다.

cargo near deploy $TRANSFER_FT_CONTRACT_ID with-init-call new_default_meta json-args '{"owner_id": "'$TRANSFER_FT_CONTRACT_ID'", "total_supply": "1000000000000000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send

다음 명령을 실행하여 FT를 소유하고 있는지 확인할 수 있습니다.

near contract call-function as-read-only $TRANSFER_FT_CONTRACT_ID ft_balance_of json-args '{"account_id": "'$TRANSFER_FT_CONTRACT_ID'"}' network-config testnet now

전송 함수 테스트

Let's test the transfer function by transferring 1 gtNEAR from the owner account to the account benjiman.testnet

Keep in mind

The Fungible tokens won't be recoverable unless the account benjiman.testnet transfers them back to you. FT를 잃어버리고 싶지 않다면, 새 계정을 만들고 대신 해당 계정으로 토큰을 전송하세요.

먼저 다음 명령을 실행하여 benjiman.testnet 계정을 등록해야 합니다.

near contract call-function as-transaction $TRANSFER_FT_CONTRACT_ID storage_deposit json-args '{"account_id": "benjiman.testnet"}' prepaid-gas '100.0 Tgas' attached-deposit '0.01 NEAR' sign-as $TRANSFER_FT_CONTRACT_ID network-config testnet sign-with-legacy-keychain send

계정이 등록되면 다음 명령을 실행하여 FT를 전송할 수 있습니다. 또한 --depositYocto 플래그를 사용하여 정확히 1 yoctoNEAR를 첨부하고 있다는 점에 주의하세요.

near contract call-function as-transaction $TRANSFER_FT_CONTRACT_ID ft_transfer json-args '{"receiver_id": "benjiman.testnet", "amount": "1000000000000000000000000", "memo": "Go Team :)"}' prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' sign-as $TRANSFER_FT_CONTRACT_ID network-config testnet sign-with-legacy-keychain send

콘솔에서 FtTransferEvent가 발생하는 것을 볼 수 있습니다. 이때 총 공급량을 확인하면 여전히 1000 gtNEAR이지만, Benji의 잔액과 소유자의 잔액을 모두 확인하면 전송이 반영되어야 합니다.

  • Check owner balance:
near contract call-function as-read-only $TRANSFER_FT_CONTRACT_ID ft_balance_of json-args '{"account_id": "'$TRANSFER_FT_CONTRACT_ID'"}' network-config testnet now
  • Check benjiman.testnet balance:
near contract call-function as-read-only $TRANSFER_FT_CONTRACT_ID ft_balance_of json-args '{"account_id": "benjiman.testnet"}' network-config testnet now
  • Check total supply:
near contract call-function as-read-only $TRANSFER_FT_CONTRACT_ID ft_total_supply json-args {} network-config testnet now

전송 호출 함수 테스트

ft_transfer 함수를 테스트했으므로, 이제 ft_transfer_call 함수를 테스트할 차례입니다. ft_on_transfer 함수를 구현하지 않은 수신자에게 토큰을 전송하려고 하면, 컨트랙트가 패닉 상태가 되고, FT가 환불됩니다. 아래에서 이 기능을 테스트해 보겠습니다.

이름에서 알 수 있듯이, 컨트랙트가 없는 no-contract.testnet 계정으로 FT를 이체할 수 있습니다. 즉, 수신자는 ft_on_transfer 함수를 구현하지 않으며, 트랜잭션이 완료된 후 FT는 당신의 것으로 유지되어야 합니다. 그러나 그전에 먼저 계정을 등록해야 합니다.

near contract call-function as-transaction $TRANSFER_FT_CONTRACT_ID storage_deposit json-args '{"account_id": "no-contract.testnet"}' prepaid-gas '100.0 Tgas' attached-deposit '0.01 NEAR' sign-as $TRANSFER_FT_CONTRACT_ID network-config testnet sign-with-legacy-keychain send
near contract call-function as-transaction $TRANSFER_FT_CONTRACT_ID ft_transfer_call json-args '{"receiver_id": "no-contract.testnet", "amount": "1000000000000000000000000", "msg": "foo"}' prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' sign-as $TRANSFER_FT_CONTRACT_ID network-config testnet sign-with-legacy-keychain send

출력 응답은 다음과 같아야 합니다.

Scheduling a call: transfer.dev-1660680326316-91393402417293.ft_transfer_call({"receiver_id": "no-contract.testnet", "amount": "1000000000000000000000000", "msg": "foo"}) with attached 0.000000000000000000000001 NEAR
Doing account.functionCall()
Receipts: AJ3yWv7tSiZRLtoTkyTgfdzmQP1dpjX9DxJDiD33uwTZ, EKtpDFoJWNnbyxJ7SriAFQYX8XV9ZTzwmCF2qhSaYMAc, 21UzDZ791pWZRKAHv8WaRKN8mqDVrz8zewLWGTNZTckh
Log [transfer.dev-1660680326316-91393402417293]: EVENT_JSON:{"standard":"nep141","version":"1.0.0","event":"ft_transfer","data":[{"old_owner_id":"transfer.dev-1660680326316-91393402417293","new_owner_id":"no-contract.testnet","amount":"1000000000000000000000000"}]}
Receipt: 5N2WV8picxwUNC5TYa3A3v4qGquQAhkU6P81tgRt1UFN
Failure [transfer.dev-1660680326316-91393402417293]: Error: Cannot find contract code for account no-contract.testnet
Receipt: AdT1bSZNCu9ACq7m6ynb12GgSb3zBenfzvvzRwfYPBmg
Log [transfer.dev-1660680326316-91393402417293]: EVENT_JSON:{"standard":"nep141","version":"1.0.0","event":"ft_transfer","data":[{"old_owner_id":"no-contract.testnet","new_owner_id":"transfer.dev-1660680326316-91393402417293","amount":"1000000000000000000000000","memo":"Refund"}]}
Transaction Id 2XVy8MZU8TWreW8C9zK6HSyBsxE5hyTbyUyxNdncxL8g
To see the transaction in the transaction explorer, please open this url in your browser
https://testnet.nearblocks.io/txns/2XVy8MZU8TWreW8C9zK6HSyBsxE5hyTbyUyxNdncxL8g
'0'

토큰의 초기 전송과 환불을 위해 생성된 전송 이벤트가 있어야 합니다. 또한 모든 토큰이 환불되었으므로, 보낸 사람이 전체 0 개의 토큰을 받는 사람에게 전송했기 때문에, 함수에서 0이 반환되어야 합니다.

no-contract.testnet의 잔고를 쿼리하면 여전히 0이어야 합니다.

near contract call-function as-read-only $TRANSFER_FT_CONTRACT_ID ft_balance_of json-args '{"account_id": "no-contract.testnet"}' network-config testnet now

만세! 이 시점에서 FT 컨트랙트가 완전히 완료되고 모든 기능이 예상대로 작동합니다. 가서 실험해보세요! 세상은 당신의 것입니다. 잊지 말고 화이팅하세요!


결론

이 튜토리얼에서는 사용자가 FT를 전송하는 방법을 추가하여 FT 컨트랙트를 확장하는 방법을 배웠습니다. 문제를 더 작고 이해하기 쉬운 하위 작업으로 분류하고, 해당 정보를 가져와 FT 전송FT 전송 호출 함수를 모두 구현했습니다. 또한 다른 컨트랙트를 배포 하고 전송 기능을 테스트했습니다.

다음 튜토리얼에서는 대체 가능한 토큰을 사용하여 NFT를 구매하는 데에 있어 NFT 마켓플레이스가 작동하는 방법에 대해 알아봅니다.


Versioning for this article

At the time of this writing, this example works with the following versions:

  • rustc: 1.77.1
  • near-sdk-rs: 5.1.0 (with enabled legacy feature)
  • cargo-near: 0.6.1
  • near-cli-rs: 0.11.0
Was this page helpful?