概要

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

概要
この概要の段落は改行するにはbrタグが必要
approve
とallowance
の仕組み
ERC20トークン規格におけるapprove
とallowance
は、あるユーザー(所有者)が、別のユーザーやスマートコントラクト(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トークンのapprove
とallowance
を呼び出す方法を解説します。
事前準備
- 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
- 必要なパッケージのインストール:Bash
go 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{}
を渡します。