XRPLのClawback機能を試してみる
Clawbackとは
Clawback機能はXRP Ledgerでトークンを管理する機能の一つであり、トークンの発行者がトークンを保有しているアカウントからトークンを回収する機能です。
rippledの1.12.0で導入され、執筆時点ではバリデータによる投票中となっています。
機能の詳細についてはこちらの記事で説明しています。
この記事ではコードの面からClawback機能を試してみます。
1. Clawback機能の有効化
トークンの発行者は他者がトークンを保有する前にClawback機能を有効化する必要があります。
つまり既存のトークン発行者はその発行アカウントのClawback機能を有効化することはできず、実質的に発行アカウントを別のアカウントに変更する必要があります。
これはユーザを保護するための仕様であり、機能有効化後に突然自身のトークンが回収されてしまうことを防ぐためです。
1.1 AccountSetトランザクションの送信
Clawback機能を有効化するにはAccountSet
トランザクションでSetFlag
フィールドにasfAllowTrustLineClawback
フラグを設定します。
import { AccountSetAsfFlags, Client, Wallet } from 'xrpl'
const main = async () => {
const client = new Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
const wallet = Wallet.fromSeed('sEdSV6WUEykK6aKAwmEEViqDQNbsk8x')
const response = await client.submitAndWait({
TransactionType: "AccountSet",
Account: issuerWallet.address,
SetFlag: AccountSetAsfFlags.asfAllowTrustLineClawback,
}, { wallet: issuerWallet })
console.log(JSON.stringify(response.result, null, 2))
}
main()
meta.AffectedNodes
のAccountRoot
情報を確認すると、lsfAllowTrustLineClawback
フラグに該当する数値2147483648
が設定されていることが分かります。
トランザクションの結果
{
"Account": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 2300764,
"Sequence": 2300690,
"SetFlag": 16,
"SigningPubKey": "EDD8EDB622BAD92E9709EB7FD090594EB85A6FF1E4713DFEFC19A47F7E82FA7D60",
"TransactionType": "AccountSet",
"TxnSignature": "48313D6DC131A954E520B6CD36B33661A7748F5FD412CAD6DFA43027C2D115D309C74C18EC21DDB5B0709D9537CA13A15C4C799015348A33851997998B356601",
"ctid": "C0231B4A00010002",
"date": 755334592,
"hash": "37D56A4BB1AB7770F8EE1D2D3854AAD6000D4A3B7B328A9C121BB080DCF157F7",
"inLedger": 2300746,
"ledger_index": 2300746,
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"Balance": "9999999988",
"Flags": 2147483648,
"OwnerCount": 0,
"Sequence": 2300691
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "F0AE6EAB7C50BB24B71C3C96E8F5C5A6E22F4884BCF80F66DD08CFE16E285CA9",
"PreviousFields": {
"Balance": "10000000000",
"Flags": 0,
"Sequence": 2300690
},
"PreviousTxnID": "E7A138B533AFB1A316D71C78DBE7A580BBF704170864425CC39A11F322F38E34",
"PreviousTxnLgrSeq": 2300690
}
}
],
"TransactionIndex": 1,
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
1.2 アカウント情報の確認
account_info
メソッドを利用するとより分かりやすくフラグの情報を確認することができます。
const accountInfoResponse = await client.request({
command: 'account_info',
account: issuerWallet.address,
})
console.log(accountInfoResponse.result)
account_flags
フィールドを確認すると各フラグの状態を確認することができます。
allowTrustLineClawback: true
がClawback機能が有効化されていることを表しています。
{
account_data: {
Account: 'rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe',
Balance: '9999999988',
Flags: 2147483648,
LedgerEntryType: 'AccountRoot',
OwnerCount: 0,
PreviousTxnID: '37D56A4BB1AB7770F8EE1D2D3854AAD6000D4A3B7B328A9C121BB080DCF157F7',
PreviousTxnLgrSeq: 2300746,
Sequence: 2300691,
index: 'F0AE6EAB7C50BB24B71C3C96E8F5C5A6E22F4884BCF80F66DD08CFE16E285CA9'
},
account_flags: {
allowTrustLineClawback: true,
defaultRipple: false,
depositAuth: false,
disableMasterKey: false,
disallowIncomingCheck: false,
disallowIncomingNFTokenOffer: false,
disallowIncomingPayChan: false,
disallowIncomingTrustline: false,
disallowIncomingXRP: false,
globalFreeze: false,
noFreeze: false,
passwordSpent: false,
requireAuthorization: false,
requireDestinationTag: false
},
ledger_current_index: 2300747,
validated: false
}
2. トークンの配布
次にトークンを回収したいアカウントにトークンを配布してみましょう。
2.1 DefaultRippleの有効化
トークンを第三者間での取引を可能とするには、同じくAccountSet
トランザクションでDefaultRipple
フラグを有効化する必要があります。
const response = await client.submitAndWait({
TransactionType: "AccountSet",
Account: issuerWallet.address,
SetFlag: AccountSetAsfFlags.asfDefaultRipple,
}, { wallet: issuerWallet })
const accountInfoResponse = await client.request({
command: 'account_info',
account: issuerWallet.address,
})
console.log(accountInfoResponse.result)
defaultRipple: true
がDefaultRippleを有効化していることを表します。
{
account_data: {
Account: 'rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe',
Balance: '9999999976',
Flags: 2155872256,
LedgerEntryType: 'AccountRoot',
OwnerCount: 0,
PreviousTxnID: '03D7A170FED89393E1C15C07CF600A7D10552D847B26631FD455E39BBF16D2CF',
PreviousTxnLgrSeq: 2300748,
Sequence: 2300692,
index: 'F0AE6EAB7C50BB24B71C3C96E8F5C5A6E22F4884BCF80F66DD08CFE16E285CA9'
},
account_flags: {
allowTrustLineClawback: true,
defaultRipple: true,
depositAuth: false,
disableMasterKey: false,
disallowIncomingCheck: false,
disallowIncomingNFTokenOffer: false,
disallowIncomingPayChan: false,
disallowIncomingTrustline: false,
disallowIncomingXRP: false,
globalFreeze: false,
noFreeze: false,
passwordSpent: false,
requireAuthorization: false,
requireDestinationTag: false
},
ledger_current_index: 2300749,
validated: false
}
2.2 トラストラインの設定
トークンを保有するためには、保有を希望するアカウントはトークンの発行者へトラストラインを設定する必要があります。
TrustSet
トランザクションを利用することでトラストラインを設定することができます。
ここではJPY
トークンを上限1000000
分まで保持することを許可してみましょう
const response = await client.submitAndWait({
TransactionType: "TrustSet",
Account: userWallet.address,
LimitAmount: {
issuer: issuerWallet.address,
currency: 'JPY',
value: '1000000'
}
}, { wallet: userWallet })
console.log(JSON.stringify(response.result, null, 2))
meta.AffectedNodes
からRippleState
オブジェクト(トラストライン)が作成されていることがわかります。
トランザクションの結果
{
"Account": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 2300768,
"LimitAmount": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "1000000"
},
"Sequence": 2300694,
"SigningPubKey": "EDED05E14C64E4185EC33B41C24A5CDFB4E531C52EC6B9646EB1A426B1F84EE5D5",
"TransactionType": "TrustSet",
"TxnSignature": "D84366B119D101BEC609717DD2E22A58425EAEA6C2F8A26C90B764488C92359CF0C70C81311792013046FFF6677A7E0B3FF00DFE38D73C49C148DF69CE53330B",
"ctid": "C0231B4E00000002",
"date": 755334610,
"hash": "A84202A99CBABAA83188DEF706291A2551EE781813D063BAF542167F8066467E",
"inLedger": 2300750,
"ledger_index": 2300750,
"meta": {
"AffectedNodes": [
{
"CreatedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "4566F99093DB97DAF517DCDC5661F0FE734A36EF9B664EC617D2A00869430DCE",
"NewFields": {
"Owner": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"RootIndex": "4566F99093DB97DAF517DCDC5661F0FE734A36EF9B664EC617D2A00869430DCE"
}
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"Balance": "9999999988",
"Flags": 0,
"OwnerCount": 1,
"Sequence": 2300695
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "7209510C97AE5984A770BF44F17CA7430392FDA8C0E32D63F9916D35D5F5C168",
"PreviousFields": {
"Balance": "10000000000",
"OwnerCount": 0,
"Sequence": 2300694
},
"PreviousTxnID": "CACD03AD7080FB779FDF66E0FEF6AFE547E26F59C00CA0F2A8F4A5A718012BCB",
"PreviousTxnLgrSeq": 2300694
}
},
{
"CreatedNode": {
"LedgerEntryType": "RippleState",
"LedgerIndex": "888F00E335B86D0B01BD6E2A54FEFE92D0F3FCBAD69ACD802C0734FBB28B5D42",
"NewFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "0"
},
"Flags": 131072,
"HighLimit": {
"currency": "JPY",
"issuer": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"value": "1000000"
},
"LowLimit": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "0"
}
}
}
},
{
"CreatedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "CAD9FE6E62D522EAD667E8260BF2150A49663DB370B62284DEEA6D39565EFD4A",
"NewFields": {
"Owner": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"RootIndex": "CAD9FE6E62D522EAD667E8260BF2150A49663DB370B62284DEEA6D39565EFD4A"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "F0AE6EAB7C50BB24B71C3C96E8F5C5A6E22F4884BCF80F66DD08CFE16E285CA9",
"PreviousTxnID": "03D7A170FED89393E1C15C07CF600A7D10552D847B26631FD455E39BBF16D2CF",
"PreviousTxnLgrSeq": 2300748
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
{
"CreatedNode": {
"LedgerEntryType": "RippleState",
"LedgerIndex": "888F00E335B86D0B01BD6E2A54FEFE92D0F3FCBAD69ACD802C0734FBB28B5D42",
"NewFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "0"
},
"Flags": 131072,
"HighLimit": {
"currency": "JPY",
"issuer": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"value": "1000000"
},
"LowLimit": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "0"
}
}
}
},
2.3 トークンの配布
トークンはPayment
トランザクションを利用すること配布・送信することができます。
受取アカウントへは100000JPY
まで保有できるように設定されているため今回は50000JPY
を送金してみます。
const response = await client.submitAndWait({
TransactionType: "Payment",
Account: issuerWallet.address,
Destination: userWallet.address,
Amount: {
issuer: issuerWallet.address,
currency: 'JPY',
value: '500000'
}
}, { wallet: issuerWallet })
console.log(JSON.stringify(response.result, null, 2))
meta.AffectedNodes
からRippleState
オブジェクト(トラストライン)が変更されていることがわかります。
トランザクションの結果
{
"Account": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"Amount": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "500000"
},
"DeliverMax": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "500000"
},
"Destination": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 2300770,
"Sequence": 2300692,
"SigningPubKey": "EDD8EDB622BAD92E9709EB7FD090594EB85A6FF1E4713DFEFC19A47F7E82FA7D60",
"TransactionType": "Payment",
"TxnSignature": "58A1C96E40B7A00951585AED3AFB070CE2B22EA8B920050472891EA41755175560856E52B9A7314A3F52082A156C73152AAF5262CB86055F23E9F50264DAFB08",
"ctid": "C0231B5000000002",
"date": 755334612,
"hash": "50942ED5814DDA04CED6E2AF1702B1249B5F71ADAA021ACC8218F99D504D98F9",
"inLedger": 2300752,
"ledger_index": 2300752,
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-500000"
},
"Flags": 131072,
"HighLimit": {
"currency": "JPY",
"issuer": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"value": "1000000"
},
"HighNode": "0",
"LowLimit": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "0"
},
"LowNode": "0"
},
"LedgerEntryType": "RippleState",
"LedgerIndex": "888F00E335B86D0B01BD6E2A54FEFE92D0F3FCBAD69ACD802C0734FBB28B5D42",
"PreviousFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "0"
}
},
"PreviousTxnID": "A84202A99CBABAA83188DEF706291A2551EE781813D063BAF542167F8066467E",
"PreviousTxnLgrSeq": 2300750
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"Balance": "9999999964",
"Flags": 2155872256,
"OwnerCount": 0,
"Sequence": 2300693
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "F0AE6EAB7C50BB24B71C3C96E8F5C5A6E22F4884BCF80F66DD08CFE16E285CA9",
"PreviousFields": {
"Balance": "9999999976",
"Sequence": 2300692
},
"PreviousTxnID": "A84202A99CBABAA83188DEF706291A2551EE781813D063BAF542167F8066467E",
"PreviousTxnLgrSeq": 2300750
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"delivered_amount": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "500000"
}
},
"validated": true
}
Balance
フィールドが-500000
に変更されていますがこれはLowLimit
のアカウントから見たときの残高であり、このトラストラインでは発行者がLowLimit
であるため、発行者から見たら-500000
、ユーザから見たら500000
となります。
どちらがLow
でどちらがHigh
なのかはアカウントアドレスをソートした場合の順番で決定されます。
{
"ModifiedNode": {
"FinalFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-500000"
},
"Flags": 131072,
"HighLimit": {
"currency": "JPY",
"issuer": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"value": "1000000"
},
"HighNode": "0",
"LowLimit": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "0"
},
"LowNode": "0"
},
"LedgerEntryType": "RippleState",
"LedgerIndex": "888F00E335B86D0B01BD6E2A54FEFE92D0F3FCBAD69ACD802C0734FBB28B5D42",
"PreviousFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "0"
}
},
"PreviousTxnID": "A84202A99CBABAA83188DEF706291A2551EE781813D063BAF542167F8066467E",
"PreviousTxnLgrSeq": 2300750
}
},
3. トークンの回収
トークンの回収はClawback
トランザクションを利用することで行うことができます。
Amount.issuer
フィールドではトークンを回収したいアカウントのアドレスを指定します。
これはトラストラインが双方向のオブジェクトであり、発行者アドレスから見た相手アカウントのトラストラインのissuer
は相手アカウントのアドレスとなっているからです。
const response = await client.submitAndWait({
TransactionType: "Clawback",
Account: issuerWallet.address,
Amount: {
issuer: userWallet.address,
currency: 'JPY',
value: '500000'
}
}, { wallet: issuerWallet })
console.log(JSON.stringify(response.result, null, 2))
meta.AffectedNodes
からRippleState
オブジェクト(トラストライン)が変更されていることがわかります。
トランザクションの結果
{
"Account": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"Amount": {
"currency": "JPY",
"issuer": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"value": "500000"
},
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 2300772,
"Sequence": 2300693,
"SigningPubKey": "EDD8EDB622BAD92E9709EB7FD090594EB85A6FF1E4713DFEFC19A47F7E82FA7D60",
"TransactionType": "Clawback",
"TxnSignature": "66E4148C4E61F6D88C73CCE172BC3950F8C032181740EBBB624F57407254DC23E9F75DFD4255B8ECB91EC6A826E9B13F7A848CBC874EA54E17077ABD4705CF0E",
"ctid": "C0231B5200000002",
"date": 755334621,
"hash": "6C373C292E3DF82DFB837A14EA97444278B4C177DFB97F0367DDA84BA4CD1712",
"inLedger": 2300754,
"ledger_index": 2300754,
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "0"
},
"Flags": 131072,
"HighLimit": {
"currency": "JPY",
"issuer": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"value": "1000000"
},
"HighNode": "0",
"LowLimit": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "0"
},
"LowNode": "0"
},
"LedgerEntryType": "RippleState",
"LedgerIndex": "888F00E335B86D0B01BD6E2A54FEFE92D0F3FCBAD69ACD802C0734FBB28B5D42",
"PreviousFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-500000"
}
},
"PreviousTxnID": "50942ED5814DDA04CED6E2AF1702B1249B5F71ADAA021ACC8218F99D504D98F9",
"PreviousTxnLgrSeq": 2300752
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"Balance": "9999999952",
"Flags": 2155872256,
"OwnerCount": 0,
"Sequence": 2300694
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "F0AE6EAB7C50BB24B71C3C96E8F5C5A6E22F4884BCF80F66DD08CFE16E285CA9",
"PreviousFields": {
"Balance": "9999999964",
"Sequence": 2300693
},
"PreviousTxnID": "50942ED5814DDA04CED6E2AF1702B1249B5F71ADAA021ACC8218F99D504D98F9",
"PreviousTxnLgrSeq": 2300752
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
Balance
が-500000
から0
へと変更されており、ユーザの残高が回収されたことを表しています。
{
"ModifiedNode": {
"FinalFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "0"
},
"Flags": 131072,
"HighLimit": {
"currency": "JPY",
"issuer": "rJMnC81oFjoA8C3JQX31VanSAQmrndwDnu",
"value": "1000000"
},
"HighNode": "0",
"LowLimit": {
"currency": "JPY",
"issuer": "rwVaAzT1UnSKVS2wSr6kzr7c2JP1F4adCe",
"value": "0"
},
"LowNode": "0"
},
"LedgerEntryType": "RippleState",
"LedgerIndex": "888F00E335B86D0B01BD6E2A54FEFE92D0F3FCBAD69ACD802C0734FBB28B5D42",
"PreviousFields": {
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-500000"
}
},
"PreviousTxnID": "50942ED5814DDA04CED6E2AF1702B1249B5F71ADAA021ACC8218F99D504D98F9",
"PreviousTxnLgrSeq": 2300752
}
},
まとめ
Clawback機能を使うことでトークンの発行者は、より規制に準拠した形でトークンを発行することができるようになります。
ゲーム向けのトークンなどでも、さまざまな形で利用されることが期待されます。
興味を持たれた方はXRP Ledger開発者のDiscordチャンネルへ是非お越しください!
日本語チャンネルもありますので、英語ができなくても大丈夫です!
また、XRPL JapanのDiscordサーバもありますので、こちらもぜひご参加ください!
私のX/Twitterアカウントはこちら!
Discussion