Use Tickets tutorial improvements and additions

This commit is contained in:
rachelflynn
2026-05-21 11:16:59 -04:00
parent 2fd46e197b
commit 994286cfb2
14 changed files with 422 additions and 729 deletions

View File

@@ -1,5 +1,5 @@
# Tickets
# Use Tickets
Create a Ticket and use it to send a transaction out of the usual Sequence order.
Create a batch of Tickets and use one to send a transaction outside the normal Sequence order.
For more context, see the interactive [Use Tickets tutorial](https://xrpl.org/use-tickets.html).
For more context, see the [Use Tickets tutorial](https://xrpl.org/docs/tutorials/best-practices/transaction-sending/use-tickets).

View File

@@ -0,0 +1,17 @@
# Use Tickets (Go)
Demonstrates how to use Tickets for out-of-order transaction submission on the XRP Ledger.
For more context, see the [Use Tickets tutorial](https://xrpl.org/docs/tutorials/best-practices/transaction-sending/use-tickets).
## Setup
```sh
go mod download
```
## Basic Ticket Usage
```sh
go run main.go
```

View File

@@ -1,21 +1,18 @@
module github.com/XRPLF
go 1.24.0
go 1.24.3
require github.com/Peersyst/xrpl-go v0.1.11
require github.com/Peersyst/xrpl-go v0.1.18
require (
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
github.com/bsv-blockchain/go-sdk v1.2.9 // indirect
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/tyler-smith/go-bip32 v1.0.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/crypto v0.45.0 // indirect
)

View File

@@ -1,22 +1,16 @@
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc=
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw=
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc=
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U=
github.com/Peersyst/xrpl-go v0.1.11 h1:P6r/gHxRnbAtAdPmzNHz/7zpsdfvwh0SS+QI2JNT44w=
github.com/Peersyst/xrpl-go v0.1.11/go.mod h1:CBRM3/soqNeeL2Jx6USVUtECqulZVUoq3UxZKMz9hdw=
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw=
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA=
github.com/Peersyst/xrpl-go v0.1.18 h1:PUIEGvnkO9oQ4yZRr6EHjglay1xmrjFhujQNVkXynqs=
github.com/Peersyst/xrpl-go v0.1.18/go.mod h1:38j60Mr65poIHdhmjvNXnwbcUFNo8J7CBDot7ZWgrb8=
github.com/bsv-blockchain/go-sdk v1.2.9 h1:LwFzuts+J5X7A+ECx0LNowtUgIahCkNNlXckdiEMSDk=
github.com/bsv-blockchain/go-sdk v1.2.9/go.mod h1:KiHWa/hblo3Bzr+IsX11v0sn1E6elGbNX0VXl5mOq6E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 h1:TvGTmUBHDU75OHro9ojPLK+Yv7gDl2hnUvRocRCjsys=
github.com/decred/dcrd/crypto/ripemd160 v1.0.2/go.mod h1:uGfjDyePSpa75cSQLzNdVmWlbQMBuiJkvXw/MNKRY4M=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -28,29 +22,17 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tyler-smith/go-bip32 v1.0.0 h1:sDR9juArbUgX+bO/iblgZnMPeWY1KZMUC2AFUJdv5KE=
github.com/tyler-smith/go-bip32 v1.0.0/go.mod h1:onot+eHknzV4BVPwrzqY5OoVpyCvnwD7lMawL5aQupE=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=

View File

@@ -8,116 +8,83 @@ import (
"github.com/Peersyst/xrpl-go/xrpl/faucet"
"github.com/Peersyst/xrpl-go/xrpl/queries/account"
"github.com/Peersyst/xrpl-go/xrpl/rpc"
rpctypes "github.com/Peersyst/xrpl-go/xrpl/rpc/types"
"github.com/Peersyst/xrpl-go/xrpl/transaction"
"github.com/Peersyst/xrpl-go/xrpl/wallet"
)
func main() {
cfg, err := rpc.NewClientConfig(
"https://s.altnet.rippletest.net:51234/",
rpc.WithFaucetProvider(faucet.NewTestnetFaucetProvider()),
)
if err != nil {
panic(err)
}
// Set up client and account
cfg, err := rpc.NewClientConfig(
"https://s.altnet.rippletest.net:51234/",
rpc.WithFaucetProvider(faucet.NewTestnetFaucetProvider()),
rpc.WithMaxRetries(30),
)
if err != nil {
panic(err)
}
client := rpc.NewClient(cfg)
client := rpc.NewClient(cfg)
// Fund wallet
w, err := wallet.New(crypto.ED25519())
if err != nil {
panic(err)
}
fmt.Println("Funding wallet...")
if err := client.FundWallet(&w); err != nil {
panic(err)
}
fmt.Println("Wallet funded")
w, err := wallet.New(crypto.ED25519())
if err != nil {
panic(err)
}
// Create Tickets
ticketCreate := &transaction.TicketCreate{
BaseTx: transaction.BaseTx{
Account: w.GetAddress(),
},
TicketCount: 10,
}
fmt.Println("Submitting TicketCreate transaction...")
tcResult, err := client.SubmitTxAndWait(ticketCreate.Flatten(), &rpctypes.SubmitOptions{
Autofill: true,
Wallet: &w,
})
if err != nil {
panic(err)
}
fmt.Printf("TicketCreate hash: %s, validated: %t\n", tcResult.Hash, tcResult.Validated)
fmt.Println("Funding wallet...")
if err := client.FundWallet(&w); err != nil {
panic(err)
}
// Check Available Tickets
objects, err := client.GetAccountObjects(&account.ObjectsRequest{
Account: w.GetAddress(),
Type: account.TicketObject,
})
if err != nil {
panic(err)
}
fmt.Printf("Found %d Tickets\n", len(objects.AccountObjects))
fmt.Println("Wallet funded")
fmt.Println()
// Choose an arbitrary Ticket to use
useTicket, err := objects.AccountObjects[0]["TicketSequence"].(json.Number).Int64()
if err != nil {
panic(err)
}
fmt.Printf("Using Ticket Sequence: %d\n", useTicket)
info, err := client.GetAccountInfo(&account.InfoRequest{
Account: w.GetAddress(),
})
if err != nil {
panic(err)
}
fmt.Println("Current wallet sequence:", info.AccountData.Sequence)
fmt.Println()
fmt.Println("Submitting TicketCreate transaction...")
tc := &transaction.TicketCreate{
BaseTx: transaction.BaseTx{
Account: w.GetAddress(),
Sequence: info.AccountData.Sequence,
},
TicketCount: 10,
}
flatTc := tc.Flatten()
if err := client.Autofill(&flatTc); err != nil {
panic(err)
}
blob, _, err := w.Sign(flatTc)
if err != nil {
panic(err)
}
res, err := client.SubmitTxBlobAndWait(blob, false)
if err != nil {
panic(err)
}
fmt.Println("TicketCreate transaction submitted")
fmt.Printf("Hash: %s\n", res.Hash)
fmt.Printf("Validated: %t\n", res.Validated)
fmt.Println()
objects, err := client.GetAccountObjects(&account.ObjectsRequest{
Account: w.GetAddress(),
})
if err != nil {
panic(err)
}
fmt.Println("Account objects:", objects.AccountObjects[0]["TicketSequence"])
seq, err := objects.AccountObjects[0]["TicketSequence"].(json.Number).Int64()
if err != nil {
panic(err)
}
fmt.Println("Submitting AccountSet transaction...")
as := &transaction.AccountSet{
BaseTx: transaction.BaseTx{
Account: w.GetAddress(),
Sequence: 0,
TicketSequence: uint32(seq),
},
}
flatAs := as.Flatten()
if err := client.Autofill(&flatAs); err != nil {
panic(err)
}
flatAs["Sequence"] = uint32(0)
blob, _, err = w.Sign(flatAs)
if err != nil {
panic(err)
}
res, err = client.SubmitTxBlobAndWait(blob, false)
if err != nil {
panic(err)
}
fmt.Println("AccountSet transaction submitted")
fmt.Printf("Hash: %s\n", res.Hash)
fmt.Printf("Validated: %t\n", res.Validated)
// Use a Ticket
ticketedTx := &transaction.AccountSet{
BaseTx: transaction.BaseTx{
Account: w.GetAddress(),
Sequence: 0,
TicketSequence: uint32(useTicket),
},
}
fmt.Println("Submitting ticketed AccountSet transaction...")
ticketedResult, err := client.SubmitTxAndWait(ticketedTx.Flatten(), &rpctypes.SubmitOptions{
Autofill: true,
Wallet: &w,
})
if err != nil {
panic(err)
}
fmt.Printf("Ticketed AccountSet hash: %s, validated: %t\n", ticketedResult.Hash, ticketedResult.Validated)
}

View File

@@ -2,6 +2,8 @@
Demonstrates how to use Tickets for out-of-order transaction submission on the XRP Ledger.
For more context, see the [Use Tickets tutorial](https://xrpl.org/docs/tutorials/best-practices/transaction-sending/use-tickets).
## Setup
```sh
@@ -10,12 +12,6 @@ npm install
## Basic Ticket Usage
### Browser
Open `demo.html` in a web browser and check the browser console to see the logs.
Alternatively, you can run the code from the command line:
```sh
node use-tickets.js
```

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Code Sample - Use Tickets</title>
<script src="https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js"></script>
<script type="application/javascript" src="use-tickets.js"></script>
</head>
<body>Open your browser's console (F12) to see the logs.</body>
</html>

View File

@@ -1,4 +1,5 @@
{
"type": "module",
"dependencies": {
"xrpl": "^4.0.0"
}

View File

@@ -1,129 +1,74 @@
if (typeof module !== "undefined") {
// Use var here because const/let are block-scoped to the if statement.
var xrpl = require('xrpl')
}
// List which Tickets are outstanding against ones own account and use Tickets to collect signatures for multisign transactions
// https://xrpl.org/use-tickets.html
// https://xrpl.org/signerlistset.html#signerlistset
// https://xrpl.org/multi-signing.html#multi-signing
import xrpl from 'xrpl'
async function main() {
// Connect to a testnet node
console.log("Connecting to Testnet...")
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
await client.connect()
// Get account credentials from the Testnet Faucet
console.log("Requesting account credentials from the Testnet faucet, this may take awhile...")
const { wallet: main_wallet } = await client.fundWallet()
// Use Tickets with multi-signing: each Ticket holds a Sequence slot for a
// transaction while you collect signatures from multiple signers.
// Signer keys don't need to be funded on the ledger, it only needs to be cryptographically valid
// Thus, we could generate keys and set them as signers without the need to fund their accounts
// But we'll still fund them for testing purposes...
const { wallet: wallet_1 } = await client.fundWallet()
const { wallet: wallet_2 } = await client.fundWallet()
const { wallet: wallet_3 } = await client.fundWallet()
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
await client.connect()
console.log(" Main Account: ", main_wallet.address)
console.log(" Seed: ", main_wallet.seed)
// Set up the main account and three signers ------------------------------
// Signer accounts only need cryptographically valid keys; the addresses
// don't need to be funded or exist on the ledger.
console.log('Funding main account from the faucet...')
const { wallet: mainWallet } = await client.fundWallet()
const signer1 = xrpl.Wallet.generate()
const signer2 = xrpl.Wallet.generate()
const signer3 = xrpl.Wallet.generate()
console.log(`Main account: ${mainWallet.address}`)
console.log("\n Signer 1: ", wallet_1.address)
console.log(" Signer 2: ", wallet_2.address)
console.log(" Signer 3: ", wallet_3.address)
// Configure the signer list (quorum 2 of 3) -------------------------------
console.log('Submitting SignerListSet...')
const signerListResult = await client.submitAndWait({
TransactionType: 'SignerListSet',
Account: mainWallet.address,
SignerQuorum: 2,
SignerEntries: [
{ SignerEntry: { Account: signer1.address, SignerWeight: 1 } },
{ SignerEntry: { Account: signer2.address, SignerWeight: 1 } },
{ SignerEntry: { Account: signer3.address, SignerWeight: 1 } }
]
}, { wallet: mainWallet, autofill: true })
console.log(`SignerListSet hash: ${signerListResult.result.hash}`)
// Send SignerListSet transaction
// Since each signer is given a signer weight of 1 and there are 3 signers, the maximum quorom would be 3
// SignerQuorom is a target number for the signer weights
// A multisig from this list is valid only if the sum weights of the signatures provided is greater than or equal to the SignerQuorom
const signerLiSetSignerList_tx = {
TransactionType: "SignerListSet",
Account: main_wallet.classicAddress,
SignerEntries: [
{
SignerEntry: {
Account: wallet_1.classicAddress,
SignerWeight: 1,
},
},
{
SignerEntry: {
Account: wallet_2.classicAddress,
SignerWeight: 1,
},
},
{
SignerEntry: {
Account: wallet_3.classicAddress,
SignerWeight: 1,
},
}
],
SignerQuorum: 2,
}
// Create Tickets ----------------------------------------------------------
console.log('Submitting TicketCreate (3 Tickets)...')
const ticketCreateResult = await client.submitAndWait({
TransactionType: 'TicketCreate',
Account: mainWallet.address,
TicketCount: 3
}, { wallet: mainWallet, autofill: true })
console.log(`TicketCreate hash: ${ticketCreateResult.result.hash}`)
const signerLiSetSignerList_tx_prepared = await client.autofill(signerLiSetSignerList_tx)
const SetSignerList_tx_signed = main_wallet.sign(signerLiSetSignerList_tx_prepared)
console.log(`\n SignerListSet Tx hash: ${SetSignerList_tx_signed.hash}`)
const setsignerlist_submit = await client.submitAndWait(SetSignerList_tx_signed.tx_blob)
console.log(`\t Submit result: ${setsignerlist_submit.result.meta.TransactionResult}`)
const CreateTicket_tx = await client.autofill({
TransactionType: "TicketCreate",
Account: main_wallet.address,
TicketCount: 3
})
const CreateTicket_tx_signed = main_wallet.sign(CreateTicket_tx)
console.log("\n CreateTicket Tx hash:", CreateTicket_tx_signed.hash)
const ticket_submit = await client.submitAndWait(CreateTicket_tx_signed.tx_blob)
console.log(" Submit result:", ticket_submit.result.meta.TransactionResult)
const ticket_response = await client.request({
command: "account_objects",
account: main_wallet.address,
ledger_index: "validated",
type: "ticket"
})
// Pick a Ticket -----------------------------------------------------------
const ticketsResponse = await client.request({
command: 'account_objects',
account: mainWallet.address,
type: 'ticket'
})
const useTicket = ticketsResponse.result.account_objects[0].TicketSequence
console.log(`Using Ticket Sequence: ${useTicket}`)
console.log(`\n- Tickets issued by ${main_wallet.address}:\n`)
for (let i = 0; i < ticket_response.result.account_objects.length; i++) {
console.log(`Ticket ${i+1}: ${ticket_response.result.account_objects[i].TicketSequence}`)
}
// Prepare a multi-signed transaction that consumes the Ticket.
// Omit LastLedgerSequence so the transaction does not expire while
// signatures are being collected.
const preparedTx = await client.autofill({
TransactionType: 'AccountSet',
Account: mainWallet.address,
TicketSequence: useTicket,
LastLedgerSequence: null,
Sequence: 0
}, 3) // signersCount for multi-sig fee calculation
// We'll use this ticket on our tx
const ticket_1 = ticket_response.result.account_objects[0].TicketSequence
console.log(`\n Ticket sequence ${ticket_1} will be used for our multi-sig transaction`)
const Payment_tx = {
"TransactionType": "AccountSet",
"Account": main_wallet.address,
"TicketSequence": ticket_1,
"LastLedgerSequence": null,
"Sequence": 0
}
// In a real workflow you would share preparedTx with each signer and
// receive their signed blobs back; here we sign in one process for clarity.
const { tx_blob: signedBlob1 } = signer1.sign(preparedTx, true)
const { tx_blob: signedBlob2 } = signer2.sign(preparedTx, true)
const { tx_blob: signedBlob3 } = signer3.sign(preparedTx, true)
const Payment_tx_prepared = await client.autofill(Payment_tx, signersCount=3)
// Each signer will sign the prepared tx (AccountSet_tx) and their signatures will be combines into 1 multi-sig transaction
const { tx_blob: Payment_tx_signed_1 } = wallet_1.sign(Payment_tx_prepared, multisign=true)
const { tx_blob: Payment_tx_signed_2 } = wallet_2.sign(Payment_tx_prepared, multisign=true)
const { tx_blob: Payment_tx_signed_3 } = wallet_3.sign(Payment_tx_prepared, multisign=true)
console.log("\n All signers have signed the transaction with their corresponding keys")
const multisignedBlob = xrpl.multisign([signedBlob1, signedBlob2, signedBlob3])
console.log('Submitting multi-signed AccountSet...')
const multisigResult = await client.submitAndWait(multisignedBlob)
console.log(`Multi-sig hash: ${multisigResult.result.hash}, result: ${multisigResult.result.meta.TransactionResult}`)
// Combine 3 of the signers' signatures to form a multi-sig transaction
const multisignedTx = xrpl.multisign([Payment_tx_signed_1, Payment_tx_signed_2, Payment_tx_signed_3])
const multisig_submit = await client.submitAndWait(multisignedTx)
console.log("\n Multi-sig Submit result:", multisig_submit.result.meta.TransactionResult)
console.log("\n Multi-sig Tx Binary:", multisignedTx)
client.disconnect()
// End main()
}
main()
// Disconnect when done (If you omit this, Node.js won't end the process)
await client.disconnect()

View File

@@ -1,76 +1,44 @@
// Dependencies for Node.js.
// In browsers, use a <script> tag instead.
if (typeof module !== "undefined") {
// Use var here because const/let are block-scoped to the if statement.
var xrpl = require('xrpl')
}
import xrpl from 'xrpl'
// Example credentials
const wallet = xrpl.Wallet.fromSeed("sn3nxiW7v8KXzPzAqzyHXbSSKNuN9")
// Set up client and account
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
await client.connect()
// Connect to Devnet (since that's where tickets are available)
async function main() {
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
await client.connect()
// Fund wallet --------------------------------------------------------------
console.log('Getting a wallet from the faucet...')
const { wallet } = await client.fundWallet()
// Get credentials from the Testnet Faucet -----------------------------------
console.log("Getting a wallet from the faucet...")
const {wallet, balance} = await client.fundWallet()
// Create Tickets -----------------------------------------------------------
console.log('Submitting TicketCreate transaction...')
const ticketCreateResult = await client.submitAndWait({
TransactionType: 'TicketCreate',
Account: wallet.address,
TicketCount: 10
}, { wallet, autofill: true })
console.log(`TicketCreate hash: ${ticketCreateResult.result.hash}, validated: ${ticketCreateResult.result.validated}`)
// Check Sequence Number -----------------------------------------------------
const account_info = await client.request({
"command": "account_info",
"account": wallet.address
})
let current_sequence = account_info.result.account_data.Sequence
// Check Available Tickets --------------------------------------------------
const ticketsResponse = await client.request({
command: 'account_objects',
account: wallet.address,
type: 'ticket'
})
const tickets = ticketsResponse.result.account_objects
console.log(`Found ${tickets.length} Tickets`)
// Prepare and Sign TicketCreate ---------------------------------------------
const prepared = await client.autofill({
"TransactionType": "TicketCreate",
"Account": wallet.address,
"TicketCount": 10,
"Sequence": current_sequence
})
const signed = wallet.sign(prepared)
console.log(`Prepared TicketCreate transaction ${signed.hash}`)
// Choose an arbitrary Ticket to use
const useTicket = tickets[0].TicketSequence
console.log(`Using Ticket Sequence: ${useTicket}`)
// Submit TicketCreate -------------------------------------------------------
const tx = await client.submitAndWait(signed.tx_blob)
console.log(tx)
// Use a Ticket -------------------------------------------------------------
console.log('Submitting ticketed AccountSet transaction...')
const ticketedResult = await client.submitAndWait({
TransactionType: 'AccountSet',
Account: wallet.address,
TicketSequence: useTicket,
Sequence: 0
}, { wallet, autofill: true })
console.log(`Ticketed AccountSet hash: ${ticketedResult.result.hash}, validated: ${ticketedResult.result.validated}`)
// Wait for Validation -------------------------------------------------------
// submitAndWait() handles this automatically, but it can take 4-7s.
// Check Available Tickets ---------------------------------------------------
let response = await client.request({
"command": "account_objects",
"account": wallet.address,
"type": "ticket"
})
console.log("Available Tickets:", response.result.account_objects)
// Choose an arbitrary Ticket to use
use_ticket = response.result.account_objects[0].TicketSequence
// Prepare and Sign Ticketed Transaction -------------------------------------
const prepared_t = await client.autofill({
"TransactionType": "AccountSet",
"Account": wallet.address,
"TicketSequence": use_ticket,
"LastLedgerSequence": null, // Never expire this transaction.
"Sequence": 0
})
const signed_t = wallet.sign(prepared_t)
console.log(`Prepared ticketed transaction ${signed_t.hash}`)
// Submit Ticketed Transaction -----------------------------------------------
const tx_t = await client.submitAndWait(signed_t.tx_blob)
console.log(tx_t)
// Wait for Validation (again) -----------------------------------------------
// Disconnect when done (If you omit this, Node.js won't end the process)
await client.disconnect()
}
main()
// Disconnect when done (If you omit this, Node.js won't end the process)
await client.disconnect()

View File

@@ -2,6 +2,8 @@
Demonstrates how to use Tickets for out-of-order transaction submission on the XRP Ledger.
For more context, see the [Use Tickets tutorial](https://xrpl.org/docs/tutorials/best-practices/transaction-sending/use-tickets).
## Setup
```sh

View File

@@ -1,136 +1,72 @@
from xrpl.models.transactions import TicketCreate, AccountSet, SignerListSet, SignerEntry
from xrpl.transaction import sign_and_submit, autofill, multisign, sign
from xrpl.models.requests.account_objects import AccountObjects, AccountObjectType
from xrpl.models.requests.submit_multisigned import SubmitMultisigned
from xrpl.wallet import generate_faucet_wallet
from xrpl.clients import JsonRpcClient
from xrpl.models.requests import AccountObjects, AccountObjectType
from xrpl.models.transactions import AccountSet, SignerEntry, SignerListSet, TicketCreate
from xrpl.transaction import autofill, multisign, sign, submit_and_wait
from xrpl.wallet import Wallet, generate_faucet_wallet
# This code sample shows how to use tickets with multisigning
# to allow you to do a series of transactions in any order while you wait
# for signers to come back with their signatures.
# https://xrpl.org/tickets.html#tickets
# https://xrpl.org/use-tickets.html
# https://xrpl.org/set-up-multi-signing.html#set-up-multi-signing
# https://xrpl.org/send-a-multi-signed-transaction.html#send-a-multi-signed-transaction
# Use Tickets with multi-signing: each Ticket holds a Sequence slot for a
# transaction while you collect signatures from multiple signers.
# Note that this will only work with xrpl-py version v2.0.0-beta.0 or later
# Connect to a testnet node
JSON_RPC_URL = "https://s.altnet.rippletest.net:51234/"
client = JsonRpcClient(JSON_RPC_URL)
# Generate a wallet and request faucet
test_wallet = generate_faucet_wallet(client=client)
myAddr = test_wallet.address
# Set up the main account and three signers.
# Signer accounts only need cryptographically valid keys; the addresses
# don't need to be funded or exist on the ledger.
print("Funding main account from the faucet...")
main_wallet = generate_faucet_wallet(client=client)
signer_1 = Wallet.create()
signer_2 = Wallet.create()
signer_3 = Wallet.create()
print(f"Main account: {main_wallet.address}")
print("Setting up all the signers' accounts via the testnet faucet, this may take a while...")
signer_1_wallet = generate_faucet_wallet(client=client)
signer_2_wallet = generate_faucet_wallet(client=client)
signer_3_wallet = generate_faucet_wallet(client=client)
# Set the list of accounts that are able to authorize transactions on behalf of our Account via a multi-sig transaction
signers = [
SignerEntry(account=signer_1_wallet.address, signer_weight=1),
SignerEntry(account=signer_2_wallet.address, signer_weight=1),
SignerEntry(account=signer_3_wallet.address, signer_weight=1)
]
# Display all the signers' account address
print("\nSigners:")
signer_int = 1
for signer in signers:
print(f"{signer_int}. {signer}")
signer_int += 1
# A multi-sig transaction is achievable by trusting a list of keys to authorize transactions on behalf of the account
# Construct a SignerList transaction
tx_set_signer_list = SignerListSet(
account=myAddr,
signer_quorum=3,
signer_entries=signers
# Configure the signer list (quorum 2 of 3)
print("Submitting SignerListSet...")
signer_list_set = SignerListSet(
account=main_wallet.address,
signer_quorum=2,
signer_entries=[
SignerEntry(account=signer_1.address, signer_weight=1),
SignerEntry(account=signer_2.address, signer_weight=1),
SignerEntry(account=signer_3.address, signer_weight=1),
],
)
signer_list_result = submit_and_wait(signer_list_set, client, main_wallet)
print(f"SignerListSet hash: {signer_list_result.result['hash']}")
# We'll set the signer_quorum to 3, each key will represent 1 signer weight
# if 3 of the signers sign a particular transaction, it's an authorized transaction on the XRPL
# Create Tickets
print("Submitting TicketCreate (3 Tickets)...")
ticket_create = TicketCreate(account=main_wallet.address, ticket_count=3)
ticket_create_result = submit_and_wait(ticket_create, client, main_wallet)
print(f"TicketCreate hash: {ticket_create_result.result['hash']}")
# Sign transaction locally and submit
print("Submitting a SignerListSet transaction to update our account to use our new Signers...")
tx_set_signer_list_signed = sign_and_submit(transaction=tx_set_signer_list, client=client, wallet=test_wallet)
# Construct a TicketCreate transaction, 3 tickets will be created
tx_create_ticket = TicketCreate(
account=myAddr,
ticket_count=len(signers)
)
# Sign transaction locally and submit
print("Submitting a TicketCreate transaction to get Ticket Sequences for future transactions...")
tx_create_ticket_signed = sign_and_submit(transaction=tx_create_ticket, client=client, wallet=test_wallet)
# Get a Ticket Sequence
get_ticket_sequence = client.request(AccountObjects(
account=myAddr,
type=AccountObjectType.TICKET
# Pick a Ticket
tickets_response = client.request(AccountObjects(
account=main_wallet.address,
type=AccountObjectType.TICKET,
))
use_ticket = tickets_response.result["account_objects"][0]["TicketSequence"]
print(f"Using Ticket Sequence: {use_ticket}")
# Since we created 3 Tickets previously, you're able to choose which Ticket you're going to use
ticket1_sequence = get_ticket_sequence.result["account_objects"][0]["TicketSequence"]
ticket2_sequence = get_ticket_sequence.result["account_objects"][1]["TicketSequence"]
ticket3_sequence = get_ticket_sequence.result["account_objects"][2]["TicketSequence"]
print(f"\nTickets issued:\n"
f"Ticket 1: {ticket1_sequence}\n"
f"Ticket 2: {ticket2_sequence}\n"
f"Ticket 3: {ticket3_sequence}")
ticket_sequence = int(input("\nPick 1 ticket sequence to use for your next multi-sig transaction: "))
# Construct AccountSet Transaction using a Ticket
tx_1 = AccountSet(
account=myAddr,
fee="1000",
# Prepare a multi-signed transaction that consumes the Ticket.
# Omit last_ledger_sequence so the transaction doesn't expire while
# signatures are being collected.
account_set = AccountSet(
account=main_wallet.address,
ticket_sequence=use_ticket,
sequence=0,
last_ledger_sequence=None,
ticket_sequence=ticket_sequence
)
prepared_tx = autofill(account_set, client, signers_count=3)
autofilled_account_set_tx = autofill(tx_1, client, len(signers))
# In a real workflow you would share prepared_tx with each signer and
# receive their signed transactions back; here we sign in one process for clarity.
signed_1 = sign(prepared_tx, signer_1, multisign=True)
signed_2 = sign(prepared_tx, signer_2, multisign=True)
signed_3 = sign(prepared_tx, signer_3, multisign=True)
# Sign 'tx_1' using all the keys on signers[]
# In a real app, you would share this transaction with key holders for them to sign
# but since we own these accounts for test purposes, we can sign directly here.
tx_result_1 = sign(transaction=autofilled_account_set_tx, wallet=signer_1_wallet, multisign=True)
tx_result_2 = sign(transaction=autofilled_account_set_tx, wallet=signer_2_wallet, multisign=True)
tx_result_3 = sign(transaction=autofilled_account_set_tx, wallet=signer_3_wallet, multisign=True)
print(f"\nTx signature by account 1: {tx_result_1.signers}")
print(f"Tx signature by account 2: {tx_result_2.signers}")
print(f"Tx signature by account 3: {tx_result_3.signers}")
# Combine all the signed transactions from the provided signed txs into a multi-sig transaction
tx_1_filled = multisign(
transaction=autofilled_account_set_tx,
tx_list=[
tx_result_1,
tx_result_2,
tx_result_3
]
)
response = client.request(SubmitMultisigned(tx_json=tx_1_filled))
result = response.result
print(f"\n Account Set tx result: {result['engine_result']}"
f"\n Tx content: {result}\n")
if result['engine_result'] == "tesSUCCESS":
print("Multi-sig transaction using a Ticket was successful!")
elif result == "terPRE_TICKET":
print(f"The provided Ticket Sequence {ticket_sequence} does not exist in the ledger")
elif result == "tefMAX_LEDGER":
print("Transaction failed to achieve consensus")
elif result == "tefPAST_SEQ":
print("The sequence number is lower than the current sequence number of the account")
elif result == "unknown":
print("Transaction status unknown")
print(f"The transaction's engine_result was {result['engine_result']}")
multisigned_tx = multisign(prepared_tx, [signed_1, signed_2, signed_3])
print("Submitting multi-signed AccountSet...")
multisig_result = submit_and_wait(multisigned_tx, client, autofill=False, check_fee=False)
print(f"Multi-sig hash: {multisig_result.result['hash']}, "
f"result: {multisig_result.result['meta']['TransactionResult']}")

View File

@@ -1,66 +1,44 @@
from xrpl.clients import JsonRpcClient
from xrpl.models.transactions import TicketCreate, AccountSet
from xrpl.transaction import sign_and_submit
from xrpl.models.requests import AccountObjects, AccountObjectType
from xrpl.models.transactions import AccountSet, TicketCreate
from xrpl.transaction import submit_and_wait
from xrpl.wallet import generate_faucet_wallet
from xrpl.models.requests.account_objects import AccountObjects, AccountObjectType
# Connect to a testnet node
# Set up client and account
JSON_RPC_URL = "https://s.altnet.rippletest.net:51234/"
client = JsonRpcClient(JSON_RPC_URL)
# Generate a wallet and request faucet
test_wallet = generate_faucet_wallet(client=client)
myAddr = test_wallet.address
# Fund wallet
print("Getting a wallet from the faucet...")
wallet = generate_faucet_wallet(client=client)
# Construct a TicketCreate transaction, 2 ticket created for future use
tx = TicketCreate(
account=myAddr,
ticket_count=2
# Create Tickets
ticket_create = TicketCreate(
account=wallet.address,
ticket_count=10,
)
print("Submitting TicketCreate transaction...")
ticket_create_result = submit_and_wait(ticket_create, client, wallet)
print(f"TicketCreate hash: {ticket_create_result.result['hash']}, validated: {ticket_create_result.result['validated']}")
# Sign transaction locally and submit
my_tx_payment_signed = sign_and_submit(transaction=tx, client=client, wallet=test_wallet)
# Check Available Tickets
tickets_response = client.request(AccountObjects(
account=wallet.address,
type=AccountObjectType.TICKET,
))
tickets = tickets_response.result["account_objects"]
print(f"Found {len(tickets)} Tickets")
# Get a Ticket Sequence
get_ticket_sequence = AccountObjects(
account=myAddr,
type=AccountObjectType.TICKET
)
# Choose an arbitrary Ticket to use
use_ticket = tickets[0]["TicketSequence"]
print(f"Using Ticket Sequence: {use_ticket}")
response = client.request(get_ticket_sequence)
# Since we created 2 Tickets previously, you're able to choose which Ticket you're going to use
ticket1_sequence = response.result["account_objects"][0]["TicketSequence"]
ticket2_sequence = response.result["account_objects"][1]["TicketSequence"]
print(f"Ticket 1: {ticket1_sequence}\n"
f"Ticket 2: {ticket2_sequence}")
ticket_sequence = int(input("Pick a ticket sequence to use for your next transaction: "))
# Construct Transaction using a Ticket
tx_1 = AccountSet(
account=myAddr,
fee="10",
# Use a Ticket
ticketed_tx = AccountSet(
account=wallet.address,
ticket_sequence=use_ticket,
sequence=0,
last_ledger_sequence=None,
ticket_sequence=ticket_sequence
)
# Send transaction (w/ Ticket)
tx_result = sign_and_submit(transaction=tx_1, client=client, wallet=test_wallet)
result = tx_result.result["engine_result"]
print(f"Account: {myAddr}")
if result == "tesSUCCESS":
print("Transaction successful!")
elif result == "terPRE_TICKET":
print("The provided Ticket Sequence does not exist in the ledger")
elif result == "tefMAX_LEDGER":
print("Transaction failed to achieve consensus")
elif result == "tefPAST_SEQ":
print("The sequence number is lower than the current sequence number of the account")
elif result == "unknown":
print("Transaction status unknown")
else:
print(f"Transaction failed with code {result}")
print("Submitting ticketed AccountSet transaction...")
ticketed_result = submit_and_wait(ticketed_tx, client, wallet)
print(f"Ticketed AccountSet hash: {ticketed_result.result['hash']}, validated: {ticketed_result.result['validated']}")