본문으로 건너뛰기

대체 가능한 토큰 전송

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

Core 튜토리얼의 완성된 코드를 보고 싶다면, 5.transfers 브랜치에서 찾을 수 있습니다. :::

소개

지금까지 소유자가 총 토큰 공급량을 발행하고, 대체 가능한 토큰(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로 전송합니다.

5.transfers/src/ft_core.rs
loading...

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:

5.transfers/src/internal.rs
loading...

이 경우 컨트랙트는 계정의 잔고를 가져오고 internal_unwrap_balance_of 함수를 호출하여 등록되었는지 확인합니다. 그런 다음 사용자의 잔고에서 금액을 빼고 맵에 다시 삽입합니다.

internal_depositinternal_withdraw 함수를 함께 사용하면, internal_transfer 함수의 핵심이 완성됩니다.

당신이 해야 할 일이 하나 더 있습니다. 토큰을 전송할 때 이벤트 표준에 따라 로그를 내보내야 한다는 것을 기억해야 합니다.

5.transfers/src/internal.rs
loading...

이것으로 간단하게 전송 케이스가 완성되었습니다! 이제 등록된 사용자 간에 FT를 전송할 수 있습니다!

전송 호출 함수

이 섹션에서는 새 함수 ft_transfer_call에 대해 알아봅니다. 이렇게 하면 FT가 수신자에게 전송되고, 동일한 트랜잭션에서 수신자의 컨트랙트에 있는 메서드도 호출됩니다.

다음 시나리오를 고려해 봅시다. 계정은 서비스 수행을 위해 FT를 스마트 컨트랙트로 전송하려고 합니다. 전통적인 접근 방식은 서비스를 수행한 다음 두 개의 개별 트랜잭션에서 토큰을 요청하는 것입니다. 단일 트랜잭션에 "전송 및 호출" 워크플로우를 도입하면 프로세스가 크게 개선될 수 있습니다.

5.transfers/src/ft_core.rs
loading...

이 함수는 다음과 같은 몇 가지 작업을 수행합니다.

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

이는 교차 컨트랙트 호출을 처리할 때 매우 일반적인 워크플로우입니다. 먼저 호출을 시작하고 실행이 완료될 때까지 기다립니다. 그런 다음 약속의 결과를 해결하는 함수를 호출하고, 그에 따라 조치를 취합니다.

Learn more here. :::

ft_on_transfer를 호출하면, 컨트랙트에서 원래 보낸 사람에게 환불해야 하는 토큰 수를 반환합니다.

이는 몇 가지 이유로 중요합니다.

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

이 로직은 모두 ft_resolve_transfer 함수에서 발생합니다

5.transfers/src/ft_core.rs
loading...

가장 먼저 할 일은 Promise의 상태를 확인하는 것입니다. 실패한 경우 발신자에게 전체 토큰을 환불합니다. Promise가 성공하면, ft_on_transfer에서 반환된 값을 기준으로 발신자에게 환불할 토큰의 양을 추출합니다. 환불에 필요한 금액이 확보되면 이전에 작성한 internal_transfer 함수를 사용하여 실제 환불 로직을 수행합니다.

그런 다음 수신자에게 전송된 순 토큰 양을 반환합니다. 보낸 사람이 100개의 토큰을 전송했지만 20개가 환불된 경우 이 함수는 80을 반환해야 합니다.

이 작업이 완료되면, 이제 사용자가 FT를 전송할 수 있도록 필요한 로직을 성공적으로 추가한 것입니다. 이제 배포하고 몇 가지 테스트를 수행할 시간입니다.

컨트랙트 배포

컨트랙트를 배포할 새로운 하위 계정을 생성해 봅시다. 이러한 변경 사항은 추가적이며, 상태가 깨지지 않기 때문에 스토리지 섹션에 배포한 컨트랙트에도 패치 수정을 배포할 수 있습니다. 컨트랙트 업그레이드에 대한 자세한 내용은 NFT Zero to Hero 튜토리얼의 컨트랙트 업그레이드 섹션을 참조하세요.

하위 계정(sub-account) 생성

다음 명령을 실행하여 초기 잔액이 25 NEAR인 하위 계정 transfer을 만듭니다.

near create-account transfer.$FT_CONTRACT_ID --masterAccount $FT_CONTRACT_ID --initialBalance 25

다음으로 개발을 쉽게 하기 위해 환경 변수를 내보낼 수 있습니다.

export TRANSFER_FT_CONTRACT_ID=transfer.$FT_CONTRACT_ID

빌드 스크립트를 사용하여 이전 튜토리얼에서와 같이 컨트랙트 배포를 빌드합니다.

cd 1.skeleton && ./build.sh && cd .. && near deploy --wasmFile out/contract.wasm --accountId $TRANSFER_FT_CONTRACT_ID

이전 튜토리얼을 완료하지 않았고 이 튜토리얼을 따라하는 경우, near login를 사용하여 계정을 만들고 CLI로 로그인하면 됩니다. 그런 다음 export TRANSFER_FT_CONTRACT_ID=YOUR_ACCOUNT_ID_HERE로 환경 변수를 내보낼 수 있습니다. 또한 5.transfers 폴더로 이동하여 컨트랙트 코드를 찾을 수 있습니다. 1.skeleton을 사용하여 빌드하는 대신, 5.transfers 폴더로 이동하여 ./build.sh를 실행하는 방식으로 빌드할 수 있습니다. :::

초기화

이제 컨트랙트가 배포되었으므로 이를 초기화하고 총 공급량을 생성할 차례입니다. 다시 한 번 초기 공급량을 1000gtNEAR로 만들어 봅시다.

near call $TRANSFER_FT_CONTRACT_ID new_default_meta '{"owner_id": "'$TRANSFER_FT_CONTRACT_ID'", "total_supply": "1000000000000000000000000000"}' --accountId $TRANSFER_FT_CONTRACT_ID

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

near view $TRANSFER_FT_CONTRACT_ID ft_balance_of '{"account_id": "'$TRANSFER_FT_CONTRACT_ID'"}'

전송 함수 테스트

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

FT는 계정 benjiman.testnet에서 당신에게 다시 전송하지 않는 한 복구할 수 없습니다. FT를 잃어버리고 싶지 않다면, 새 계정을 만들고 대신 해당 계정으로 토큰을 전송하세요. :::

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

near call $TRANSFER_FT_CONTRACT_ID storage_deposit '{"account_id": "benjiman.testnet"}' --accountId $TRANSFER_FT_CONTRACT_ID --amount 0.01

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

near call $TRANSFER_FT_CONTRACT_ID ft_transfer '{"receiver_id": "benjiman.testnet", "amount": "1000000000000000000000000", "memo": "Go Team :)"}' --accountId $TRANSFER_FT_CONTRACT_ID --depositYocto 1

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

near view $TRANSFER_FT_CONTRACT_ID ft_balance_of '{"account_id": "'$TRANSFER_FT_CONTRACT_ID'"}'
near view $TRANSFER_FT_CONTRACT_ID ft_balance_of '{"account_id": "benjiman.testnet"}'
near view $TRANSFER_FT_CONTRACT_ID ft_total_supply

전송 호출 함수 테스트

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

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

near call $TRANSFER_FT_CONTRACT_ID storage_deposit '{"account_id": "no-contract.testnet"}' --accountId $TRANSFER_FT_CONTRACT_ID --amount 0.01
near call $TRANSFER_FT_CONTRACT_ID ft_transfer_call '{"receiver_id": "no-contract.testnet", "amount": "1000000000000000000000000", "msg": "foo"}' --accountId $TRANSFER_FT_CONTRACT_ID --depositYocto 1 --gas 200000000000000

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

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 view $TRANSFER_FT_CONTRACT_ID ft_balance_of '{"account_id": "no-contract.testnet"}'

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

결론

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

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

Was this page helpful?