Solidityのallowanceとapprove 徹底解説

Solidityのallowanceとapprove 徹底解説

概要

SolidityのERC20トークン標準におけるapprove()とallowance()は、トークンの委任管理に不可欠な機能です。**approve(spender, amount)**関数は、トークン所有者(owner)が指定した第三者(spender)に対し、自身のトークンをamount分まで引き出すことを許可します。これにより、DEX(分散型取引所)やDeFiプロトコルなどのスマートコントラクトが、ユーザーの資金を直接操作することなく、安全に取引やサービスを実行できるようになります。

概要

概要

この概要の段落は改行するにはbrタグが必要

approveallowanceの仕組み

ERC20トークン規格におけるapproveallowanceは、あるユーザー(所有者)が、別のユーザーやスマートコントラクト(Spender: 消費者)に対して、自分のトークンを代理で引き出すことを許可するための非常に重要な機能です。

これは、DEX(分散型取引所)などでトークンを交換する際に、ユーザーが秘密鍵をDEXに渡すことなく、安全に取引を行うために不可欠な仕組みです。

  • approve(spender, amount)
    • 目的: トークンの所有者が呼び出します。
    • 機能: spender(通常はスマートコントラクトのアドレス)が、所有者のウォレットからamountで指定した数量までのトークンを引き出すことを**許可(Approve)**します。
    • これは許可を与えるだけで、実際にはトークンの移動はまだ発生しません。
  • allowance(owner, spender)
    • 目的: 誰でも呼び出すことができます。
    • 機能: owner(所有者)がspender(消費者)に対して、あとどれくらいの量のトークンを引き出すことを許可しているか(許容量: Allowance)を確認します。
  • transferFrom(sender, recipient, amount)
    • 目的: spender(消費者)が呼び出します。
    • 機能: approveで得た許可を元に、sender(所有者)のウォレットからrecipient(受取人)のウォレットへamount分のトークンを実際に移動させます。この時、allowanceで設定された許容量がamount以上でなければトランザクションは失敗します。

Go/go-ethereumによる実装例

ここでは、go-ethereumライブラリを使用してERC20トークンのapproveallowanceを呼び出す方法を解説します。

事前準備

  1. Goバインディングの生成: まず、操作したいERC20トークンのABI(Application Binary Interface)ファイルを用意します。go-ethereumに付属するabigenというツールを使って、ABIからGoのコード(バインディング)を生成します。Bash# abigen --abi <ABIファイルへのパス> --pkg <パッケージ名> --type <コントラクト名> --out <出力ファイル名.go> abigen --abi erc20.abi --pkg main --type Token --out token.go
  2. 必要なパッケージのインストール:Bashgo get github.com/ethereum/go-ethereum

Goによる実装コード

以下のコードは、指定したspenderアドレスに対して一定量のトークンをapproveし、その後allowanceで許可量を確認するサンプルです。

Go

package main

import (
	"context"
	"crypto/ecdsa"
	"fmt"
	"log"
	"math/big"

	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.comcom/ethereum/go-ethereum/ethclient"
)

// abigenで生成したTokenバインディングをインポートしている想定
// import "your-project/token"

func main() {
	// 1. イーサリアムノードへの接続
	client, err := ethclient.Dial("https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID")
	if err != nil {
		log.Fatalf("Failed to connect to the Ethereum client: %v", err)
	}
	defer client.Close()

	// 2. アカウント情報の準備
	privateKeyHex := "YOUR_PRIVATE_KEY" // 実際には環境変数などから安全に読み込むこと
	privateKey, err := crypto.HexToECDSA(privateKeyHex)
	if err != nil {
		log.Fatalf("Failed to parse private key: %v", err)
	}

	publicKey := privateKey.Public()
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		log.Fatal("Cannot assert type: publicKey is not of type *ecdsa.PublicKey")
	}

	ownerAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

	// 3. トークンコントラクトのインスタンス化
	tokenAddress := common.HexToAddress("0xYOUR_ERC20_TOKEN_CONTRACT_ADDRESS") // 操作したいトークンのアドレス
	// abigenで生成した`NewToken`関数を使用
	tokenInstance, err := NewToken(tokenAddress, client)
	if err != nil {
		log.Fatalf("Failed to instantiate a Token contract: %v", err)
	}

	// 4. トランザクション署名用のAuthオブジェクトを作成
	chainID, err := client.ChainID(context.Background())
	if err != nil {
		log.Fatalf("Failed to get chainID: %v", err)
	}

	auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
	if err != nil {
		log.Fatalf("Failed to create authorized transactor: %v", err)
	}

	// === approveの実行 ===
	// 5. 許可を与える相手(Spender)と数量を設定
	spenderAddress := common.HexToAddress("0xSPENDER_ADDRESS") // DEXのコントラクトアドレスなど
	// 100トークンを許可 (トークンのdecimalsに応じて調整)
	// 例: 18桁のトークンなら 100 * 10^18
	amount := new(big.Int)
	amount.SetString("100000000000000000000", 10) // 100 Tokens

	fmt.Printf("Approving %s to spend %s tokens on behalf of %s\n", spenderAddress.Hex(), amount.String(), ownerAddress.Hex())

	// 6. `approve`トランザクションを送信
	tx, err := tokenInstance.Approve(auth, spenderAddress, amount)
	if err != nil {
		log.Fatalf("Failed to send approve transaction: %v", err)
	}
	fmt.Printf("Approve transaction sent: %s\n", tx.Hash().Hex())

	// トランザクションが承認されるのを待つ
	receipt, err := bind.WaitMined(context.Background(), client, tx)
	if err != nil {
		log.Fatalf("Failed to mine transaction: %v", err)
	}
	fmt.Printf("Transaction mined in block: %d\n", receipt.BlockNumber)


	// === allowanceの確認 ===
	fmt.Println("\nChecking allowance...")
	
	// 7. `allowance`を呼び出して許可量を確認
	// `allowance`は状態を変更しない読み取り専用の呼び出しなので、`CallOpts`を使用(`auth`は不要)
	allowanceAmount, err := tokenInstance.Allowance(&bind.CallOpts{}, ownerAddress, spenderAddress)
	if err != nil {
		log.Fatalf("Failed to retrieve allowance: %v", err)
	}

	fmt.Printf("Allowance for %s to spend on behalf of %s is: %s\n", spenderAddress.Hex(), ownerAddress.Hex(), allowanceAmount.String())
}

コードのポイント解説

big.Int: イーサリアムのトークン量は非常に大きな数になる可能性があるため、Goの標準のint型ではなく、math/bigパッケージの*big.Int型で扱います。トークンのdecimals(小数点以下の桁数)も考慮して値を設定する必要があります。

abigenの利用: Solidityの関数(approve, allowanceなど)がGoのメソッドとしてラップされ、型安全にスマートコントラクトを操作できます。これにより、手動でABIを操作する手間が省け、コードがクリーンになります。

認証情報(auth): approveのような状態を変更するトランザクションには、ガス代の支払いや署名が必要です。bind.NewKeyedTransactorWithChainIDは、秘密鍵を使ってトランザクションに署名するためのTransactOptsオブジェクトを生成します。

CallOptsの使用: allowanceのようなブロックチェーンの状態を読み取るだけの「コール」には、署名やガス代は不要です。そのため、&bind.CallOpts{}を渡します。

-EVM