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']}")

View File

@@ -1,274 +1,188 @@
---
html: use-tickets.html
parent: manage-account-settings.html
seo:
description: Use Tickets to send a transaction outside of normal Sequence order.
embed_xrpl_js: true
filters:
- interactive_steps
description: Use Tickets to send a transaction outside the normal Sequence order.
labels:
- Accounts
steps: ['Generate', 'Connect', 'Check Sequence', 'Prepare & Sign', 'Submit', 'Wait', 'Intermission', 'Check Tickets', 'Prepare Ticketed Tx', 'Submit Ticketed Tx', 'Wait Again']
- Transactions
---
# Use Tickets
[Tickets](../../../concepts/accounts/tickets.md) provide a way to send transactions out of the normal order. This tutorial walks through the steps of creating a Ticket, then using it to send another transaction.
[Tickets](../../../concepts/accounts/tickets.md) let you reserve Sequence numbers in advance so you can send transactions out of order. This is useful when collecting signatures on multi-signed transactions while normal account activity continues, or queuing transactions for later submission.
This tutorial walks through creating a batch of Tickets, using one in a transaction, and applying the same pattern to multi-signed workflows.
## Goals
By the end of this tutorial, you will be able to:
- Create Tickets to reserve Sequence numbers for future use.
- Use a Ticket to send a transaction outside the normal Sequence order.
## Prerequisites
<!-- Source for this specific tutorial's interactive bits: -->
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
<script type="application/javascript" src="/js/tutorials/use-tickets.js"></script>
To complete this tutorial, you need:
This page provides JavaScript examples that use the [xrpl.js](https://js.xrpl.org/) library. See [Get Started Using JavaScript](../../get-started/get-started-javascript.md) for setup instructions.
Since JavaScript works in the web browser, you can read along and use the interactive steps without any setup.
- A basic understanding of the XRP Ledger.
- A basic understanding of how [Sequence numbers](../../../references/protocol/data-types/basic-data-types.md#account-sequence) work in transactions.
- An XRP Ledger [client library](../../../references/client-libraries.md) set up.
- (Optional) A basic understanding of [multi-signing](../../../concepts/accounts/multi-signing.md), if you plan to combine it with Tickets.
- {% amendment-disclaimer name="TicketBatch" /%}
## Source Code
You can find the complete source code for this tutorial's examples in the [code samples section of this website's repository](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/use-tickets/).
## Steps
This tutorial is divided into a few phases:
- (Steps 1-2) **Setup:** You need an XRP Ledger address and secret. For production, you can use the same address and secret consistently. For this tutorial, you can generate new test credentials as needed. You also need to be connected to the network.
- (Steps 3-6) **Create Tickets:** Send a transaction to set aside some Tickets.
- (Optional) **Intermission:** After creating Tickets, you can send various other transactions at any time before, during, and after the following steps.
- (Steps 7-10) **Use Ticket:** Use one of your set-aside Tickets to send a transaction. You can repeat these steps while skipping the previous parts as long as you have at least one Ticket remaining to use.
### 1. Get Credentials
To transact on the XRP Ledger, you need an address and secret key, and some XRP. For development purposes, you can get these on the [Testnet](../../../concepts/networks-and-servers/parallel-networks.md) using the following interface:
{% partial file="/docs/_snippets/interactive-tutorials/generate-step.md" /%}
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../../concepts/transactions/secure-signing.md).
### 2. Connect to Network
You must be connected to the network to submit transactions to it. Since Tickets are only available on Devnet so far, you should connect to a Devnet server. For example:
### 1. Install dependencies
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Connect to" before="// Get credentials" language="js" /%}
{% /tab %}
From the `_code-samples/use-tickets/js/` folder, use `npm` to install dependencies:
```sh
npm install
```
{% /tab %}
{% tab label="Python" %}
From the `_code-samples/use-tickets/py/` folder, set up a virtual environment and use `pip` to install dependencies:
```sh
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %}
{% tab label="Go" %}
From the `_code-samples/use-tickets/go/` folder, install dependencies with Go modules:
```sh
go mod download
```
{% /tab %}
{% /tabs %}
{% admonition type="info" name="Note" %}The code samples in this tutorial use JavaScript's [`async`/`await` pattern](https://javascript.info/async-await). Since `await` needs to be used from within an `async` function, the remaining code samples are written to continue inside the `main()` function started here. You can also use Promise methods `.then()` and `.catch()` instead of `async`/`await` if you prefer.{% /admonition %}
### 2. Set up client and account
For this tutorial, click the following button to connect:
{% partial file="/docs/_snippets/interactive-tutorials/connect-step.md" /%}
### 3. Check Sequence Number
Before you create any Tickets, you should check what [Sequence Number][] your account is at. You want the current Sequence number for the next step, and the Ticket Sequence numbers it sets aside start from this number.
Import the XRPL client library, connect to the network, and generate a test account funded by the [Testnet](../../../concepts/networks-and-servers/parallel-networks.md) faucet. Using a live test network lets you submit transactions without risking real XRP.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Check Sequence" before="// Prepare and Sign TicketCreate" language="js" /%}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" before="// Create Tickets" language="js" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/use-tickets/py/use-tickets.py" before="# Create Tickets" language="py" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/use-tickets/go/main.go" before="// Create Tickets" language="go" /%}
{% /tab %}
{% /tabs %}
{% interactive-block label="Check Sequence" steps=$frontmatter.steps %}
<button id="check-sequence" class="btn btn-primary previous-steps-required">Check Sequence Number</button>
{% loading-icon message="Querying..." /%}
<div class="output-area"></div>
{% /interactive-block %}
### 4. Prepare and Sign TicketCreate
Construct a [TicketCreate transaction][] using the sequence number you determined in the previous step. Use the `TicketCount` field to specify how many Tickets to create. For example, to prepare a transaction that would make 10 Tickets:
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Prepare and Sign TicketCreate" before="// Submit TicketCreate" language="js" /%}
{% /tab %}
{% /tabs %}
Record the transaction's hash and `LastLedgerSequence` value so you can [be sure whether or not it got validated](../../../concepts/transactions/reliable-transaction-submission.md) later.
{% interactive-block label="Prepare & Sign" steps=$frontmatter.steps %}
<button id="prepare-and-sign" class="btn btn-primary previous-steps-required">Prepare & Sign</button>
<div class="output-area"></div>
{% /interactive-block %}
### 5. Submit TicketCreate
Submit the signed transaction blob that you created in the previous step. For example:
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Submit TicketCreate" before="// Wait for Validation" language="js" /%}
{% /tab %}
{% /tabs %}
{% interactive-block label="Submit" steps=$frontmatter.steps %}
<button id="ticketcreate-submit" class="btn btn-primary previous-steps-required" data-tx-blob-from="#tx_blob" data-wait-step-name="Wait">Submit</button>
{% loading-icon message="Sending..." /%}
<div class="output-area"></div>
{% /interactive-block %}
### 6. Wait for Validation
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. If the XRP Ledger is busy or poor network connectivity delays a transaction from being relayed throughout the network, a transaction may take longer to be confirmed. (For information on how to set an expiration for transactions, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).)
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Wait for Validation" before="// Check Available" language="js" /%}
{% /tab %}
{% /tabs %}
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" /%}
### (Optional) Intermission
The power of Tickets is that you can carry on with your account's business as usual while you are getting Ticketed transactions ready. When you want to send a transaction using a Ticket, you can do that in parallel with other sending transactions, including ones using different Tickets, and submit a Ticketed transaction at any time. The only constraint is that each Ticket can only be used once.
{% admonition type="success" name="Tip" %}You can come back here to send Sequenced transactions between or during any of the following steps, without interfering with the success of your Ticketed transaction.{% /admonition %}
{% interactive-block label="Intermission" steps=$frontmatter.steps %}
<button id="intermission-payment" class="btn btn-primary previous-steps-required">Payment</button>
<button id="intermission-escrowcreate" class="btn btn-primary previous-steps-required">EscrowCreate</button>
<button id="intermission-accountset" class="btn btn-primary previous-steps-required">AccountSet</button>
<div class="output-area"></div>
{% /interactive-block %}
### 7. Check Available Tickets
When you want to send a Ticketed transaction, you need to know what Ticket Sequence number to use for it. If you've been keeping careful track of your account, you already know which Tickets you have, but if you're not sure, you can use the [account_objects method][] to look up your available tickets. For example:
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Check Available Tickets" before="// Prepare and Sign Ticketed" language="js" /%}
{% /tab %}
{% /tabs %}
{% interactive-block label="Check Tickets" steps=$frontmatter.steps %}
<button id="check-tickets" class="btn btn-primary previous-steps-required">Check Tickets</button>
<div class="output-area"></div>
{% /interactive-block %}
{% admonition type="success" name="Tip" %}You can repeat the steps from here through the end as long as you have Tickets left to be used!{% /admonition %}
### 8. Prepare Ticketed Transaction
Now that you have a Ticket available, you can prepare a transaction that uses it.
This can be any [type of transaction](../../../references/protocol/transactions/types/index.md) you like. The following example uses a no-op [AccountSet transaction][] since that doesn't require any other setup in the ledger. Set the `Sequence` field to `0` and include a `TicketSequence` field with the Ticket Sequence number of one of your available Tickets.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Prepare and Sign Ticketed" before="// Submit Ticketed Transaction" language="js" /%}
{% /tab %}
{% /tabs %}
{% admonition type="success" name="Tip" %}
If you don't plan to submit the TicketCreate transaction right away, you should be sure not to set the `LastLedgerSequence` so that the transaction does not expire. The way you do this varies by library:
- **xrpl.js:** Specify `"LastLedgerSequence": null` when auto-filling the transaction.
- **`rippled`:** Omit `LastLedgerSequence` from the prepared instructions. The server does not provide a value by default.
{% admonition type="info" name="Note" %}
When you're building production-ready software, use an existing account and manage your keys using a [secure signing configuration](../../../concepts/transactions/secure-signing.md).
{% /admonition %}
{% interactive-block label="Prepare Ticketed Tx" steps=$frontmatter.steps %}
### 3. Create Tickets
<div id="ticket-selector">
<h4>Select a Ticket:</h4>
<div class="form-area"></div>
</div>
<button id="prepare-ticketed-tx" class="btn btn-primary previous-steps-required">Prepare Ticketed Transaction</button>
<div class="output-area"></div>
Send a [TicketCreate transaction][] and set the `TicketCount` field to the number of Tickets you want to create (up to 250 per account). Each new Ticket reserves a future [Sequence Number][] you can use later. Each Ticket also counts toward your account's [owner reserve](../../../concepts/accounts/reserves.md), which is released when the Ticket is used. Tickets stay reserved on the ledger until you consume them, regardless of any other transactions your account sends.
{% /interactive-block %}
### 9. Submit Ticketed Transaction
Submit the signed transaction blob that you created in the previous step. For example:
Submit the transaction and wait for validation. For example, to create 10 Tickets:
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Submit Ticketed Transaction" before="// Wait for Validation (again)" language="js" /%}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Create Tickets" before="// Check Available Tickets" language="js" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/use-tickets/py/use-tickets.py" from="# Create Tickets" before="# Check Available Tickets" language="py" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/use-tickets/go/main.go" from="// Create Tickets" before="// Check Available Tickets" language="go" /%}
{% /tab %}
{% /tabs %}
{% interactive-block label="Submit Ticketed Tx" steps=$frontmatter.steps %}
{% admonition type="info" name="Note" %}
A transaction is not final until it is validated by [consensus](../../../concepts/consensus-protocol/index.md), which typically occurs 4-7 seconds after submission. The submit-and-wait call above (`submitAndWait` in xrpl.js, `submit_and_wait` in xrpl-py, `SubmitTxAndWait` in xrpl-go) blocks until validation completes, so the validated result is already available.
<button id="ticketedtx-submit" class="btn btn-primary previous-steps-required" data-tx-blob-from="#tx_blob_t" data-wait-step-name="Wait Again">Submit</button>
<div class="output-area"></div>
For production code that needs to handle longer waits or transaction expiration, see [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md).
{% /admonition %}
{% /interactive-block %}
### 4. Check available Tickets
To send a Ticketed transaction, you need a Ticket Sequence number. Use the [account_objects method][] to list your Tickets. See the method's [Response Format](../../../references/http-websocket-apis/public-api-methods/account-methods/account_objects.md#response-format) for the response shape.
### 10. Wait for Validation
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Check Available Tickets" before="// Use a Ticket" language="js" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/use-tickets/py/use-tickets.py" from="# Check Available Tickets" before="# Use a Ticket" language="py" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/use-tickets/go/main.go" from="// Check Available Tickets" before="// Use a Ticket" language="go" /%}
{% /tab %}
{% /tabs %}
Ticketed transactions go through the consensus process the same way that Sequenced transactions do.
You can repeat the next step as long as you have unused Tickets.
{% partial file="/docs/_snippets/interactive-tutorials/wait-step.md" variables={label: "Wait Again"} /%}
### 5. Use a Ticket
Now that you have a Ticket available, you can use it in a transaction. This can be any [type of transaction](../../../references/protocol/transactions/types/index.md). You can use Tickets in any order, but each can only be used once. For the multi-signing variant, see [With Multi-Signing](#with-multi-signing) below.
{% admonition type="info" name="The Ticket pattern" %}
For any transaction that uses a Ticket, set the `Sequence` field to `0` and include a `TicketSequence` field with one of your available Ticket Sequence numbers.
{% /admonition %}
The following example uses an [AccountSet transaction][] with no fields set, making it a no-op that requires no setup. Ticketed transactions go through the same [consensus](../../../concepts/consensus-protocol/index.md) process as Sequenced transactions.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Use a Ticket" language="js" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/use-tickets/py/use-tickets.py" from="# Use a Ticket" language="py" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/use-tickets/go/main.go" from="// Use a Ticket" language="go" /%}
{% /tab %}
{% /tabs %}
After the transaction is validated, the Ticket is consumed and removed from the ledger. To verify, re-run step 4, and the Ticket you used should no longer appear in the result.
{% admonition type="success" name="Tip" %}
You can use this same pattern to cancel an unused Ticket — just send a no-op AccountSet using the Ticket you no longer need.
{% /admonition %}
## With Multi-Signing
One of the main use cases for Tickets is to be able to collect signatures for several [multi-signed transactions](../../../concepts/accounts/multi-signing.md) in parallel. By using a Ticket, you can send a multi-signed transaction as soon as it is fully signed and ready to go, without worrying about which one will be ready first. <!-- STYLE_OVERRIDE: will -->
Reserve a Ticket for each [multi-signed transaction](../../../concepts/accounts/multi-signing.md) to hold its slot while you collect signatures. Then submit each transaction as soon as its signatures are complete, in any order.
In this scenario, [step 8, "Prepare Ticketed Transaction"](#8-prepare-ticketed-transaction) is slightly different. Instead of preparing and signing all at once, you would follow the steps for [sending any multi-signed transaction](../../best-practices/key-management/send-a-multi-signed-transaction.md): first prepare the transaction, then circulate it among trusted signers to collect their signatures, and finally combine the signatures into the final multi-signed transaction.
In this scenario, [step 5: Use a Ticket](#5-use-a-ticket) becomes a multi-step process: prepare the transaction, circulate it among signers, and combine their signatures. See [Send a Multi-Signed Transaction](../../best-practices/key-management/send-a-multi-signed-transaction.md) for more details.
You could do this in parallel for several different potential transactions as long as each one uses a different Ticket.
For complete working examples that combine Tickets and multi-signing, see `use-tickets-multisig.js` (JavaScript) or `use-tickets-to-multisign.py` (Python) in the [code samples directory](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/use-tickets/). A Go variant isn't currently available.
You can prepare multiple multi-signed transactions simultaneously, each using a different Ticket.
{% admonition type="success" name="Tip" %}
For this scenario, omit the `LastLedgerSequence` field so that the transaction does not expire while you collect signatures. How you do this varies by library:
- **xrpl.js:** Specify `"LastLedgerSequence": null` when auto-filling the transaction.
- **xrpl-py:** Set `last_ledger_sequence=None` on the transaction.
- **xrpl-go:** Leave the field unset before signing.
- **`rippled` directly:** Omit the field from the prepared instructions. The server doesn't provide a default.
{% /admonition %}
## See Also
- **Concepts:**
- [Tickets](../../../concepts/accounts/tickets.md)
- [Multi-Signing](../../../concepts/accounts/multi-signing.md)
- [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md)
- **Tutorials:**
- [Set Up Multi-Signing](../../best-practices/key-management/set-up-multi-signing.md)
- [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md)
- [Send a Multi-Signed Transaction](../../best-practices/key-management/send-a-multi-signed-transaction.md)
- **References:**
- [account_objects method][]
- [sign_for method][]
- [submit_multisigned method][]
- [TicketCreate transaction][]
- [Transaction Common Fields](../../../references/protocol/transactions/common-fields.md)
- [Transaction Common Fields][common fields]
{% raw-partial file="/docs/_snippets/common-links.md" /%}